1 #!/usr/bin/python pseudoserver.py
4 # based on psm_quotes.py, which is based on psm_limitserv.py written by
5 # celestin - martin <martin@rizon.net>
11 from istring
import istring
12 from pseudoclient
import sys_log
, sys_options
, sys_channels
, inviteable
20 import cmd_admin
, sys_auth
, trivia_engine
22 import pyva_net_rizon_acid_core_AcidCore
as AcidCore
26 inviteable
.InviteablePseudoclient
30 # (table name, display name); display name is currently forced to be
31 # compatible with original Trivia
32 themes
= (('anime', 'Anime'),
33 ('default', 'default'),
34 ('geography', 'Geography'),
35 ('history', 'History'),
36 ('lotr-books', 'LOTR-Books'),
37 ('lotr-movies', 'LOTR-Movies'),
40 ('sciandnature', 'ScienceAndNature'),
41 ('simpsons', 'Simpsons'),
42 ('sg1qs', 'Stargate'))
44 # channel name => Trivia instance
47 def start_threads(self
):
52 def bind_function(self
, function
):
53 func
= types
.MethodType(function
, self
, trivia
)
54 setattr(trivia
, function
.__name
__, func
)
57 def bind_admin_commands(self
):
58 list = cmd_admin
.get_commands()
59 self
.commands_admin
= []
62 self
.commands_admin
.append((command
, {'permission': 'j', 'callback': self
.bind_function(list[command
][0]),
63 'usage': list[command
][1]}))
66 AcidPlugin
.__init
__(self
)
69 self
.log
= logging
.getLogger(__name__
)
72 self
.nick
= istring(self
.config
.get('trivia', 'nick'))
73 except Exception, err
:
74 self
.log
.exception("Error reading 'trivia:nick' configuration option: %s" % err
)
78 self
.chan
= istring(self
.config
.get('trivia', 'channel'))
79 except Exception, err
:
80 self
.log
.exception("Error reading 'trivia:channel' configuration option: %s" % err
)
83 self
.bind_admin_commands()
87 self
.dbp
.execute("CREATE TABLE IF NOT EXISTS trivia_chans (id INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(200) NOT NULL, theme VARCHAR(64), UNIQUE KEY(name)) ENGINE=MyISAM")
88 self
.dbp
.execute("CREATE TABLE IF NOT EXISTS trivia_scores (nick VARCHAR(30), points INT(10) DEFAULT '0', fastest_time FLOAT, highest_streak INT(10), channel INT(10)) ENGINE=MyISAM")
89 except Exception, err
:
90 self
.log
.exception("Error creating table for trivia module (%s)" % err
)
94 AcidPlugin
.start(self
)
95 inviteable
.InviteablePseudoclient
.start(self
)
97 self
.options
= sys_options
.OptionManager(self
)
98 self
.elog
= sys_log
.LogManager(self
)
99 self
.auth
= sys_auth
.TriviaAuthManager(self
)
100 self
.channels
= sys_channels
.ChannelManager(self
)
101 except Exception, err
:
102 self
.log
.exception('Error initializing subsystems for trivia module (%s)' % err
)
105 if not AcidCore
.me
.isBursting():
106 for channel
in self
.channels
.list_valid():
107 self
.join(channel
.name
)
109 self
.elog
.debug('Joined channels.')
113 except Exception, err
:
114 self
.log
.exception('Error starting threads for trivia module (%s)' % err
)
117 self
.initialized
= True
118 self
.elog
.debug('Started threads.')
122 for channel
in self
.channels
.list_valid():
123 self
.join(channel
.name
)
125 self
.elog
.debug('Joined channels.')
128 if hasattr(self
, 'auth'):
131 if hasattr(self
, 'channels'):
133 self
.channels
.force()
136 self
.channels
.db_close()
138 if hasattr(self
, 'options'):
143 self
.options
.db_close()
145 for cname
, trivia
in self
.trivias
.iteritems():
150 def join(self
, channel
):
151 me
= self
.inter
.findUser(self
.nick
)
153 self
.dbp
.execute("INSERT IGNORE INTO trivia_chans(name) VALUES(%s)", (str(channel
),))
155 def part(self
, channel
):
156 me
= self
.inter
.findUser(self
.nick
)
159 self
.stop_trivia(channel
, True)
160 self
.dbp
.execute("DELETE FROM trivia_chans WHERE name=%s", (str(channel
),))
162 def msg(self
, target
, message
):
164 self
.inter
.privmsg(self
.nick
, target
, format_ascii_irc(message
))
166 def multimsg(self
, target
, count
, intro
, separator
, pieces
, outro
= ''):
169 while cur
< len(pieces
):
170 self
.msg(target
, intro
+ separator
.join(pieces
[cur
:cur
+ count
]) + outro
)
173 def notice(self
, target
, message
):
175 self
.inter
.notice(self
.nick
, target
, format_ascii_irc(message
))
179 def get_cid(self
, cname
):
180 """Fetches the channel id for a given channel name."""
181 self
.dbp
.execute("SELECT id FROM trivia_chans WHERE name = %s", (cname
,))
182 cid
= self
.dbp
.fetchone()[0]
185 def stop_trivia(self
, cname
, forced
):
186 '''Stops a trivia instance and removes it from our dict.'''
187 if istring(cname
) not in self
.trivias
:
190 self
.trivias
[istring(cname
)].stop(forced
)
191 del self
.trivias
[istring(cname
)]
193 def onPrivmsg(self
, source
, target
, message
):
194 # Parse ADD/DEL requests
195 if not self
.initialized
:
199 # if inviteable didn't catch the command, it means we can handle it here instead
200 if not inviteable
.InviteablePseudoclient
.onPrivmsg(self
, source
, target
, message
):
203 myself
= self
.inter
.findUser(self
.nick
)
206 userinfo
= self
.inter
.findUser(source
)
207 sender
= userinfo
['nick']
209 msg
= message
.strip()
210 index
= msg
.find(' ')
216 command
= msg
[:index
]
217 arg
= msg
[index
+ 1:]
219 command
= command
.lower()
221 if self
.channels
.is_valid(channel
): # a channel message
222 if command
.startswith("."): # a command
223 # Log each and every command. This is very, very abusive and
224 # thus should NOT be used in production if it can be helped.
225 # But who cares about ethics? Well, I apparently don't.
226 self
.elog
.debug("%s:%s > %s" % (sender
, channel
, msg
[1:]))
227 command
= command
[1:]
228 if command
== 'help' and arg
== '':
229 self
.notice(sender
, "Trivia: .help trivia - for trivia commands.")
230 elif command
== 'help' and arg
.startswith('trivia'):
231 self
.notice(sender
, "Trivia: .trivia [number] - to start playing.")
232 self
.notice(sender
, "Trivia: .strivia - to stop current round.")
233 self
.notice(sender
, "Trivia: .topten/.tt - lists top ten players.")
234 self
.notice(sender
, "Trivia: .rank [nick] - shows yours or given nicks current rank.")
235 # Defunct in orig trivia
236 #self.notice(sender, "Trivia: .next - skips question.")
237 self
.notice(sender
, "Trivia: .themes - lists available question themes.")
238 self
.notice(sender
, "Trivia: .theme set <name> - changes current question theme (must be channel founder).")
239 elif command
== 'trivia':
240 if istring(channel
) in self
.trivias
:
241 self
.elog
.debug("Trivia, but we're in %s" % channel
)
245 if arg
.isdigit() and arg
> 0:
248 self
.dbp
.execute("SELECT theme FROM trivia_chans WHERE name = %s", (channel
,))
249 theme
= self
.dbp
.fetchone()[0]
253 self
.trivias
[istring(channel
)] = trivia_engine
.Trivia(channel
, self
, theme
, int(rounds
))
254 elif command
== 'strivia':
255 # stop_trivia does sanity checking
256 self
.stop_trivia(channel
, True)
257 elif command
== 'topten' or command
== 'tt':
258 self
.dbp
.execute("SELECT nick, points FROM trivia_scores WHERE channel = %s ORDER BY points DESC LIMIT 10", (self
.get_cid(channel
),))
259 # XXX: This is silent if no entries have been done; but
260 # original trivia does so, too. Silly behavior?
262 for i
, row
in enumerate(self
.dbp
.fetchall()):
263 # i+1 = 1 should equal out[0]
264 out
.append("%d. %s %d" % (i
+1, row
[0], row
[1]))
266 self
.notice(sender
, '; '.join(out
))
267 elif command
== 'rank':
268 self
.dbp
.execute("SELECT nick, points FROM trivia_scores WHERE channel = %s ORDER BY points DESC", (self
.get_cid(channel
),))
269 rows
= self
.dbp
.fetchall()
271 # XXX: This is inefficient
272 for i
, row
in enumerate(rows
):
273 if row
[0].lower() == sender
.lower():
274 out
= "You are currently ranked #%d with %d points" % (i
+1, row
[1])
276 out
+= ", %d points behind %s" % (rows
[i
-1][1] - row
[1], rows
[i
-1][0])
280 self
.notice(sender
, out
)
281 #elif command == 'next': # Defunct in orig trivia
283 elif command
== 'themes':
284 for theme
in self
.themes
:
285 # stupid original trivia design is stupid and breaks good
286 # db coding practice >.>
287 self
.dbp
.execute("SELECT COUNT(*) FROM `trivia_questions_%s`" % theme
[0])
288 self
.notice(sender
, "Theme: %s, %d questions" %
289 (theme
[1], self
.dbp
.fetchone()[0]))
290 elif command
== 'theme':
291 args
= arg
.split(' ')
293 if len(args
) < 2 or args
[0].lower() != 'set':
296 self
.notice(sender
, "Checking if you are the channel founder.")
297 self
.auth
.request(sender
, channel
, 'set_theme_' + args
[1])
298 else: # not a command, but might be an answer!
299 if istring(channel
) not in self
.trivias
:
300 return # no trivia running, disregard that
302 self
.trivias
[istring(channel
)].check_answer(sender
, msg
)
304 def onChanModes(self
, prefix
, channel
, modes
):
305 if not self
.initialized
:
308 if not modes
== '-z':
311 if channel
in self
.channels
:
312 self
.channels
.remove(channel
)
314 self
.dbp
.execute("DELETE FROM trivia_scores WHERE channel = %s", (self
.get_cid(channel
),))
317 self
.dbp
.execute("DELETE FROM trivia_chans WHERE name = %s", (channel
,))
318 self
.elog
.request('Channel @b%s@b was dropped. Deleting it.' % channel
)
320 def getCommands(self
):
321 return self
.commands_admin