]>
jfr.im git - irc/rizon/acid.git/blob - pyva/src/main/python/trivia/trivia.py
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
24 inviteable
. InviteablePseudoclient
28 # (table name, display name); display name is currently forced to be
29 # compatible with original Trivia
30 themes
= (( 'anime' , 'Anime' ),
31 ( 'default' , 'default' ),
32 ( 'geography' , 'Geography' ),
33 ( 'history' , 'History' ),
34 ( 'lotr-books' , 'LOTR-Books' ),
35 ( 'lotr-movies' , 'LOTR-Movies' ),
38 ( 'sciandnature' , 'ScienceAndNature' ),
39 ( 'simpsons' , 'Simpsons' ),
40 ( 'sg1qs' , 'Stargate' ))
42 # channel name => Trivia instance
45 def start_threads ( self
):
50 def bind_function ( self
, function
):
51 func
= types
. MethodType ( function
, self
, trivia
)
52 setattr ( trivia
, function
.__ name
__ , func
)
55 def bind_admin_commands ( self
):
56 list = cmd_admin
. get_commands ()
57 self
. commands_admin
= []
60 self
. commands_admin
. append (( command
, { 'permission' : 'j' , 'callback' : self
. bind_function ( list [ command
][ 0 ]),
61 'usage' : list [ command
][ 1 ]}))
64 AcidPlugin
.__ init
__ ( self
)
67 self
. log
= logging
. getLogger ( __name__
)
70 self
. nick
= istring ( self
. config
. get ( 'trivia' , 'nick' ))
71 except Exception , err
:
72 self
. log
. exception ( "Error reading 'trivia:nick' configuration option: %s " % err
)
76 self
. chan
= istring ( self
. config
. get ( 'trivia' , 'channel' ))
77 except Exception , err
:
78 self
. log
. exception ( "Error reading 'trivia:channel' configuration option: %s " % err
)
81 self
. bind_admin_commands ()
85 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" )
86 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" )
87 except Exception , err
:
88 self
. log
. exception ( "Error creating table for trivia module ( %s )" % err
)
92 AcidPlugin
. start ( self
)
93 inviteable
. InviteablePseudoclient
. start ( self
)
95 self
. options
= sys_options
. OptionManager ( self
)
96 self
. elog
= sys_log
. LogManager ( self
)
97 self
. auth
= sys_auth
. TriviaAuthManager ( self
)
98 self
. channels
= sys_channels
. ChannelManager ( self
)
99 except Exception , err
:
100 self
. log
. exception ( 'Error initializing subsystems for trivia module ( %s )' % err
)
103 for channel
in self
. channels
. list_valid ():
104 self
. join ( channel
. name
)
106 self
. elog
. debug ( 'Joined channels.' )
110 except Exception , err
:
111 self
. log
. exception ( 'Error starting threads for trivia module ( %s )' % err
)
114 self
. initialized
= True
115 self
. elog
. debug ( 'Started threads.' )
119 for channel
in self
. channels
. list_valid ():
120 self
. join ( channel
. name
)
122 self
. elog
. debug ( 'Joined channels.' )
125 if hasattr ( self
, 'auth' ):
128 if hasattr ( self
, 'channels' ):
130 self
. channels
. force ()
133 self
. channels
. db_close ()
135 if hasattr ( self
, 'options' ):
140 self
. options
. db_close ()
142 for cname
, trivia
in self
. trivias
. iteritems ():
147 def join ( self
, channel
):
148 me
= self
. inter
. findUser ( self
. nick
)
150 self
. dbp
. execute ( "INSERT IGNORE INTO trivia_chans(name) VALUES( %s )" , ( str ( channel
),))
152 def part ( self
, channel
):
153 me
= self
. inter
. findUser ( self
. nick
)
156 self
. stop_trivia ( channel
, True )
157 self
. dbp
. execute ( "DELETE FROM trivia_chans WHERE name= %s " , ( str ( channel
),))
159 def msg ( self
, target
, message
):
161 self
. inter
. privmsg ( self
. nick
, target
, format_ascii_irc ( message
))
163 def multimsg ( self
, target
, count
, intro
, separator
, pieces
, outro
= '' ):
166 while cur
< len ( pieces
):
167 self
. msg ( target
, intro
+ separator
. join ( pieces
[ cur
: cur
+ count
]) + outro
)
170 def notice ( self
, target
, message
):
172 self
. inter
. notice ( self
. nick
, target
, format_ascii_irc ( message
))
176 def get_cid ( self
, cname
):
177 """Fetches the channel id for a given channel name."""
178 self
. dbp
. execute ( "SELECT id FROM trivia_chans WHERE name = %s " , ( cname
,))
179 cid
= self
. dbp
. fetchone ()[ 0 ]
182 def stop_trivia ( self
, cname
, forced
):
183 '''Stops a trivia instance and removes it from our dict.'''
184 if istring ( cname
) not in self
. trivias
:
187 self
. trivias
[ istring ( cname
)]. stop ( forced
)
188 del self
. trivias
[ istring ( cname
)]
190 def onPrivmsg ( self
, source
, target
, message
):
191 # Parse ADD/DEL requests
192 if not self
. initialized
:
196 # if inviteable didn't catch the command, it means we can handle it here instead
197 if not inviteable
. InviteablePseudoclient
. onPrivmsg ( self
, source
, target
, message
):
200 myself
= self
. inter
. findUser ( self
. nick
)
203 userinfo
= self
. inter
. findUser ( source
)
204 sender
= userinfo
[ 'nick' ]
206 msg
= message
. strip ()
207 index
= msg
. find ( ' ' )
213 command
= msg
[: index
]
214 arg
= msg
[ index
+ 1 :]
216 command
= command
. lower ()
218 if self
. channels
. is_valid ( channel
): # a channel message
219 if command
. startswith ( "." ): # a command
220 # Log each and every command. This is very, very abusive and
221 # thus should NOT be used in production if it can be helped.
222 # But who cares about ethics? Well, I apparently don't.
223 self
. elog
. debug ( " %s : %s > %s " % ( sender
, channel
, msg
[ 1 :]))
224 command
= command
[ 1 :]
225 if command
== 'help' and arg
== '' :
226 self
. notice ( sender
, "Trivia: .help trivia - for trivia commands." )
227 elif command
== 'help' and arg
. startswith ( 'trivia' ):
228 self
. notice ( sender
, "Trivia: .trivia [number] - to start playing." )
229 self
. notice ( sender
, "Trivia: .strivia - to stop current round." )
230 self
. notice ( sender
, "Trivia: .topten/.tt - lists top ten players." )
231 self
. notice ( sender
, "Trivia: .rank [nick] - shows yours or given nicks current rank." )
232 # Defunct in orig trivia
233 #self.notice(sender, "Trivia: .next - skips question.")
234 self
. notice ( sender
, "Trivia: .themes - lists available question themes." )
235 self
. notice ( sender
, "Trivia: .theme set <name> - changes current question theme (must be channel founder)." )
236 elif command
== 'trivia' :
237 if istring ( channel
) in self
. trivias
:
238 self
. elog
. debug ( "Trivia, but we're in %s " % channel
)
242 if arg
. isdigit () and arg
> 0 :
245 self
. dbp
. execute ( "SELECT theme FROM trivia_chans WHERE name = %s " , ( channel
,))
246 theme
= self
. dbp
. fetchone ()[ 0 ]
250 self
. trivias
[ istring ( channel
)] = trivia_engine
. Trivia ( channel
, self
, theme
, int ( rounds
))
251 elif command
== 'strivia' :
252 # stop_trivia does sanity checking
253 self
. stop_trivia ( channel
, True )
254 elif command
== 'topten' or command
== 'tt' :
255 self
. dbp
. execute ( "SELECT nick, points FROM trivia_scores WHERE channel = %s ORDER BY points DESC LIMIT 10" , ( self
. get_cid ( channel
),))
256 # XXX: This is silent if no entries have been done; but
257 # original trivia does so, too. Silly behavior?
259 for i
, row
in enumerate ( self
. dbp
. fetchall ()):
260 # i+1 = 1 should equal out[0]
261 out
. append ( " %d . %s %d " % ( i
+ 1 , row
[ 0 ], row
[ 1 ]))
263 self
. notice ( sender
, '; ' . join ( out
))
264 elif command
== 'rank' :
265 self
. dbp
. execute ( "SELECT nick, points FROM trivia_scores WHERE channel = %s ORDER BY points DESC" , ( self
. get_cid ( channel
),))
266 rows
= self
. dbp
. fetchall ()
268 # XXX: This is inefficient
269 for i
, row
in enumerate ( rows
):
270 if row
[ 0 ]. lower () == sender
. lower ():
271 out
= "You are currently ranked # %d with %d points" % ( i
+ 1 , row
[ 1 ])
273 out
+= ", %d points behind %s " % ( rows
[ i
- 1 ][ 1 ] - row
[ 1 ], rows
[ i
- 1 ][ 0 ])
277 self
. notice ( sender
, out
)
278 #elif command == 'next': # Defunct in orig trivia
280 elif command
== 'themes' :
281 for theme
in self
. themes
:
282 # stupid original trivia design is stupid and breaks good
283 # db coding practice >.>
284 self
. dbp
. execute ( "SELECT COUNT(*) FROM `trivia_questions_ %s `" % theme
[ 0 ])
285 self
. notice ( sender
, "Theme: %s , %d questions" %
286 ( theme
[ 1 ], self
. dbp
. fetchone ()[ 0 ]))
287 elif command
== 'theme' :
288 args
= arg
. split ( ' ' )
290 if len ( args
) < 2 or args
[ 0 ]. lower () != 'set' :
293 self
. notice ( sender
, "Checking if you are the channel founder." )
294 self
. auth
. request ( sender
, channel
, 'set_theme_' + args
[ 1 ])
295 else : # not a command, but might be an answer!
296 if istring ( channel
) not in self
. trivias
:
297 return # no trivia running, disregard that
299 self
. trivias
[ istring ( channel
)]. check_answer ( sender
, msg
)
301 def onChanModes ( self
, prefix
, channel
, modes
):
302 if not self
. initialized
:
305 if not modes
== '-z' :
308 if channel
in self
. channels
:
309 self
. channels
. remove ( channel
)
311 self
. dbp
. execute ( "DELETE FROM trivia_scores WHERE channel = %s " , ( self
. get_cid ( channel
),))
314 self
. dbp
. execute ( "DELETE FROM trivia_chans WHERE name = %s " , ( channel
,))
315 self
. elog
. request ( 'Channel @b %s @b was dropped. Deleting it.' % channel
)
317 def getCommands ( self
):
318 return self
. commands_admin