]>
Commit | Line | Data |
---|---|---|
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 | ||
6 | import sys | |
7 | import traceback | |
8 | import types | |
9 | import logging | |
10 | ||
11 | from istring import istring | |
12 | from datetime import datetime | |
13 | from decimal import Decimal, InvalidOperation | |
14 | ||
15 | from utils import * | |
16 | from pyva import * | |
17 | from core import * | |
18 | from plugin import * | |
19 | import mythreading as threading | |
02901647 | 20 | from pseudoclient import sys_antiflood, sys_log, sys_options, cmd_manager, inviteable, argparser |
685e346e | 21 | |
02901647 | 22 | import cmd_admin, cmd_private, cmd_user, internets_channels, internets_users, sys_auth |
f0ac597d | 23 | from api import bing, calc, google, imdb, ipinfo, lastfm, quotes, urbandictionary, urls, weather, wolfram, words, steam, twitch |
685e346e | 24 | from internets_utils import * |
9c9f0247 | 25 | from api.steam import SteamUser |
685e346e | 26 | |
2d09c59a | 27 | import pyva_net_rizon_acid_core_Acidictive as Acidictive |
f6353b7a | 28 | import pyva_net_rizon_acid_core_AcidCore as AcidCore |
2d09c59a | 29 | import pyva_net_rizon_acid_core_User as User |
f6353b7a | 30 | |
685e346e A |
31 | class 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 | 201 | if len(message) > 450: # from Protocol.privmsg -> long, do not spam chan |
7e002c01 D |
202 | message = message[:445] + ' ...' |
203 | Acidictive.privmsg(self.nick, target, format_ascii_irc(message)) | |
685e346e A |
204 | |
205 | def multimsg(self, target, count, intro, separator, pieces, outro = ''): | |
206 | cur = 0 | |
207 | ||
208 | while cur < len(pieces): | |
209 | self.msg(target, intro + separator.join(pieces[cur:cur + count]) + outro) | |
210 | cur += count | |
211 | ||
212 | def notice(self, target, message): | |
213 | if message != '': | |
2d09c59a | 214 | Acidictive.notice(self.nick, target, format_ascii_irc(message)) |
685e346e A |
215 | |
216 | def execute(self, manager, command, argument, channel, sender, userinfo): | |
217 | full_command = '%s%s' % (command, ' %s' % argument if len(argument) else '') | |
218 | cmd = manager.get_command(command) | |
219 | ||
220 | if cmd == None: | |
221 | self.msg(channel, manager.invalid) | |
222 | self.elog.debug('Parsed command @b%s@b: invalid command.' % full_command) | |
223 | return | |
224 | ||
225 | if self.users.is_banned(sender) or self.antiflood.check_user(sender, command, argument): | |
226 | user = self.users[sender] | |
227 | message = 'You were banned by @b%s@b.' % user.ban_source | |
228 | ||
229 | if user.ban_reason != None: | |
230 | message += ' Reason: @b%s@b.' % user.ban_reason | |
231 | ||
232 | if user.ban_expiry != None: | |
233 | message += ' Expires: @b%s@b.' % datetime.fromtimestamp(user.ban_expiry) | |
234 | ||
235 | self.notice(sender, message) | |
236 | self.elog.debug('Parsed command @b%s@b: user is banned.' % full_command) | |
237 | return | |
238 | ||
239 | self.elog.command('%s%s > %s' % (sender, ':%s' % channel if channel != sender else '', full_command)) | |
240 | ||
bdf4ecea | 241 | parser = argparser.ArgumentParser(add_help_option = False, option_class = argparser.ArgumentParserOption) |
685e346e A |
242 | cmd_type = cmd[1] |
243 | cmd_args = cmd[3] | |
244 | ||
245 | parser.add_option('-?', '--help', action = 'store_true') | |
246 | ||
247 | for cmd_arg in cmd_args: | |
248 | parser.add_option(cmd_arg[1], '--' + cmd_arg[0], **cmd_arg[3]) | |
249 | ||
250 | try: | |
c2ee98e6 | 251 | argument = argument.lstrip() |
252 | if len(argument) > 1: | |
253 | if argument[0] == '-' and argument[1] != '-' and not cmd_args: | |
254 | argument = '-- ' + argument | |
685e346e | 255 | (popts, pargs) = parser.parse_args(args = argument.split(' ')) |
bdf4ecea | 256 | except argparser.ArgumentParserError, err: |
685e346e A |
257 | self.msg(channel, str(err)) #TODO: Avoid str, use unicode. |
258 | parser.destroy() | |
259 | self.elog.debug('Parsed command @b%s@b: invalid options.' % full_command) | |
260 | return | |
261 | ||
262 | if popts.help == True: | |
263 | manager.commands['help'][0](self, manager, {}, command, channel, sender) | |
264 | parser.destroy() | |
265 | self.elog.debug('Parsed command @b%s@b: help intercepted.' % full_command) | |
266 | return | |
267 | ||
268 | opt_dict = {} | |
269 | larg = ' '.join(pargs).strip() | |
270 | is_offline = True | |
271 | ||
272 | for cmd_arg in cmd_args: | |
273 | parg = getattr(popts, cmd_arg[0]) | |
274 | ||
275 | if parg != None: | |
276 | if len(cmd_arg) <= 4 or not (cmd_arg[4] & cmd_manager.ARG_OFFLINE): | |
277 | is_offline = False | |
278 | ||
279 | if len(cmd_arg) > 4 and (cmd_arg[4] & cmd_manager.ARG_YES) and larg == '': | |
280 | self.msg(channel, 'Error: %s option requires an argument.' % cmd_arg[1]) | |
281 | parser.destroy() | |
282 | self.elog.debug('Parsed command @b%s@b: option constraint was broken.' % full_command) | |
283 | return | |
284 | ||
285 | opt_dict[cmd_arg[0]] = parg | |
286 | elif len(cmd_arg) > 4 and (cmd_arg[4] & cmd_manager.ARG_OFFLINE and cmd_arg[4] & cmd_manager.ARG_OFFLINE_REQ): | |
287 | is_offline = False | |
288 | ||
289 | if not self.online and ((len(pargs) > 0 and not (cmd_type & cmd_manager.ARG_OFFLINE)) or not is_offline): | |
290 | self.notice(sender, 'The eRepublik API is offline. Please retry later.') | |
291 | parser.destroy() | |
292 | self.elog.debug('Parsed command @b%s@b: offline.' % full_command) | |
293 | return | |
294 | ||
295 | if (cmd_type & cmd_manager.ARG_YES) and (larg == None or larg == ''): | |
296 | self.notice(sender, '@bUsage@b: %s @b%s@b' % (command, cmd[4] if len(cmd) > 4 else 'argument')) | |
297 | else: | |
298 | try: | |
299 | cmd[0](self, manager, opt_dict, larg, channel, sender, userinfo) | |
300 | except Exception, e: | |
301 | tb = traceback.extract_tb(sys.exc_info()[2]) | |
302 | longest = 0 | |
303 | ||
304 | for entry in tb: | |
305 | length = len(entry[2]) | |
306 | ||
307 | if length > longest: | |
308 | longest = length | |
309 | ||
310 | self.elog.exception('%s%s > @b%s@b: %s' % (sender, ':%s' % channel if channel != sender else '', full_command, e)) | |
311 | self.log.exception("internets error!") | |
312 | ||
313 | for entry in tb: | |
314 | self.elog.traceback('@b%-*s@b : %d %s' % (longest, entry[2], entry[1], entry[3])) | |
315 | ||
316 | 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.') | |
317 | ||
318 | parser.destroy() | |
319 | self.elog.debug('Parsed command @b%s@b: execution terminated.' % full_command) | |
320 | ||
adadb95c M |
321 | def onPrivmsg(self, source, target, message): |
322 | if not super(internets, self).onPrivmsg(source, target, message): | |
323 | # It's not a command | |
324 | return | |
325 | ||
6f5b3072 O |
326 | if not target in self.channels: |
327 | return | |
328 | ||
0b1c92a7 | 329 | # both 'youtube.com' and 'youtu.be' get caught |
eadfde5a | 330 | if not 'youtu' in message or not self.channels[target].youtube_info: |
0b1c92a7 A |
331 | return |
332 | ||
333 | sourceNick = User.findUser(source)['nick'] | |
334 | if not self.users.is_banned(sourceNick) and not self.antiflood.check_user(sourceNick, 'ytinfo', '') and target[0] == '#': | |
335 | threading.deferToThread(cmd_user.onPrivmsg_regex_youtube, self, source, target, message) | |
adadb95c | 336 | |
685e346e A |
337 | def onChanModes(self, prefix, channel, modes): |
338 | if not self.initialized: | |
339 | return | |
340 | ||
341 | if not modes == '-z': | |
342 | return | |
343 | ||
344 | if channel in self.channels: | |
345 | self.channels.remove(channel) | |
346 | self.elog.request('Channel @b%s@b was dropped. Deleting it.' % channel) | |
347 | ||
348 | def getCommands(self): | |
349 | return self.commands_admin | |
350 | ||
351 | def get_location(self, opts, arg, channel, sender): | |
352 | nick = None | |
353 | location = None | |
354 | ||
355 | if 'nick' in opts: | |
356 | nick = arg | |
357 | elif arg == '': | |
358 | nick = sender | |
359 | else: | |
360 | location = arg | |
361 | ||
362 | if nick: | |
363 | location = self.users.get(nick, 'location') | |
364 | ||
365 | if not location: | |
366 | if 'nick' in opts: | |
367 | self.msg(channel, 'No location found linked to nick %s.' % arg) | |
368 | else: | |
9c9f0247 | 369 | 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 |
370 | |
371 | return location | |
9c9f0247 M |
372 | |
373 | def get_steamid(self, opts, arg, channel, sender): | |
374 | """Gets the steamid from the database.""" | |
375 | nick = None | |
376 | steamid = None | |
377 | ||
378 | if 'nick' in opts: | |
379 | nick = arg | |
380 | else: | |
381 | nick = sender | |
382 | ||
383 | steamid = self.users.get(nick, 'steamid') | |
384 | ||
385 | if not steamid: | |
386 | if 'nick' in opts: | |
387 | self.msg(channel, 'No steamid found linked to nick %s.' % arg) | |
388 | return | |
389 | else: | |
390 | self.msg(channel, 'No steamid found linked to your nick. To link one, type: @b%sregister_steam <steamid>@b' % self.commands_user.get_prefix()) | |
391 | return | |
392 | else: | |
393 | return SteamUser(nick, steamid) |