]> jfr.im git - irc/rizon/acid.git/blob - pyva/pyva/src/main/python/quotes/quotes.py
Split pyva plugin into pyva.core and pyva.pyva
[irc/rizon/acid.git] / pyva / pyva / src / main / python / quotes / quotes.py
1 #!/usr/bin/python pseudoserver.py
2 # psm_quotes.py
3 # based on psm_limitserv.py written by celestin - martin <martin@rizon.net>
4
5 import sys
6 import types
7 import random
8 from istring import istring
9 from pseudoclient import sys_log, sys_options, sys_channels, inviteable
10 from utils import *
11
12 from pyva import *
13 import logging
14 from core import *
15 from plugin import *
16
17 import cmd_admin, sys_auth
18
19 class quotes(
20 AcidPlugin,
21 inviteable.InviteablePseudoclient
22 ):
23 initialized = False
24
25 def start_threads(self):
26 self.options.start()
27 self.channels.start()
28 self.auth.start()
29
30 def bind_function(self, function):
31 func = types.MethodType(function, self, quotes)
32 setattr(quotes, function.__name__, func)
33 return func
34
35 def bind_admin_commands(self):
36 list = cmd_admin.get_commands()
37 self.commands_admin = []
38
39 for command in list:
40 self.commands_admin.append((command, {'permission': 'j', 'callback': self.bind_function(list[command][0]),
41 'usage': list[command][1]}))
42
43 def __init__(self):
44 AcidPlugin.__init__(self)
45
46 self.name = "quotes"
47 self.log = logging.getLogger(__name__)
48
49 try:
50 self.nick = istring(self.config.get('quotes', 'nick'))
51 except Exception, err:
52 self.log.exception("Error reading 'quotes:nick' configuration option: %s" % err)
53 raise
54
55 try:
56 self.chan = istring(self.config.get('quotes', 'channel'))
57 except Exception, err:
58 self.log.exception("Error reading 'quotes:channel' configuration option: %s" % err)
59 raise
60
61 self.bind_admin_commands()
62
63 def start(self):
64 try:
65 self.dbp.execute("CREATE TABLE IF NOT EXISTS quotes_chans (id INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(200) NOT NULL, UNIQUE KEY(name)) ENGINE=MyISAM;")
66 self.dbp.execute("CREATE TABLE IF NOT EXISTS quotes_quotes (id INT(10) NOT NULL, quote VARCHAR(512) NOT NULL, nick VARCHAR(30) NOT NULL, time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, channel INT NOT NULL, KEY `chan_index` (`channel`)) ENGINE=MyISAM;")
67 except Exception, err:
68 self.log.exception("Error creating table for quotes module (%s)" % err)
69 raise
70
71 try:
72 AcidPlugin.start(self)
73 inviteable.InviteablePseudoclient.start(self)
74
75 self.options = sys_options.OptionManager(self)
76 self.elog = sys_log.LogManager(self)
77 self.auth = sys_auth.QuotesAuthManager(self)
78 self.channels = sys_channels.ChannelManager(self)
79 except Exception, err:
80 self.log.exception('Error initializing subsystems for quotes module (%s)' % err)
81 raise
82
83 for channel in self.channels.list_valid():
84 self.join(channel.name)
85
86 self.elog.debug('Joined channels.')
87
88 try:
89 self.start_threads()
90 except Exception, err:
91 self.log.exception('Error starting threads for quotes module (%s)' % err)
92 raise
93
94 self.initialized = True
95 self.elog.debug('Started threads.')
96 return True
97
98 def onSync(self):
99 for channel in self.channels.list_valid():
100 self.join(channel.name)
101
102 self.elog.debug('Joined channels.')
103
104 def stop(self):
105 if hasattr(self, 'auth'):
106 self.auth.stop()
107
108 if hasattr(self, 'channels'):
109 if self.initialized:
110 self.channels.force()
111
112 self.channels.stop()
113 self.channels.db_close()
114
115 if hasattr(self, 'options'):
116 if self.initialized:
117 self.options.force()
118
119 self.options.stop()
120 self.options.db_close()
121
122 def join(self, channel):
123 me = self.inter.findUser(self.nick)
124 me.joinChan(channel)
125 self.dbp.execute("INSERT IGNORE INTO quotes_chans(name) VALUES(%s)", (str(channel),))
126
127 def part(self, channel):
128 me = self.inter.findUser(self.nick)
129 me.partChan(channel)
130
131 self.dbp.execute("DELETE FROM quotes_quotes WHERE channel=%s", (self.get_cid(channel),))
132 self.dbp.execute("DELETE FROM quotes_chans WHERE name=%s", (str(channel),))
133
134 def msg(self, target, message):
135 if message != '':
136 self.inter.privmsg(self.nick, target, format_ascii_irc(message))
137
138 def multimsg(self, target, count, intro, separator, pieces, outro = ''):
139 cur = 0
140
141 while cur < len(pieces):
142 self.msg(target, intro + separator.join(pieces[cur:cur + count]) + outro)
143 cur += count
144
145 def notice(self, target, message):
146 if message != '':
147 self.inter.notice(self.nick, target, format_ascii_irc(message))
148
149 def get_cid(self, cname):
150 """Fetches the channel id for a given channel name."""
151 self.dbp.execute("SELECT id FROM quotes_chans WHERE name = %s", (str(cname),))
152 cid = self.dbp.fetchone()[0]
153 return cid
154
155 def read_quote(self, qid, cid, cname):
156 """Fetches the quote id qid from the channel with id cid and displays
157 the result in the channel cname. cname is to be passed on so we can
158 avoid querying the db about the name, which we have in all scenarios
159 where read_quote is called anyway."""
160 qid = str(qid)
161 num = self.dbp.execute("SELECT nick, quote, time FROM quotes_quotes WHERE id = %s AND channel = %s",
162 (qid, cid))
163
164 if num == 0:
165 self.msg(cname, "[Quote] " + qid + " does not exist!")
166 return
167
168 nick, quote, time = self.dbp.fetchone()
169 tdelta = int((datetime.now() - time).total_seconds())
170 tdeltastr = ""
171 years = weeks = days = hours = minutes = 0
172
173 while tdelta > 31540000:
174 years += 1
175 tdelta -= 31540000
176 while tdelta > 604800:
177 weeks += 1
178 tdelta -= 604800
179 while tdelta > 86400:
180 days += 1
181 tdelta -= 86400
182 while tdelta > 3600:
183 hours += 1
184 tdelta -= 3600
185 while tdelta > 60:
186 minutes += 1
187 tdelta -= 60
188
189 if years > 0:
190 tdeltastr += str(years) + " year"
191 if years != 1:
192 tdeltastr += "s"
193 tdeltastr += " "
194 if weeks > 0:
195 tdeltastr += str(weeks) + " week"
196 if weeks != 1:
197 tdeltastr += "s"
198 tdeltastr += " "
199 if days > 0:
200 tdeltastr += str(days) + " day"
201 if days != 1:
202 tdeltastr += "s"
203 tdeltastr += " "
204 if hours > 0:
205 tdeltastr += str(hours) + " hour"
206 if hours != 1:
207 tdeltastr += "s"
208 tdeltastr += " "
209 if minutes > 0:
210 tdeltastr += str(minutes) + " minute"
211 if minutes != 1:
212 tdeltastr += "s"
213 tdeltastr += " "
214 if tdelta > 0:
215 tdeltastr += str(tdelta) + " second"
216 if tdelta != 1:
217 tdeltastr += "s"
218
219 self.msg(cname, "[Quote] #%s added by %s %s ago." % (qid, nick, tdeltastr))
220 self.inter.privmsg(self.nick, cname, '[Quote] %s' % quote)
221 return
222
223 def onPrivmsg(self, source, target, message):
224 # Parse ADD/DEL requests
225 if not self.initialized:
226 return
227
228 # HACKY
229 # if inviteable didn't catch the command, it means we can handle it here instead
230 if not inviteable.InviteablePseudoclient.onPrivmsg(self, source, target, message):
231 return
232
233 myself = self.inter.findUser(self.nick)
234 channel = target
235
236 userinfo = self.inter.findUser(source)
237 sender = userinfo['nick']
238
239 msg = message.strip()
240 index = msg.find(' ')
241
242 if index == -1:
243 command = msg
244 arg = ''
245 else:
246 command = msg[:index]
247 arg = msg[index + 1:]
248
249 command = command.lower()
250
251 if self.channels.is_valid(channel) and command.startswith("."): # a channel message
252 command = command[1:]
253 if command == 'help' and arg == '':
254 self.notice(sender, "Quotes: .help quotes - for quote commands.")
255 elif command == 'help' and arg.startswith('quotes'):
256 self.notice(sender, "Quotes: .quote add <quote> - adds given quote to database, must have voice or higher on channel.")
257 self.notice(sender, "Quotes: .quote del <number> - removes quote number from database, must be channel founder.")
258 self.notice(sender, "Quotes: .quote search <word> - searches database for given word.")
259 self.notice(sender, "Quotes: .quote read <number> - messages quote matching given number.")
260 self.notice(sender, "Quotes: .quote random - messages a random quote.")
261 self.notice(sender, "Quotes: .quote total - messages number of quotes in database.")
262 elif command == 'quote':
263 args = arg.split(' ')
264 cid = self.get_cid(channel)
265
266 if args[0] == 'add':
267 c = self.inter.findChannel(channel)
268 # Voice is the lowest rank, i.e., we can just check for emptiness
269 if not c or c.getModes(sender) == "":
270 self.notice(sender, "You must have voice or higher on the channel to add quotes.")
271 return
272 quote = istring(' '.join(args[1:]))
273
274 # Extremely inefficient. If possible, somehow merge into
275 # just one MySQL query
276 # MAX(id) may return NULL if (and only if) there are no results, i.e., 0
277 # quotes. Work around that with IFNULL(expr,elsevalue).
278 self.dbp.execute("SELECT IFNULL(MAX(id)+1,1) FROM quotes_quotes WHERE channel = %s", (cid,))
279 qid = self.dbp.fetchone()[0]
280
281 self.dbp.execute("INSERT INTO quotes_quotes(id, quote, nick, channel) VALUES(%s, %s, %s, %s)",
282 (qid, str(quote), sender, cid))
283
284 self.msg(channel, "[Quote] Added quote #%d by %s" % (qid, sender))
285 self.inter.privmsg(self.nick, channel, '[Quote] %s' % quote)
286
287 if args[0] == 'del':
288 if len(args) > 1 and not args[1].isdigit():
289 self.notice(sender, args[1] + " is not a valid quote number.")
290 return
291
292 self.notice(sender, "Checking if you are the channel founder.")
293 self.auth.request(sender, channel, 'delete_quote' + args[1])
294
295 if args[0] == 'search':
296 if len(args) < 2:
297 return
298
299 num = self.dbp.execute("SELECT id FROM quotes_quotes WHERE channel = %s AND quote LIKE CONCAT('%%',%s,'%%')",
300 (cid, args[1]))
301 res = self.dbp.fetchall()
302 if num == 0:
303 self.msg(channel, "[Quote] No quotes found.")
304 return
305 if num > 1:
306 ids = []
307 for row in res:
308 ids.append(str(row[0]))
309 self.msg(channel, "[Quote] %d matches found: #%s" % (num, ','.join(ids)))
310 return
311
312 self.read_quote(res[0][0], cid, channel)
313
314 if args[0] == 'read':
315 if len(args) < 2:
316 return
317
318 self.read_quote(args[1], cid, channel)
319
320 if args[0] == 'random':
321 self.dbp.execute("SELECT id FROM quotes_quotes WHERE channel = %s ORDER BY RAND() LIMIT 1", (cid,))
322 res = self.dbp.fetchall()
323
324 if not res:
325 self.msg(channel, "[Quote] No quotes found!")
326 return
327
328 self.read_quote(res[0][0], cid, channel)
329
330 if args[0] == 'total':
331 self.dbp.execute("SELECT COUNT(id) FROM quotes_quotes WHERE channel = %s", (cid,))
332 qtotal = self.dbp.fetchone()[0]
333
334 if qtotal != 1:
335 self.msg(channel, "[Quote] %d quotes in total" % qtotal)
336 else:
337 self.msg(channel, "[Quote] %d quote in total" % qtotal)
338
339 if args[0] == 'last':
340 self.dbp.execute("SELECT id FROM quotes_quotes where channel = %s ORDER BY id DESC LIMIT 1", (cid,))
341 res = self.dbp.fetchall()
342
343 if not res:
344 self.msg(channel, "[Quote] No quotes found!")
345 return
346
347 self.read_quote(res[0][0], cid, channel)
348
349 return
350
351 def delete_quote(self, channel, user, qid):
352 cid = self.get_cid(channel)
353
354 self.dbp.execute("DELETE FROM quotes_quotes WHERE id = %s AND channel = %s", (qid, cid))
355 self.notice(user, "Deleted quote #%d" % qid)
356 return
357
358 def onChanModes(self, prefix, channel, modes):
359 if not self.initialized:
360 return
361
362 if not modes == '-z':
363 return
364
365 if channel in self.channels:
366 self.channels.remove(channel)
367 try:
368 self.dbp.execute("DELETE FROM quotes_quotes WHERE channel = %s", (self.get_cid(channel),))
369 except:
370 pass
371 self.dbp.execute("DELETE FROM quotes_chans WHERE name = %s", (channel,))
372 self.elog.request('Channel @b%s@b was dropped. Deleting it.' % channel)
373
374 def getCommands(self):
375 return self.commands_admin