]> jfr.im git - irc/rizon/acid.git/blame - pyva/pyva/src/main/python/internets/internets.py
Send large multi line output per notice. Fixes #62
[irc/rizon/acid.git] / pyva / pyva / src / main / python / internets / internets.py
CommitLineData
685e346e
A
1#!/usr/bin/python pseudoserver.py
2# psm_internets.py
3# module for pypseudoserver
4# written by ElChE <elche@rizon.net>, martin <martin@rizon.net>
5
6import sys
7import traceback
8import types
9import logging
10
11from istring import istring
12from datetime import datetime
13from decimal import Decimal, InvalidOperation
14
15from utils import *
16from pyva import *
17from core import *
18from plugin import *
19import mythreading as threading
02901647 20from pseudoclient import sys_antiflood, sys_log, sys_options, cmd_manager, inviteable, argparser
685e346e 21
02901647 22import cmd_admin, cmd_private, cmd_user, internets_channels, internets_users, sys_auth
f0ac597d 23from api import bing, calc, google, imdb, ipinfo, lastfm, quotes, urbandictionary, urls, weather, wolfram, words, steam, twitch
685e346e 24from internets_utils import *
9c9f0247 25from api.steam import SteamUser
685e346e 26
2d09c59a 27import pyva_net_rizon_acid_core_Acidictive as Acidictive
f6353b7a 28import pyva_net_rizon_acid_core_AcidCore as AcidCore
2d09c59a 29import pyva_net_rizon_acid_core_User as User
f6353b7a 30
685e346e
A
31class internets(
32 AcidPlugin,
33 inviteable.InviteablePseudoclient
34):
35 initialized = False
36
37 def bind_function(self, function):
38 func = types.MethodType(function, self, internets)
39 setattr(internets, function.__name__, func)
40 return func
41
42 def bind_admin_commands(self):
43 list = cmd_admin.get_commands()
44 self.commands_admin = []
45
46 for command in list:
47 self.commands_admin.append(
48 (command,
49 {
50 'permission': 'e',
51 'callback': self.bind_function(list[command][0]),
52 'usage': list[command][1]
53 }
54 )
55 )
56
57 def __init__(self):
58 AcidPlugin.__init__(self)
59
60 self.name = "internets"
61 self.log = logging.getLogger(__name__)
62
63 try:
41ae6aae 64 self.nick = istring(self.config.get('internets').get('nick'))
685e346e
A
65 except Exception, err:
66 self.log.exception("Error reading 'internets:nick' configuration option: %s" % err)
67 raise
68
69 try:
41ae6aae 70 self.chan = istring(self.config.get('internets').get('channel'))
685e346e
A
71 except Exception, err:
72 self.log.exception("Error reading 'internets:channel' configuration option: %s" % err)
73 raise
74
d0baaf8a
D
75 try:
76 self.output_limit = int(self.config.get('internets').get('outputlimit'))
77 except Exception, err:
78 self.log.exception("Error reading 'internets:outputlimit' configuration option: %s" % err)
79 raise
80
efd2c73c
O
81 try:
82 self.max_line_length = int(self.config.get('internets').get('maxlinelength'))
83 except Exception, err:
84 self.log.exception("Error reading 'internets:maxlinelength' configuration option: %s" % err)
85 raise
86
685e346e
A
87 self.bind_admin_commands()
88
89 def start_threads(self):
90 self.options.start()
91 self.channels.start()
92 self.users.start()
93 self.auth.start()
94 self.antiflood.start()
95
96 def start(self):
97 try:
98 AcidPlugin.start(self)
99 inviteable.InviteablePseudoclient.start(self)
100
101 self.options = sys_options.OptionManager(self)
102 self.elog = sys_log.LogManager(self)
acd76da0 103 self.commands_private = cmd_private.PrivateCommandManager()
685e346e
A
104 self.commands_user = cmd_user.UserCommandManager()
105 except Exception, err:
106 self.log.exception('Error initializing core subsystems for internets module (%s)' % err)
107 raise
108
109 self.elog.debug('Started core subsystems.')
110
111 try:
acd76da0 112 self.channels = internets_channels.InternetsChannelManager(self)
685e346e 113 self.users = internets_users.InternetsUserManager(self)
acd76da0 114 self.auth = sys_auth.InternetsAuthManager(self)
685e346e
A
115 self.antiflood = sys_antiflood.AntiFloodManager(self)
116 except Exception, err:
117 self.log.exception('Error initializing subsystems for internets module (%s)' % err)
118 raise
119
120 self.elog.debug('Started subsystems.')
121
122 try:
123 try:
41ae6aae 124 self.bing = bing.Bing(self.config.get('internets').get('bing_appid'))
685e346e
A
125 except Exception, err:
126 self.log.exception('Error initializing internets bing API (%s)' % err)
127 self.nsp = calc.NumericStringParser()
adadb95c 128 self.google = google.Google(self.config.get('internets').get('key_google'), self.config.get('internets').get('yt_parse_delay'))
42d82f07 129 self.imdb = imdb.Imdb(self.config.get('internets').get('key_imdb'))
41ae6aae 130 self.ipinfo = ipinfo.IpInfo(self.config.get('internets').get('key_ipinfodb'))
131 self.lastfm = lastfm.LastFm(self.config.get('internets').get('key_lastfm'))
132 self.quotes = quotes.Quotes(self.config.get('internets').get('key_fml'))
685e346e 133 self.urbandictionary = urbandictionary.UrbanDictionary()
41ae6aae 134 self.urls = urls.Urls(self.config.get('internets').get('user_bitly'), self.config.get('internets').get('key_bitly'))
135 self.weather = weather.Weather(self.config.get('internets').get('key_openweathermap'))
136 self.wolfram = wolfram.Wolfram(self.config.get('internets').get('key_wolframalpha'))
137 self.wordnik = words.Words(self.config.get('internets').get('key_wordnik'))
138 self.steam = steam.Steam(self.config.get('internets').get('key_steam'))
f0ac597d 139 self.twitch = twitch.Twitch()
685e346e
A
140 except Exception, err:
141 self.log.exception('Error initializing internets module (%s)' % err)
142 raise
143
e7697283
A
144 for channel in self.channels.list_valid():
145 self.join(channel.name)
685e346e 146
e7697283 147 self.log.debug('Joined channels.')
685e346e
A
148
149 try:
150 self.start_threads()
151 except Exception, err:
152 self.log.exception('Error starting threads for internets module (%s)' % err)
153 raise
154
155 self.initialized = True
156 self.online = True
157 self.elog.debug('Started threads.')
158 return True
159
685e346e
A
160 def stop(self):
161 if hasattr(self, 'antiflood'):
162 self.antiflood.stop()
163
164 if hasattr(self, 'auth'):
165 self.auth.stop()
166
167 if hasattr(self, 'users'):
168 if self.initialized:
169 self.users.force()
170
171 self.users.stop()
172 self.users.db_close()
173
174 if hasattr(self, 'channels'):
175 if self.initialized:
176 self.channels.force()
177
178 self.channels.stop()
179 self.channels.db_close()
180
181 if hasattr(self, 'options'):
182 if self.initialized:
183 self.options.force()
184
185 self.options.stop()
186 self.options.db_close()
187
685e346e
A
188 def errormsg(self, target, message):
189 self.msg(target, '@b@c4Error:@o %s' % message)
190
191 def usagemsg(self, target, description, examples):
192 message = '@errsep @bUsage@b %s @errsep' % description
193
194 if examples != None:
195 message += ' @bExamples@b %s @errsep' % ', '.join(examples)
196
197 self.msg(target, message)
198
199 def msg(self, target, message):
200 if message != '':
72453076
D
201 if len(message) > 450: # from Protocol.privmsg -> long, do not spam chan
202 Acidictive.notice(self.nick, target, format_ascii_irc(message))
203 else:
204 Acidictive.privmsg(self.nick, target, format_ascii_irc(message))
685e346e
A
205
206 def multimsg(self, target, count, intro, separator, pieces, outro = ''):
207 cur = 0
208
209 while cur < len(pieces):
210 self.msg(target, intro + separator.join(pieces[cur:cur + count]) + outro)
211 cur += count
212
213 def notice(self, target, message):
214 if message != '':
2d09c59a 215 Acidictive.notice(self.nick, target, format_ascii_irc(message))
685e346e
A
216
217 def execute(self, manager, command, argument, channel, sender, userinfo):
218 full_command = '%s%s' % (command, ' %s' % argument if len(argument) else '')
219 cmd = manager.get_command(command)
220
221 if cmd == None:
222 self.msg(channel, manager.invalid)
223 self.elog.debug('Parsed command @b%s@b: invalid command.' % full_command)
224 return
225
226 if self.users.is_banned(sender) or self.antiflood.check_user(sender, command, argument):
227 user = self.users[sender]
228 message = 'You were banned by @b%s@b.' % user.ban_source
229
230 if user.ban_reason != None:
231 message += ' Reason: @b%s@b.' % user.ban_reason
232
233 if user.ban_expiry != None:
234 message += ' Expires: @b%s@b.' % datetime.fromtimestamp(user.ban_expiry)
235
236 self.notice(sender, message)
237 self.elog.debug('Parsed command @b%s@b: user is banned.' % full_command)
238 return
239
240 self.elog.command('%s%s > %s' % (sender, ':%s' % channel if channel != sender else '', full_command))
241
bdf4ecea 242 parser = argparser.ArgumentParser(add_help_option = False, option_class = argparser.ArgumentParserOption)
685e346e
A
243 cmd_type = cmd[1]
244 cmd_args = cmd[3]
245
246 parser.add_option('-?', '--help', action = 'store_true')
247
248 for cmd_arg in cmd_args:
249 parser.add_option(cmd_arg[1], '--' + cmd_arg[0], **cmd_arg[3])
250
251 try:
c2ee98e6 252 argument = argument.lstrip()
253 if len(argument) > 1:
254 if argument[0] == '-' and argument[1] != '-' and not cmd_args:
255 argument = '-- ' + argument
685e346e 256 (popts, pargs) = parser.parse_args(args = argument.split(' '))
bdf4ecea 257 except argparser.ArgumentParserError, err:
685e346e
A
258 self.msg(channel, str(err)) #TODO: Avoid str, use unicode.
259 parser.destroy()
260 self.elog.debug('Parsed command @b%s@b: invalid options.' % full_command)
261 return
262
263 if popts.help == True:
264 manager.commands['help'][0](self, manager, {}, command, channel, sender)
265 parser.destroy()
266 self.elog.debug('Parsed command @b%s@b: help intercepted.' % full_command)
267 return
268
269 opt_dict = {}
270 larg = ' '.join(pargs).strip()
271 is_offline = True
272
273 for cmd_arg in cmd_args:
274 parg = getattr(popts, cmd_arg[0])
275
276 if parg != None:
277 if len(cmd_arg) <= 4 or not (cmd_arg[4] & cmd_manager.ARG_OFFLINE):
278 is_offline = False
279
280 if len(cmd_arg) > 4 and (cmd_arg[4] & cmd_manager.ARG_YES) and larg == '':
281 self.msg(channel, 'Error: %s option requires an argument.' % cmd_arg[1])
282 parser.destroy()
283 self.elog.debug('Parsed command @b%s@b: option constraint was broken.' % full_command)
284 return
285
286 opt_dict[cmd_arg[0]] = parg
287 elif len(cmd_arg) > 4 and (cmd_arg[4] & cmd_manager.ARG_OFFLINE and cmd_arg[4] & cmd_manager.ARG_OFFLINE_REQ):
288 is_offline = False
289
290 if not self.online and ((len(pargs) > 0 and not (cmd_type & cmd_manager.ARG_OFFLINE)) or not is_offline):
291 self.notice(sender, 'The eRepublik API is offline. Please retry later.')
292 parser.destroy()
293 self.elog.debug('Parsed command @b%s@b: offline.' % full_command)
294 return
295
296 if (cmd_type & cmd_manager.ARG_YES) and (larg == None or larg == ''):
297 self.notice(sender, '@bUsage@b: %s @b%s@b' % (command, cmd[4] if len(cmd) > 4 else 'argument'))
298 else:
299 try:
300 cmd[0](self, manager, opt_dict, larg, channel, sender, userinfo)
301 except Exception, e:
302 tb = traceback.extract_tb(sys.exc_info()[2])
303 longest = 0
304
305 for entry in tb:
306 length = len(entry[2])
307
308 if length > longest:
309 longest = length
310
311 self.elog.exception('%s%s > @b%s@b: %s' % (sender, ':%s' % channel if channel != sender else '', full_command, e))
312 self.log.exception("internets error!")
313
314 for entry in tb:
315 self.elog.traceback('@b%-*s@b : %d %s' % (longest, entry[2], entry[1], entry[3]))
316
317 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.')
318
319 parser.destroy()
320 self.elog.debug('Parsed command @b%s@b: execution terminated.' % full_command)
321
adadb95c
M
322 def onPrivmsg(self, source, target, message):
323 if not super(internets, self).onPrivmsg(source, target, message):
324 # It's not a command
325 return
326
6f5b3072
O
327 if not target in self.channels:
328 return
329
0b1c92a7 330 # both 'youtube.com' and 'youtu.be' get caught
eadfde5a 331 if not 'youtu' in message or not self.channels[target].youtube_info:
0b1c92a7
A
332 return
333
334 sourceNick = User.findUser(source)['nick']
335 if not self.users.is_banned(sourceNick) and not self.antiflood.check_user(sourceNick, 'ytinfo', '') and target[0] == '#':
336 threading.deferToThread(cmd_user.onPrivmsg_regex_youtube, self, source, target, message)
adadb95c 337
685e346e
A
338 def onChanModes(self, prefix, channel, modes):
339 if not self.initialized:
340 return
341
342 if not modes == '-z':
343 return
344
345 if channel in self.channels:
346 self.channels.remove(channel)
347 self.elog.request('Channel @b%s@b was dropped. Deleting it.' % channel)
348
349 def getCommands(self):
350 return self.commands_admin
351
352 def get_location(self, opts, arg, channel, sender):
353 nick = None
354 location = None
355
356 if 'nick' in opts:
357 nick = arg
358 elif arg == '':
359 nick = sender
360 else:
361 location = arg
362
363 if nick:
364 location = self.users.get(nick, 'location')
365
366 if not location:
367 if 'nick' in opts:
368 self.msg(channel, 'No location found linked to nick %s.' % arg)
369 else:
9c9f0247 370 self.msg(channel, 'No location found linked to your nick. To link one, type: @b%sregister_location <location>@b' % self.commands_user.get_prefix())
685e346e
A
371
372 return location
9c9f0247
M
373
374 def get_steamid(self, opts, arg, channel, sender):
375 """Gets the steamid from the database."""
376 nick = None
377 steamid = None
378
379 if 'nick' in opts:
380 nick = arg
381 else:
382 nick = sender
383
384 steamid = self.users.get(nick, 'steamid')
385
386 if not steamid:
387 if 'nick' in opts:
388 self.msg(channel, 'No steamid found linked to nick %s.' % arg)
389 return
390 else:
391 self.msg(channel, 'No steamid found linked to your nick. To link one, type: @b%sregister_steam <steamid>@b' % self.commands_user.get_prefix())
392 return
393 else:
394 return SteamUser(nick, steamid)