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