]>
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 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) |