]> jfr.im git - irc/rizon/acid.git/blob - pyva/pyva/src/main/python/internets/cmd_user.py
Merge branch 'edupe_join/part' of https://gitlab.com/rizon/acid into edupe_join/part
[irc/rizon/acid.git] / pyva / pyva / src / main / python / internets / cmd_user.py
1 import random
2 import re
3 from datetime import datetime, timedelta
4 from xml.parsers.expat import ExpatError
5
6 import core
7 from pseudoclient.cmd_manager import *
8 from utils import *
9 from internets_utils import *
10 from api.feed import FeedError
11 from api.idlerpg import IrpgPlayer
12 from api.quotes import FmlException
13 from api.weather import WeatherException
14 from api.steam import SteamException
15
16 def get_citystate_from_zipcode(self, zipcode):
17 """Return [city,state] for the given U.S. zip code (if database has been imported)"""
18 try:
19 con = core.dbpool.get_connection()
20 try:
21 cursor = con.cursor()
22 cursor.execute("SELECT city, state FROM zipcode_citystate WHERE zipcode=%s", [int(zipcode)])
23 city, state = cursor.fetchone()
24 return city, state
25 finally:
26 core.dbpool.put_connection(con)
27 except:
28 return None
29
30 ##
31 # Returns colour coded test of persona state in Steam.
32 ##
33 def get_personastate_text(self, state):
34 if state == 0:
35 # user is offline.
36 return '@c14OFFLINE@c'
37 elif state == 1:
38 # user is online
39 return '@c3ONLINE@c'
40 elif state == 2:
41 # user is busy
42 return '@c4BUSY@c'
43 elif state == 3:
44 # user is away
45 return '@c7AWAY@c'
46 elif state == 4:
47 # user is snooze
48 return '@c7SNOOZE@c'
49 elif state == 5:
50 # user is looking to trade
51 return '@c5LOOKING TO TRADE@c'
52 elif state == 6:
53 # user is looking to play
54 return '@c10LOOKING TO PLAY@c'
55 else:
56 # unknown status
57 return '@c14UNKNOWN@c'
58
59 def command_weather(self, manager, opts, arg, channel, sender, userinfo):
60 arg = self.get_location(opts, arg, channel, sender)
61 if not arg:
62 return
63 w_state = ''
64 if arg.isdigit():
65 location = get_citystate_from_zipcode(self, arg)
66 if location is None:
67 self.errormsg(channel, 'zip code not recognised.')
68 return False
69 city, state = location
70 location = '{city}, {state}, USA'.format(city=city, state=state)
71 w_state = state + u', '
72 else:
73 location = arg.strip()
74
75 try:
76 w = self.weather.get_conditions(location)
77 except WeatherException as exc:
78 if exc == 'this key is not valid':
79 self.elog.warning('WARNING: OpenWeatherMap API key is not correctly set (%s)' % exc)
80 self.errormsg(channel, 'weather data is temporarily unavailable. Try again later')
81 else:
82 self.errormsg(channel, exc)
83 return
84 except FeedError, e:
85 self.errormsg(channel, e.msg)
86 return
87
88 code = get_tempcolor(w['temp_c'])
89
90 self.msg(channel, format_weather(code, u"""@sep @b{w[city]}{w_state}{w[country]}@b @sep @bConditions@b {w[description]} @sep \
91 @bTemperature@b {tempcolor}{w[temp_c]}C / {w[temp_f]}F@o @sep \
92 @bPressure@b {w[pressure]}mb @sep @bHumidity@b {w[humidity]}% @sep \
93 @bRain@b {w[rain]} @sep \
94 Powered by OpenWeatherMap http://openweathermap.org/city/{w[id]} @sep""".format(w=w, tempcolor=code, w_state=w_state)))
95
96
97 def command_forecast(self, manager, opts, arg, channel, sender, userinfo):
98 arg = self.get_location(opts, arg, channel, sender)
99 if not arg:
100 return
101 w_state = ''
102 if arg.isdigit():
103 location = get_citystate_from_zipcode(self, arg)
104 if location is None:
105 self.errormsg(channel, 'zip code not recognised.')
106 return False
107 city, state = location
108 location = '{city}, {state}, USA'.format(city=city, state=state)
109 w_state = state + u', '
110 else:
111 location = arg.strip()
112
113 try:
114 w = self.weather.get_forecast(location)
115 except WeatherException as exc:
116 if exc == 'this key is not valid':
117 self.elog.warning('WARNING: OpenWeatherMap API key is not correctly set (%s)' % exc)
118 self.errormsg(channel, 'weather data is temporarily unavailable. Try again later')
119 else:
120 self.errormsg(channel, exc)
121 return
122 except FeedError, e:
123 self.errormsg(channel, e.msg)
124 return
125
126 fc = ' @sep '.join([u"""@b{day[name]}@b {day[description]} @c04{day[max_c]}C / {day[max_f]}F \
127 @c10{day[min_c]}C / {day[min_f]}F""".format(day=day) for day in w['days']])
128
129 self.msg(channel, u'@sep @b{w[city]}{w_state}{w[country]}@b @sep {fc} @sep'.format(w=w, fc=fc, w_state=w_state))
130
131
132 def command_register_location(self, manager, opts, arg, channel, sender, userinfo):
133 arg = arg.strip()
134 try:
135 w_state = ''
136 if arg.isdigit():
137 location = get_citystate_from_zipcode(self, arg)
138 if location is None:
139 self.errormsg(channel, 'zip code not recognised.')
140 return False
141 city, state = location
142 location = '{city}, {state}, USA'.format(city=city, state=state)
143 w_state = state + u', '
144 else:
145 location = arg.strip()
146
147 w = self.weather.get_conditions(location)
148 except WeatherException as exc:
149 if exc == 'this key is not valid':
150 self.elog.warning('WARNING: OpenWeatherMap API key is not correctly set (%s)' % exc)
151 self.errormsg(channel, 'weather data is temporarily unavailable. Try again later')
152 else:
153 self.errormsg(channel, exc)
154 return
155 except FeedError, e:
156 self.errormsg(channel, e.msg)
157 return
158
159 loc_name = u'{w[city]}{w_state}{w[country]}'.format(w=w, w_state=w_state)
160
161 self.users.set(sender, 'location', arg)
162 self.msg(channel, u'%s: registered location @b%s@b' % (sender, loc_name))
163
164 def command_bing_translate(self, manager, opts, arg, channel, sender, userinfo):
165 sp = arg.split(' ', 2)
166 try:
167 if len(sp) > 2:
168 source, target, text = sp
169 if source.lower() not in self.bing.languages or target.lower() not in self.bing.languages:
170 source = self.bing.detect_language(arg)
171 target = None
172 translation = self.bing.translate(arg)
173 else:
174 source = source.lower()
175 target = target.lower()
176 translation = self.bing.translate(text, source, target)
177 else:
178 source = self.bing.detect_language(arg)
179 target = None
180 translation = self.bing.translate(arg)
181 except FeedError, e:
182 self.elog.warning('WARNING: Bing translate failed: %s' % e)
183 self.errormsg(channel, e.msg)
184 return
185
186 self.msg(channel, '[t] [from %s] %s' % (source, translation))
187
188 def command_google_search(self, manager, opts, arg, channel, sender, userinfo):
189 try:
190 result = self.google.search(arg, userinfo['ip'] if userinfo['ip'] != '0' else '255.255.255.255')
191 except FeedError, e:
192 self.errormsg(channel, e.msg)
193 return
194
195 if result['responseStatus'] == 403:
196 self.elog.warning('WARNING: Google Search failed: %s' % result['responseDetails'] if 'responseDetails' in result else 'unknown error')
197 self.notice(sender, 'Google Search is temporarily unavailable. Try again later.')
198 return
199
200 result = result['responseData']['results']
201 if not result:
202 self.msg(channel, '[Google] No results found')
203 return
204
205 json = result[0]
206 self.msg(channel, '[Google] @b%(title)s@b <@u%(url)s@u>' % {
207 'title': unescape(json['titleNoFormatting']),
208 'url': json['unescapedUrl']})
209
210 if json['content'] != '':
211 self.msg(channel, '[Google] @bDescription@b: %s' % unescape(json['content']).replace('<b>', '@b').replace('</b>', '@b'))
212
213 def command_google_image_search(self, manager, opts, arg, channel, sender, userinfo):
214 try:
215 result = self.google.image_search(arg, userinfo['ip'] if userinfo['ip'] != '0' else '255.255.255.255')
216 except FeedError, e:
217 self.errormsg(channel, e.msg)
218 return
219
220 if result['responseStatus'] == 403:
221 self.elog.warning('WARNING: Google Image Search failed: %s' % result['responseDetails'] if 'responseDetails' in result else 'unknown error')
222 self.notice(sender, 'Google Search is temporarily unavailable. Try again later.')
223 return
224
225 result = result['responseData']['results']
226 if not result:
227 self.msg(channel, '[Google Image] No results found')
228 return
229
230 json = result[0]
231 self.msg(channel, '[Google Image] @b%(title)s@b <@u%(url)s@u>' % {
232 'title': unescape(json['titleNoFormatting']),
233 'width': json['width'],
234 'height': json['height'],
235 'url': json['unescapedUrl']})
236
237 self.msg(channel, '[Google Image] @bSize@b: %(width)sx%(height)spx%(desc)s' % {
238
239 'width': json['width'],
240 'height': json['height'],
241 'desc': (' - @bDescription@b: %s' % unescape(json['content']).replace('<b>', '@b').replace('</b>', '@b')) if json['content'] else ''})
242
243
244 def command_calc(self, manager, opts, arg, channel, sender, userinfo):
245 try: # local calculation using PyParsing
246 result = self.nsp.eval(arg)
247 self.msg(channel, '[calc] {} = {}'.format(arg, result))
248 except: # just throw it at W|A, hopefully they can get it
249 try:
250 result = self.wolfram.alpha(arg)
251 except FeedError as e:
252 self.errormsg(channel, e.msg)
253 return
254
255 if result is None:
256 self.msg(channel, '[W|A] Invalid input.')
257 else:
258 self.msg(channel, u'[W|A] {r[0]} = {r[1]}'.format(r=result))
259
260 def command_youtube_search(self, manager, opts, arg, channel, sender, userinfo):
261 try:
262 res = self.google.yt_search(arg)
263 except FeedError, e:
264 self.errormsg(channel, e.msg)
265 return
266
267 if not res:
268 self.msg(channel, '[YouTube] No results found')
269 return
270
271 self.msg(channel, """@sep @bYouTube@b %(title)s @sep @bURL@b %(url)s (%(duration)s) @sep @bViews@b %(views)s @sep \
272 @bRating@b %(rating)s/5 - %(votes)s votes @c3@b[+]@b %(liked)s likes @c4@b[-]@b %(disliked)s dislikes @sep""" % {
273 'title': res['title'],
274 'url': 'http://www.youtube.com/watch?v=' + res['id'],
275 'duration': '%s' % format_hms(res['duration']),
276 'views': format_thousand(res['view_count']),
277 'rating': round(res['rating'], 2) if res['rating'] else 0,
278 'votes': format_thousand(res['rate_count']) if res['rate_count'] else 0,
279 'liked': format_thousand(res['liked']) if res['liked'] else 0,
280 'disliked': format_thousand(res['disliked']) if res['disliked'] else 0
281 })
282
283 def command_dictionary(self, manager, opts, arg, channel, sender, userinfo):
284 try:
285 results = self.wordnik.definition(arg)
286 except FeedError, e:
287 self.errormsg(channel, e.msg)
288 return
289
290 if not results:
291 self.msg(channel, '[dictionary] Nothing found')
292 return
293
294 if 'all' in opts:
295 for n, res in enumerate(results, 1):
296 self.notice(sender, u'@sep [{num}/{tot}] @bDefinition@b {res.word} @sep {res.text} @sep'.format(
297 res=res, num=n, tot=len(results)))
298 elif 'number' in opts:
299 if opts['number'] - 1 < 0 or opts['number'] - 1 > len(results):
300 self.errormsg(channel, 'option -n out of range: only %d definitions found.' % len(results))
301 return
302
303 result = results[opts['number'] - 1]
304 self.msg(channel, u'@sep [{num}/{tot}] @bDefinition@b {res.word} @sep {res.text} @sep'.format(
305 res=result, num=opts['number'], tot=len(results)))
306 else:
307 for n, res in enumerate(results, 1):
308 self.msg(channel, u'@sep [{num}/{tot}] @bDefinition@b {res.word} @sep {res.text} @sep'.format(
309 res=res, num=n, tot=len(results)))
310 if n == 4:
311 self.notice(sender, 'To view all definitions: .dict {res.word} -a. To view the n-th definition: .dict {res.word} -n <number>'.format(res=res))
312 break
313
314 def command_urbandictionary(self, manager, opts, arg, channel, sender, userinfo):
315 expr, sep, def_id = arg.partition('/')
316 try:
317 res = self.urbandictionary.get_definitions(expr.strip())
318 except FeedError, e:
319 self.errormsg(channel, e.msg)
320 self.elog.warning('feed error in .urbandictionary: %s' % e)
321 return
322
323 if res['result_type'] == 'no_results' or res['result_type'] == 'fulltext':
324 self.errormsg(channel, 'no results found')
325 elif res['result_type'] == 'exact':
326 if def_id:
327 try:
328 def_id = int(def_id)
329 if def_id < 1:
330 self.errormsg(channel, 'invalid definition number')
331 return
332
333 entry = res['list'][def_id - 1]
334 definition = entry['definition'].replace('\r\n', ' / ').replace('\n', ' / ')
335 example = entry['example'].replace('\r\n', ' / ').replace('\n', ' / ')
336 self.msg(channel, u'@sep [{num}/{total}] {entry[word]} @sep {definition} @sep'.format(
337 num = def_id,
338 total = len(res['list']),
339 res = res,
340 definition = definition if len(definition) < 200 else definition[:200] + '...',
341 entry = entry))
342 self.msg(channel, u'@sep @bExample@b %s @sep' % (example if len(example) < 280 else example[:280] + '...'))
343 except ValueError:
344 self.errormsg(channel, 'invalid definition number')
345 except IndexError:
346 self.errormsg(channel, 'definition id out of range: only %d definitions available' % len(res['list']))
347 else:
348 for num, entry in enumerate(res['list'], 1):
349 if num == 4:
350 self.notice(sender, 'To view a single definition with a related example, type: @b.u %s /def_number@b. For more definitions, visit: %s' % (expr, res['list'][0]['permalink']))
351 break
352
353 definition = entry['definition'].replace('\r\n', ' / ').replace('\n', ' / ')
354 self.msg(channel, u'@sep [{num}/{total}] {entry[word]} @sep {definition} @sep'.format(
355 num = num,
356 total = len(res['list']),
357 res = res,
358 definition = definition if len(definition) < 200 else definition[:200] + '...',
359 entry = entry))
360 else:
361 self.msg(channel, 'An exception occurred and has been reported to the developers. If this error persists please do not use the faulty command until it has been fixed.')
362 self.elog.warning('unrecognized result type: %s' % res['result_type'])
363
364 def command_imdb(self, manager, opts, arg, channel, sender, userinfo):
365 try:
366 reply = self.imdb.get(arg)
367 except FeedError, e:
368 self.errormsg(channel, e.msg)
369 return
370 except ValueError:
371 self.errormsg(channel, 'movie not found')
372 return
373
374 if reply['Response'] != 'True':
375 self.msg(channel, '[imdb] Nothing found')
376 return
377
378 self.msg(channel, u"""@sep @b{r[Title]}@b [{r[Year]}] Rated {r[Rated]} @sep @bRating@b {r[imdbRating]}/10, {r[imdbVotes]} votes @sep \
379 @bGenre@b {r[Genre]} @sep @bDirector@b {r[Director]} @sep @bActors@b {r[Actors]} @sep @bRuntime@b {r[Runtime]} @sep""".format(r=reply))
380 self.msg(channel, u'@sep @bPlot@b {r[Plot]} @sep @uhttp://www.imdb.com/title/{r[imdbID]}/@u @sep'.format(r=reply))
381
382 #
383 # Registers the user's steam ID and links it to his/her nickname.
384 #
385 def command_register_steam(self, manager, opts, arg, channel, sender, userinfo):
386 arg = arg.strip()
387 try:
388 steam_data = self.steam.find_user(arg)
389 self.notice(sender, u'Steam ID registered, current personaname: {name}'.format(name = steam_data['personaname']))
390 self.users.set(sender, 'steamid', steam_data['steamid'])
391 except SteamException as exc:
392 self.notice(sender, 'No user found')
393
394 ##
395 # Shows user's online status and what game he/sh is playing.
396 # Game does not show when user's profile is set to private.
397 ##
398 def command_steam(self, manager, opts, arg, channel, sender, userinfo):
399 steamuser = self.get_steamid(opts, arg, channel, sender)
400 if not steamuser:
401 return
402 steam_data = self.steam.get_status(steamuser.steamid)
403
404 if steam_data['communityvisibilitystate'] == 1:
405 # Profile is hidden
406 self.notice(sender, 'Profile is hidden. If you want to use this functionality, set your profile to Public.')
407 return
408
409 if 'games' in opts:
410 steam_games = self.steam.get_games(steamuser.steamid)
411 playtime_forever = 0
412 playtime_2weeks = 0
413 playtime_forever_top = 0
414 game_forever = ""
415 playtime_2weeks_top = 0
416 game_2weeks = ""
417 message = u"""@sep @b{player}@b @sep""".format(player = steam_data['personaname'])
418
419 if steam_games['game_count'] == 0:
420 # You know, because it's possible
421 message += u""" Does not own any games. @sep"""
422 self.msg(channel, message)
423 return
424
425 for item in steam_games['games']:
426 ptf = item['playtime_forever']
427 if ptf > playtime_forever_top:
428 game_forever = item['appid']
429 playtime_forever_top = ptf
430 playtime_forever += ptf
431
432 try:
433 ptw = item['playtime_2weeks']
434 if ptw > playtime_2weeks_top:
435 game_2weeks = item['appid']
436 playtime_2weeks_top = ptw
437 playtime_2weeks += ptw
438 except Exception:
439 #just skip it
440 continue
441
442 message += u""" @bTotal games:@b {total} @sep @bTotal playtime:@b {ftime} hours @sep @bPlaytime last 2 weeks:@b {wtime} hours @sep""".format(
443 total = steam_games['game_count'],
444 ftime = round(playtime_forever / 60, 0),
445 wtime = round(playtime_2weeks / 60, 0))
446 if game_forever != "":
447 fgame = self.steam.get_game_name(game_forever)
448 message += u""" @bMost played game:@b {fgame}, {ftime} hours @sep""".format(
449 fgame = fgame,
450 ftime = round(playtime_forever_top / 60, 0))
451 if game_2weeks != "":
452 wgame = self.steam.get_game_name(game_2weeks)
453 message += u""" @bMost played last 2 weeks:@b {wgame}, {wtime} hours @sep""".format(
454 wgame = wgame,
455 wtime = round(playtime_2weeks / 60, 0))
456 self.msg(channel, message)
457 else:
458 # Prepare message
459 message = u"""@sep @b{player}@b [{status}] @sep""".format(
460 player = steam_data['personaname'],
461 status = get_personastate_text(self, steam_data['personastate']))
462 if steam_data['personastate'] == 0 or steam_data['personastate'] > 7:
463 # User is offline or unknown state
464 # NOTE: lastlogoff is actual logoff timestamp, not "appear offline" timestamp
465 latestdate = get_timespan(datetime.fromtimestamp(steam_data['lastlogoff']))
466 message += u""" @bLast seen@b {latestdate} ago @sep""".format(
467 latestdate = latestdate)
468 self.msg(channel, message)
469 else:
470 # user is online, busy, away, snooze, looking to trade or looking to play
471 if 'gameextrainfo' in steam_data:
472 message += u""" @bPlaying:@b {gamename} @sep""".format(
473 gamename = steam_data['gameextrainfo'])
474 if 'gameserverip' in steam_data:
475 message += u""" @bPlaying on server:@b {gameserver} @sep""".format(
476 gameserver = steam_data['gameserverip'])
477 else:
478 # User is not playing a game.
479 message += u""" Not playing anything right now @sep"""
480
481 self.msg(channel, message)
482
483 def command_lastfm(self, manager, opts, arg, channel, sender, userinfo):
484 try:
485 user = self.lastfm.get_user(arg)
486 if 'error' in user:
487 self.errormsg(channel, user['message'])
488 return
489 latest = self.lastfm.get_recent_tracks(arg, 1)
490 except FeedError as e:
491 self.errormsg(channel, e.msg)
492 return
493
494 user = user['user']
495 userinfo = []
496 if user['realname']:
497 userinfo.append(user['realname'])
498 if user['age']:
499 userinfo.append(user['age'])
500 if user['country']:
501 userinfo.append(user['country'])
502
503 if userinfo:
504 userinfo = ' [%s]' % ', '.join(userinfo)
505 else:
506 userinfo = ''
507
508 if 'track' in latest['recenttracks']:
509 if isinstance(latest['recenttracks']['track'], list):
510 latest = latest['recenttracks']['track'][0]
511 else:
512 latest = latest['recenttracks']['track']
513 try:
514 latest['@attr']['nowplaying']
515 latest_str = u' @bNow playing@b {latest[artist][#text]} - {latest[name]} @sep'.format(latest=latest)
516 except KeyError:
517 latestdate = get_timespan(datetime.fromtimestamp(int(latest['date']['uts'])))
518 latest_str = u' @bLatest track@b {latest[artist][#text]} - {latest[name]} ({latestdate} ago) @sep'.format(
519 latest=latest, latestdate=latestdate)
520 else:
521 latest_str = ''
522
523 self.msg(channel, u'@sep @b{user[name]}@b{userinfo} @sep @bPlays@b {plays} since {regdate} @sep \
524 @bLink@b {user[url]} @sep{latest_track}'.format(
525 userinfo = userinfo,
526 plays = format_thousand(int(user['playcount'])),
527 regdate = user['registered']['#text'][:10],
528 user = user,
529 latest_track = latest_str))
530
531 def command_url_shorten(self, manager, opts, arg, channel, sender, userinfo):
532 if not arg.startswith('http://') and not arg.startswith('https://'):
533 self.errormsg(channel, 'a valid URL must start with http:// or https://')
534 return
535
536 try:
537 reply = self.urls.shorten(arg)
538 except FeedError, e:
539 self.errormsg(channel, e.msg)
540 return
541
542 if reply['status_code'] != 200:
543 self.errormsg(channel, 'an error occurred.')
544 self.elog.warning('[shorten] error: code %d, %s' % (reply['status_code'], reply['status_txt']))
545 else:
546 self.msg(channel, '@sep @bShort URL@b %s @sep' % reply['data']['url'])
547
548 def command_url_expand(self, manager, opts, arg, channel, sender, userinfo):
549 if not arg.startswith('http://') and not arg.startswith('https://'):
550 self.errormsg(channel, 'a valid URL must start with http:// or https://')
551 return
552
553 try:
554 reply = self.urls.expand(arg)
555 except FeedError, e:
556 self.errormsg(channel, e.msg)
557 return
558
559 if 'error' in reply:
560 self.errormsg(channel, reply['error'])
561 else:
562 self.msg(channel, '@sep @bLong URL@b {reply[long-url]} @sep @bContent-type@b {reply[content-type]} @sep'.format(reply=reply))
563
564 def command_idlerpg(self, manager, opts, arg, channel, sender, userinfo):
565 try:
566 player = IrpgPlayer(arg)
567 except FeedError, e:
568 self.errormsg(channel, e.msg)
569 return
570
571 if not player.name:
572 self.errormsg(channel, 'player not found. @bNote@b: nicks are case sensitive.')
573 return
574
575 self.msg(channel, """@sep @b{player.name}@b [{status}] @sep @bLevel@b {player.level} {player.classe} @sep @bNext level@b \
576 {nextlevel} @sep @bIdled@b {idled_for} @sep @bAlignment@b {player.alignment} @sep""".format(
577 player = player,
578 status = '@c3ON@c' if player.is_online else '@c4OFF@c',
579 nextlevel = timedelta(seconds=player.ttl),
580 idled_for = timedelta(seconds=player.idled_for)))
581
582 def command_ipinfo(self, manager, opts, arg, channel, sender, userinfo):
583 try:
584 reply = self.ipinfo.get_info(arg)
585 except FeedError, e:
586 self.errormsg(channel, e.msg)
587 return
588
589 self.msg(channel, """@sep @bIP/Host@b {arg} ({reply[ip_addr]}) @sep @bLocation@b {reply[city]}, {reply[region]}, \
590 {reply[country_name]} [{reply[country_code]}] @sep{map}""".format(
591 reply = reply,
592 arg = arg.lower(),
593 map = ' http://maps.google.com/maps?q=%s,%s @sep' % (reply['latitude'], reply['longitude']) if reply['latitude'] and reply['longitude'] else ''))
594
595 dice_regex = re.compile('^(?:(\d+)d)?(\d+)(?:([\+\-])(\d+))?$')
596
597 def command_dice(self, manager, opts, arg, channel, sender, userinfo):
598 r = dice_regex.search(arg)
599 if not r:
600 self.errormsg(channel, 'invalid format')
601 return
602
603 num, faces, type, modifier = r.groups()
604 if num:
605 num = int(num)
606 else:
607 num = 1
608 faces = int(faces)
609 if num < 1 or num > 32 or faces < 2 or faces > 65536:
610 self.errormsg(channel, 'parameter out of range')
611 return
612
613 total = 0
614 results = []
615 for n in xrange(int(num)):
616 randnum = random.randint(1, int(faces))
617 total += randnum
618 results.append(randnum)
619
620 if type == '-':
621 modifier = int(modifier)
622 total -= modifier
623 max = num * faces - modifier
624 elif type == '+':
625 modifier = int(modifier)
626 total += modifier
627 max = num * faces + modifier
628 else:
629 max = num * faces
630
631 self.msg(channel, '@sep @bTotal@b {total} / {max} [{percent}%] @sep @bResults@b {results} @sep'.format(
632 total = total,
633 max = max,
634 percent = 100 * total / max if max != 0 else '9001',
635 results = str(results)))
636
637 def command_qdb(self, manager, opts, arg, channel, sender, userinfo):
638 try:
639 if not arg:
640 quote = self.quotes.get_qdb_random()
641 else:
642 try:
643 quote_id = int(arg)
644 quote = self.quotes.get_qdb_id(quote_id)
645 if not quote:
646 self.errormsg(channel, 'quote @b%d@b not found' % quote_id)
647 return
648 except ValueError:
649 self.errormsg(channel, 'invalid quote ID')
650 return
651 except ExpatError: # qdb returns a malformed xml when the quote doesn't exist
652 self.errormsg(channel, 'quote @b%d@b not found' % quote_id)
653 return
654 except FeedError, e:
655 self.errormsg(channel, e.msg)
656 return
657
658 id = quote['id']
659 for line in quote['lines']:
660 self.msg(channel, u'[qdb {id}] {line}'.format(id=id, line=line.replace('\n', '')))
661
662 def command_fml(self, manager, opts, arg, channel, sender, userinfo):
663 try:
664 if not arg:
665 quote = self.quotes.get_fml()
666 else:
667 try:
668 quote_id = int(arg)
669 quote = self.quotes.get_fml(quote_id)
670 if not quote:
671 self.errormsg(channel, 'quote @b%d@b not found' % quote_id)
672 return
673 except (ValueError, IndexError):
674 self.errormsg(channel, 'invalid quote ID')
675 return
676 except (FeedError, FmlException) as e:
677 self.errormsg(channel, e.msg)
678 self.elog.warning('WARNING: .fml error: %s' % e.msg)
679 return
680
681 self.msg(channel, u'[fml #{quote[id]}] {quote[text]}'.format(quote=quote))
682
683 def command_internets_info(self, manager, opts, arg, channel, sender, userinfo):
684 self.notice(sender, '@sep @bRizon Internets Bot@b @sep @bDevelopers@b martin <martin@rizon.net> @sep @bHelp/feedback@b %(channel)s @sep' % {
685 'channel' : '#internets'})
686
687 def command_internets_help(self, manager, opts, arg, channel, sender, userinfo):
688 command = arg.lower()
689
690 if command == '':
691 message = ['internets: .help internets - for internets commands']
692 elif command == 'internets':
693 message = manager.get_help()
694 else:
695 message = manager.get_help(command)
696
697 if message == None:
698 message = ['%s is not a valid command.' % arg]
699
700 for line in message:
701 self.notice(sender, line)
702
703 class UserCommandManager(CommandManager):
704 def get_prefix(self):
705 return '.'
706
707 def get_commands(self):
708 return {
709 'cc': 'calc',
710 'calc': (command_calc, ARG_YES, 'Calculates an expression', [], 'expression'),
711
712 'dict': 'dictionary',
713 'dictionary': (command_dictionary, ARG_YES, 'Search for a dictionary definition', [
714 ('number', '-n', 'display the n-th result', {'type': '+integer'}, ARG_YES),
715 ('all', '-a', 'display all results (using /notice)', {'action': 'store_true'}, ARG_YES)], 'word'),
716
717 'u': 'urbandictionary',
718 'urbandictionary': (command_urbandictionary, ARG_YES, 'Search for a definition on Urban Dictionary', [], 'word'),
719
720 'g': 'google',
721 'google': (command_google_search, ARG_YES, 'Search for something on Google', [], 'google_search'),
722
723 'gi': 'google_image',
724 'google_image': (command_google_image_search, ARG_YES, 'Search for images via Google Image', [], 'google_image_search'),
725
726 't': 'translate',
727 'translate': (command_bing_translate, ARG_YES, 'Translate something from a language to another', [], 'from to text'),
728
729 'yt': 'youtube',
730 'youtube': (command_youtube_search, ARG_YES, 'Search for something on YouTube', [], 'youtube_search'),
731
732 'w': 'weather',
733 'weather': (command_weather, ARG_OPT, 'Displays current weather conditions for a location', [
734 ('nick', '-n', 'use the weather location linked to a nick', {'action': 'store_true'}, ARG_YES)]),
735
736 'f': 'forecast',
737 'forecast': (command_forecast, ARG_OPT, 'Displays 5-day forecast for a location', [
738 ('nick', '-n', 'use the weather location linked to a nick', {'action': 'store_true'}, ARG_YES)]),
739
740 'regloc': 'register_location',
741 'register_location': (command_register_location, ARG_YES, 'Links a location to your nick that will be used as default location in .w and .f', [], 'location'),
742
743 'imdb': (command_imdb, ARG_YES, 'Search for information on a movie on IMDB', [], 'movie_title'),
744
745 'lastfm': (command_lastfm, ARG_YES, 'Returns information on a Last.fm user', [], 'lastfm_user'),
746
747 'shorten': (command_url_shorten, ARG_YES, 'Shortens a URL using http://j.mp', [], 'long_url'),
748
749 'expand': (command_url_expand, ARG_YES, 'Expands a shortened URL using http://longurl.org', [], 'shortened_url'),
750
751 'irpg': 'idlerpg',
752 'idlerpg': (command_idlerpg, ARG_YES, 'Returns info on a player in Rizon IdleRPG (http://idlerpg.rizon.net/)', [], 'player_name'),
753
754 'ipinfo': (command_ipinfo, ARG_YES, 'Returns short info on a IP address/hostname', [], 'ip/host'),
755
756 'd': 'dice',
757 'dice': (command_dice, ARG_YES, 'Rolls X N-sided dice with an optional modifier A (XdN+A format)', [], 'dice_notation'),
758
759 'qdb': (command_qdb, ARG_OPT, 'Displays a quote from qdb.us', []),
760
761 'fml': (command_fml, ARG_OPT, 'Displays a quote from http://www.fmylife.com', []),
762
763 'steam': (command_steam, ARG_OPT, 'Shows your steam information', [
764 ('nick', '-n', 'use the steamid linked to a nick.', {'action': 'store_true'}, ARG_YES),
765 ('games', '-g', 'shows the total games owned by nick and shows most played game.', {'action': 'store_true'}, ARG_NO)]),
766
767 'regsteam': 'register_steam',
768 'register_steam': (command_register_steam, ARG_YES, 'Registers your Steam user ID', [], 'steamid'),
769
770 'info': (command_internets_info, ARG_NO|ARG_OFFLINE, 'Displays version and author information', []),
771 'help': (command_internets_help, ARG_OPT|ARG_OFFLINE, 'Displays available commands and their usage', []),
772 }