def search(code, input): """Queries DuckDuckGo for the specified input.""" try: data = web.get(uri, params={'q': input.group(2)}) tmp = data.text.replace('\r', '').replace('\n', '').strip() target = r'(?im)<div class="results_links .*?(?!.*web\-result\-sponsored)">.*?<a .*? href="(.*?)">.*?</a>.*?' \ '<div class="snippet">(.*?)</div>.*?<div class="url">(.*?)</div>' found = list(re.findall(target, tmp)) if len(found) > url_count: found = found[:url_count] results = [] if len(found) < 2: return code.say('{b}No results found{b}') count = 0 for item in found: i = list(item) result = {} result['url'] = web.escape(web.striptags(i[0])) result['short'] = web.escape(web.striptags(i[2]).capitalize().split('/')[0]) result['title'] = web.escape(web.striptags(i[1])) if len(result['title']) > title_length: result['title'] = result['title'][:title_length] + '{b}...{b}' results.append('{b}%s{b} - {%s}%s{c} - %s' % (result['short'], url_colors[count], result['title'], result['url'])) count += 1 return code.say(' | '.join(results)) except Exception as e: output.error('Error in search.py: %s' % str(e)) return code.say('{b}Unable to search for %s{b}' % input.group(2))
def watch(self): try: os.wait() except KeyboardInterrupt: output.error('Terminating the bot...') self.kill() sys.exit()
def trigger_NOTICE(code, origin, line, args, text): """ ID: NOTICE Decription: The NOTICE message is used similarly to PRIVMSG. The difference between NOTICE and PRIVMSG is that automatic replies must never be sent in response to a NOTICE message. This rule applies to servers too - they must not send any error reply back to the client on receipt of a notice. Format: <nickname> <text> """ if 'Invalid password for ' in text: if not code.debug: output.error('Invalid NickServ password') os._exit(1) if 'AUTHENTICATION SUCCESSFUL as ' in args[2]: if code.config('undernet_hostmask'): code.write(('MODE', code.nick, '+x')) if not code.debug: output.normal('({}) {}'.format(origin.nick, text), 'NOTICE') # Add notices to the bot logs tmp = { 'message': text, 'nick': origin.nick, 'time': int(time.time()), 'channel': 'NOTICE' } code.logs['bot'].append(tmp)
def initiate_connect(self, host, port): count = 0 max_attempts = 5 if hasattr(self.config, 'delay'): delay = int(self.config.delay) else: delay = 20 while True: if count >= max_attempts: break try: count += 1 if count > 1: output.error( 'Failed to connect! Trying again in ' '%s seconds.' % str(delay) ) time.sleep(delay) if self.verbose: output.normal('Connecting to %s:%s... (try %s)' % (host, port, str(count)), 'STATUS') self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) count = 0 asyncore.loop() except: pass output.error('Too many failed attempts. Exiting.') os._exit(1)
def initiate_connect(self, host, port): count = 0 max_attempts = 5 if self.config('connect_delay'): delay = int(self.config('connect_delay')) else: delay = 20 while True: if count >= max_attempts: break try: count += 1 if count > 1: output.error('Failed to connect! Trying again in ' '%s seconds.' % str(delay)) time.sleep(delay) if self.verbose: output.normal( 'Connecting to %s:%s... (try %s)' % (host, port, str(count)), 'STATUS') self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) count = 0 asyncore.loop() except: pass output.error('Too many failed attempts. Exiting.') os._exit(1)
def run(self): global data while True: if self.code.get("webserver.object") != self.id: return False time.sleep(2) if len(data) < 1: continue # Parse data before we do anything with it try: for query in data: # Query is everything that's sent, as a dict() if "pass" not in query: continue if query["pass"] != self.password: continue # Authenticated.. Now we need to find some variables in the query # 1. args (used for IRC commands) # 2. data (The arguement for the IRC command (the arguements arguement!)) if "args" not in query or "data" not in query: continue self.code.write(query["args"].split(), self.code.format(query["data"])) except: output.error("Failed to parse data! (%s)" % data) continue data = []
def daemon(): try: port = bot.config('webserver_port', 8888) output.info('Starting server [%s] [%s]' % (host, str(port)), 'WEBSERVER') run(host=host, port=int(port), quiet=True) except Exception as e: if bot.debug: output.error(str(e), "WEBSERVER")
def sendping(code): while True: try: code.write(('PING', 'Code')) except Exception as e: if code.debug: output.error('Error while pinging server. (%s)' % str(e)) time.sleep(int(float(code.irc_timeout) / 3))
def setup(self): self.variables = {} filenames = [] if not hasattr(self.config, 'enable'): for fn in os.listdir(os.path.join(home, 'modules')): if fn.endswith('.py') and not fn.startswith('_'): filenames.append(os.path.join(home, 'modules', fn)) else: for fn in self.config.enable: filenames.append(os.path.join(home, 'modules', fn + '.py')) if hasattr(self.config, 'extra'): for fn in self.config.extra: if os.path.isfile(fn): filenames.append(fn) elif os.path.isdir(fn): for n in os.listdir(fn): if n.endswith('.py') and not n.startswith('_'): filenames.append(os.path.join(fn, n)) # Add system modules that the user should always require. Still can # be removed by deleting them or moving them out of the system # modules directory for fn in os.listdir(os.path.join(home, 'core/modules')): if fn.endswith('.py') and not fn.startswith('_'): filenames.append(os.path.join(home, 'core/modules', fn)) # Should fix excluded_modules = getattr(self.config, 'exclude', []) filenames = sorted(list(set(filenames))) # Reset some variables that way we don't get dups self.modules = [] self.cmds = {} # Load modules for filename in filenames: name = os.path.basename(filename)[:-3] if name in excluded_modules: continue #if name in sys.modules: # del sys.modules[name] try: module = imp.load_source(name, filename) except Exception as e: output.error("Failed to load %s: %s" % (name, e)) else: if hasattr(module, 'setup'): module.setup(self) self.register(vars(module)) self.modules.append(name) if self.modules: output.info('Registered modules: ' + ', '.join(self.modules)) else: output.warning('Couldn\'t find any modules') self.bind_commands()
def setup(self): self.variables = {} filenames = [] for fn in os.listdir(os.path.join(home, 'modules')): if fn.endswith('.py') and not fn.startswith('_'): filenames.append(os.path.join(home, 'modules', fn)) if self.config('extra'): for fn in self.config('extra'): if os.path.isfile(fn): filenames.append(fn) elif os.path.isdir(fn): for n in os.listdir(fn): if n.endswith('.py') and not n.startswith('_'): filenames.append(os.path.join(fn, n)) # Add system modules that the user should always require. Still can # be removed by deleting them or moving them out of the system # modules directory for fn in os.listdir(os.path.join(home, 'core/modules')): if fn.endswith('.py') and not fn.startswith('_'): filenames.append(os.path.join(home, 'core/modules', fn)) # Should fix excluded_modules = self.config('excluded_modules', []) filenames = sorted(list(set(filenames))) # Reset some variables that way we don't get dups self.modules = [] self.cmds = {} # Load modules for filename in filenames: name = os.path.basename(filename)[:-3] if name in excluded_modules: continue # if name in sys.modules: # del sys.modules[name] try: module = imp.load_source(name, filename) except Exception as e: output.error("Failed to load %s: %s" % (name, e)) else: if hasattr(module, 'setup'): module.setup(self) self.register(vars(module)) self.modules.append(name) if self.modules: output.info('Registered modules: ' + ', '.join(self.modules)) else: output.warning('Couldn\'t find any modules') self.bind_commands()
def trigger_437(code, origin, line, args, text): """ ID: ERR_UNAVAILRESOURCE Decription: Return when the target is unable to be reached temporarily, eg. a delay mechanism in play, or a service being offline Format: <nick/channel/service> :<reason> """ if not code.debug: output.error(text) os._exit(1)
def search(code, input): try: url = input.group(2) niggurl = requests.get(apiurl + url) if 'error' in niggurl.text.lower(): raise NiggException return code.say('Shortened URL: http://nig.gr/%s' % niggurl.text) except Exception as e: output.error('Error in nigurl.py: %s' % str(e)) return code.say('{b}Unable to shorten %s (probably banned from nig.gr because of abuse){b}' % input.group(2))
def initiate_connect(self, host, port): output.normal('Connecting to %s:%s... ' % (host, port), 'STATUS') try: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) asyncore.loop() except KeyboardInterrupt: os._exit(0) except: output.error('Failed to keep connection to %s:%s!' % (host, port)) sys.exit()
def setup_module(self, name, filename, is_startup=True): try: module = imp.load_source(name, filename) if hasattr(module, 'setup'): module.setup(self) self.register(vars(module)) self.modules.append(name) self.modules = sorted(list(set(self.modules))) except Exception as e: output.error("Failed to load %s: %s" % (name, e)) if not is_startup: # Only raise exception again if it's user-triggered raise Exception("Failed to load %s: %s" % (name, e))
def trigger_NOTICE(code, origin, line, args, text): if 'Invalid password for ' in text: output.error('Invalid NickServ password') os._exit(1) output.normal('({}) {}'.format(origin.nick, text), 'NOTICE') # Add notices to the bot logs tmp = { 'message': text, 'nick': origin.nick, 'time': int(time.time()), 'channel': 'NOTICE' } code.logs['bot'].append(tmp)
def run_code(config): if hasattr(config, 'delay'): delay = config.delay else: delay = 20 def connect(config): p = bot.Code(config) p.run(config['host'], config['port']) try: Watcher() except Exception, e: output.error('%s (in core/__init__.py)' % e)
def initiate_connect(self, host, port): output.normal('Connecting to %s:%s... ' % (host, port), 'STATUS') try: # self.create_socket(socket.AF_INET, socket.SOCK_STREAM) source_address = ((self.config('bind_ip'), 0) if self.config('bind_ip') else None) self.set_socket(socket.create_connection((host, port), source_address=source_address)) self.connect((host, port)) asyncore.loop() except KeyboardInterrupt: os._exit(0) except Exception as e: output.error('Failed to keep connection to %s:%s! (%s)' % (host, port, str(e))) sys.exit()
def run_code(config): if hasattr(config, 'delay'): delay = config.delay else: delay = 20 def connect(config): p = bot.Code(config) p.run(config.host, config.port) try: Watcher() except Exception, e: output.error('%s (in core/__init__.py)' % e)
def setup(code): # Read the databases here, make global variables. If we can't read the db # then we need to disable the module.. global enabled global db # Try to read the db... db = database.get(code.nick, "twss") if not db: try: db = web.json("http://misc.liamstanley.io/twss.json")["lines"] database.set(code.nick, db, "twss") output.info('Downloaded "That\'s What She Said" library and saved') except: output.error(('Failed to download "That\'s What She Said" library. ' "Disabling twss.py.")) if db: enabled = True
def auto_honeypot(code, input): """Check joining users against the Project Honeypot Database""" if not code.config('honeypot_on_join') or input.nick == code.nick: return global db ip = get_ip(input.host) try: abuser = check(ip) except: return output.error( 'Failed to get IP information. Project Honeypot seems to be down!') if abuser: # First, we need to check if we've already checked for it, and got a # match... if ip in db: return db.append(ip) database.set(code.default, db, 'honeypot') if code.config('kickban_on_honeypot') and code.chan[input.sender][ code.nick]['op']: # Wants to kickban, and we've got op. BANHAMMER TIME! code.write(['MODE', input.sender, '+b', '*!*@' + input.host]) code.write(['KICK', input.sender, input.nick], abuser) code.say(abuser)
def trigger_474(code, origin, line, args, text): """ ID: ERR_BANNEDFROMCHAN Decription: Returned when attempting to join a channel a user is banned from Format: <channel> :<reason> """ return output.error(args[3], args[2])
def error(self, origin): try: trace = traceback.format_exc() output.error(trace) lines = list(reversed(trace.splitlines())) report = [lines[0].strip()] for line in lines: line = line.strip() if line.startswith('File "/'): report.append(line[0].lower() + line[1:]) break else: report.append('{red}Source unknown.{c}') self.msg(origin.sender, report[0] + ' (' + report[1] + ')') except: self.msg(origin.sender, '{red}Got an error.')
def initiate_connect(self, host, port): output.normal('Connecting to %s:%s...' % (host, port), 'STATUS') try: # self.create_socket(socket.AF_INET, socket.SOCK_STREAM) source_address = ((self.config('bind_ip'), 0) if self.config('bind_ip') else None) self.set_socket( socket.create_connection((host, port), source_address=source_address)) self.connect((host, port)) asyncore.loop() except KeyboardInterrupt: os._exit(0) except Exception as e: output.error('Connection to %s:%s failed! (%s)' % (host, port, str(e))) os._exit(1)
def trigger_475(code, origin, line, args, text): """ ID: ERR_BADCHANNELKEY Decription: Returned when attempting to join a key-locked channel either without a key or with the wrong key Format: <channel> :<reason> """ return output.error(args[3], args[2])
def trigger_473(code, origin, line, args, text): """ ID: ERR_INVITEONLYCHAN Decription: Returned when attempting to join a channel which is invite only without an invitation Format: <channel> :<reason> """ return output.error(args[3], args[2])
def setup(code): # Read the databases here, make global variables. If we can't read the db # then we need to disable the module.. global enabled global db # Try to read the db... db = database.get(code.default, 'twss') if not db: try: db = web.json('https://static.liam.sh/code/twss.json')['lines'] database.set(code.default, db, 'twss') output.info('Downloaded "That\'s What She Said" library and saved') except: output.error( ('Failed to download "That\'s What She Said" library. ' 'Disabling twss.py.')) if db: enabled = True
def error(self, origin): try: # import traceback trace = traceback.format_exc() output.error(trace) lines = list(reversed(trace.splitlines())) report = [lines[0].strip()] for line in lines: line = line.strip() if line.startswith('File "/'): report.append(line[0].lower() + line[1:]) break else: report.append('{red}Source unknown.') self.msg(origin.sender, report[0] + ' (' + report[1] + ')') except: self.msg(origin.sender, '{red}Got an error.')
def trigger_471(code, origin, line, args, text): """ ID: ERR_CHANNELISFULL Decription: Returned when attempting to join a channel which is set +l and is already full Format: <channel> :<reason> """ return output.error(args[3], args[2])
def login_auth(): bad = "Failed login from %s" % request.remote_addr good = "Successful login from %s" % request.remote_addr if request.json: post_data = request.json else: post_data = request.forms if not post_data: output.error(bad, "WEBSERVER") return {'success': False} isauthed = verify(post_data['passwd'], raw=True) if isauthed: response.set_cookie("auth", isauthed, max_age=2419200, path="/") output.success(good, "WEBSERVER") return {'success': True} else: output.error(bad, "WEBSERVER") return {'success': False}
def setup(code): id = str(gen(0, 10000000)) code.set("webserver.object", id) # Nasty area, we check for configuration options, some are required and some arent if not hasattr(code.config, "run_webserver") or not hasattr(code.config, "webserver_pass"): return if not code.config.run_webserver: return if not code.config.webserver_pass: output.error("To use webserver.py you must have a password setup in default.py!") return if not hasattr(code.config, "webserver_port"): port = 8888 else: port = code.config.webserver_port Sender = CollectData(code, input, id) Sender.start() # Initiate the thread. thread.start_new_thread(init, (str(host), int(port)))
def setup(code): # Read the databases here, make global variables. If we can't read the db # then we need to disable the module.. global enabled global db # Try to read the db... db = database.get(code.default, 'twss') if not db: try: db = web.json( 'https://gist.githubusercontent.com/lrstanley/5448b6512827f69cc0a1b167fc9cf893/raw/twss.json' )['lines'] database.set(code.default, db, 'twss') output.info('Downloaded "That\'s What She Said" library and saved') except: output.error( ('Failed to download "That\'s What She Said" library. ' 'Disabling twss.py.')) if db: enabled = True
def error(self, origin, supress=False): try: trace = traceback.format_exc() output.error(trace) if supress: return lines = list(reversed(trace.splitlines())) report = [lines[0].strip()] for line in lines: line = line.strip() if line.startswith('File "/'): report.append(line[0].lower() + line[1:]) break else: report.append('{red}Source unknown.{c}') self.msg(origin.sender, '{red}%s{c} ({b}%s{b})' % (report[0], report[1])) except: self.msg(origin.sender, '{red}Got an error.')
def setupServer(server): defaults = { 'website': 'https://www.liamstanley.io/Code.git', 'name': '\x0307Code -- Python IRC Bot', 'user': '******', 'port': 6667, 'server_password': None } for key, value in defaults.iteritems(): if key not in server: server[key] = value continue if not server[key]: server[key] = value if server['host'] == 'irc.example.net': error = 'You must edit the config file first!' output.error(error) sys.exit(1) return server
def setupServer(server): defaults = { 'website': 'https://github.com/lrstanley/Code', 'name': '\x0307Code -- Python IRC Bot', 'user': '******', 'port': 6667, 'server_password': None } for key, value in defaults.iteritems(): if key not in server: server[key] = value continue if not server[key]: server[key] = value if server['host'] == 'irc.example.net': error = 'You must edit the config file first!' output.error(error) sys.exit(1) return server
def create_logdir(): try: os.mkdir(cwd + '/logs') except Exception, e: output.error('There was a problem creating the logs directory.') output.error(e.__class__, str(e)) output.error('Please fix this and then run code again.') sys.exit(1)
def setupServer(server): defaults = { 'website': 'https://github.com/adiq/stah', 'name': '\x0307Stah -- Python IRC Bot', 'user': '******', 'port': 6667, 'server_password': None } for key, value in defaults.iteritems(): if key not in server: server[key] = value continue if not server[key]: server[key] = value if server['host'] == 'irc.example.net': error = ( 'You must edit the config file first!\n' 'You\'re currently using %s' % server.filename ) output.error(error) sys.exit(1) return server
def search(code, input): """Queries DuckDuckGo for the specified input.""" try: data = web.get(uri, params={'q': input.group(2)}) tmp = data.text.replace('\r', '').replace('\n', '').strip() target = r'(?im)(<div class="result results_links.*?">.*?<a .*?class="result__a" href="([^"]+)">(.*?)</a>.*?</div>)' found = [ x for x in list(re.findall(target, tmp)) if len(x) > 0 and "badge--ad" not in x[0] ] if len(found) > url_count: found = found[:url_count] results = [] if len(found) < 1: return code.say('{b}No results found{b}') count = 0 for item in found: i = list(item) result = {} result['url'] = i[1] result['title'] = web.escape(web.striptags(i[2])) if len(result['title']) > title_length: result['title'] = result['title'][:title_length] + '{b}...{b}' results.append('{%s}%s{c} - %s' % (url_colors[count], result['title'], result['url'])) count += 1 return code.say(' | '.join(results)) except Exception as e: output.error('Error in search.py: %s' % str(e)) return code.say('{b}Unable to search for %s{b}' % input.group(2))
def connect(id, config): while True: try: # Todo, pass thread number, if more than one thread, pass in # console bot.Code(config).run(id, config['host'], config['port']) except: output.error('Error in process (Server: %s, port: %s)' % (config['host'], config['port'])) output.error('Terminating and restarting in 20 seconds...') time.sleep(5) output.error('Restarting...') pass
def auto_honeypot(code, input): """Check joining users against the Project Honeypot Database""" if not code.config('honeypot_on_join') or input.nick == code.nick: return global db ip = get_ip(input.host) try: abuser = check(ip) except: return output.error('Failed to get IP information. Project Honeypot seems to be down!') if abuser: # First, we need to check if we've already checked for it, and got a # match... if ip in db: return db.append(ip) database.set(code.default, db, 'honeypot') if code.config('kickban_on_honeypot') and code.chan[input.sender][code.nick]['op']: # Wants to kickban, and we've got op. BANHAMMER TIME! code.write(['MODE', input.sender, '+b', '*!*@' + input.host]) code.write(['KICK', input.sender, input.nick], abuser) code.say(abuser)
def close(): output.error("Nobody PONGed our PING, restarting") code.handle_close()
def bind_commands(code): code.commands = {'high': {}, 'medium': {}, 'low': {}} def bind(code, priority, regexp, func): if not hasattr(func, 'name'): func.name = func.__name__ code.commands[priority].setdefault(regexp, []).append(func) def sub(pattern, code=code): # These replacements have significant order pattern = pattern.replace('$nickname', re.escape(code.nick)) return pattern.replace('$nick', r'%s[,:] +' % re.escape(code.nick)) for name, func in code.variables.iteritems(): # print name, func code.doc[name] = { 'commands': [], 'info': None, 'example': None, 'syntax': None } if func.__doc__: doc = func.__doc__.replace('\n', '').strip() while ' ' in doc: doc = doc.replace(' ', ' ') if ' -- ' in doc: code.doc[name]['syntax'] = code.prefix + doc.split(' -- ', 1)[0] code.doc[name]['info'] = doc.split(' -- ', 1)[1] else: code.doc[name]['info'] = doc else: doc = None if hasattr(func, 'example'): example = func.example example = code.prefix + example.replace('$nickname', code.nick) else: example = None code.doc[name]['example'] = example if not hasattr(func, 'priority'): func.priority = 'medium' if not hasattr(func, 'supress'): func.supress = False if not hasattr(func, 'thread'): func.thread = True if not hasattr(func, 'event'): func.event = 'PRIVMSG' else: func.event = func.event.upper() if not hasattr(func, 'rate'): if hasattr(func, 'commands'): func.rate = 3 else: func.rate = 0 if hasattr(func, 'rule'): if isinstance(func.rule, str): pattern = sub(func.rule) try: regexp = re.compile(pattern) except Exception as e: # assume its defunct regex, throw error.. but continue output.error('Error in %s: %s' % (name, str(e))) bind(code, func.priority, regexp, func) if isinstance(func.rule, tuple): # 1) e.g. ('$nick', '(.*)') if len(func.rule) == 2 and isinstance(func.rule[0], str): prefix, pattern = func.rule prefix = sub(prefix) regexp = re.compile(prefix + pattern) bind(code, func.priority, regexp, func) # 2) e.g. (['p', 'q'], '(.*)') elif len(func.rule) == 2 and isinstance(func.rule[0], list): prefix = code.prefix commands, pattern = func.rule for command in commands: command = r'(?i)(\%s)\b(?: +(?:%s))?' % (command, pattern) regexp = re.compile(prefix + command) bind(code, func.priority, regexp, func) # 3) e.g. ('$nick', ['p', 'q'], '(.*)') elif len(func.rule) == 3: prefix, commands, pattern = func.rule prefix = sub(prefix) for command in commands: command = r'(?i)(\%s) +' % command regexp = re.compile(prefix + command + pattern) bind(code, func.priority, regexp, func) if hasattr(func, 'commands'): code.doc[name]['commands'] = list(func.commands) for command in list(func.commands): # <prefix><command> # <bot-name>: <command> # <bot-name>, <command> template = r'(?i)^(?:{prefix}|{nick}: |{nick}, )({cmd})(?: +(.*))?$' regexp = re.compile( template.format(prefix=re.escape(code.prefix), nick=re.escape(code.nick), cmd=command)) bind(code, func.priority, regexp, func)
def call(self, func, origin, code, input): # custom decorators try: if func.op and not code.chan[input.sender][input.nick]['op']: return code.say('{b}{red}You must be op to use that command!') if func.voiced and not code.chan[input.sender][input.nick]['voiced']: return code.say('{b}{red}You must be voiced to use that command!') input.op = code.chan[input.sender][input.nick]['op'] input.voiced = code.chan[input.sender][input.nick]['voiced'] input.chan = code.chan[input.sender] except KeyError: pass if func.admin and not input.admin: return code.say('{b}{red}You are not authorized to use that command!') if func.owner and not input.owner: return code.say('{b}{red}You must be owner to use that command!') if func.args and not input.group(2): msg = '{red}No arguments supplied! Try: ' + \ '"{b}{purple}%shelp %s{b}{r}"' return code.say(msg % ( code.prefix, code.doc[func.name]['commands'][0]) ) nick = input.nick.lower() if nick in self.times: if func in self.times[nick]: if not input.admin: if time.time() - self.times[nick][func] < func.rate: self.times[nick][func] = time.time() return else: self.times[nick] = dict() self.times[nick][func] = time.time() try: if hasattr(self, 'excludes'): if input.sender in self.excludes: if '!' in self.excludes[input.sender]: # block all function calls for this channel return fname = func.func_code.co_filename.split( '/')[-1].split('.')[0] if fname in self.excludes[input.sender]: # block function call if channel is blacklisted print( 'Blocked:', input.sender, func.name, func.func_code.co_filename ) return except: output.error("Error attempting to block: ", str(func.name)) self.error(origin) try: func_return = func(code, input) if isinstance(func_return, str) or isinstance(func_return, unicode): code.say(func_return) except: self.error(origin)
def main(argv=None): # 1: Parse The Command Line parser = optparse.OptionParser('%prog [options]') parser.add_option( '-c', '--config', metavar='fn', help='use this configuration file or directory' ) opts, args = parser.parse_args(argv) # 2: Documentation output docstring() # 3: Require python 2.7 or later if sys.version_info < (2, 7): output.error('Requires Python 2.7.x, from www.python.org') sys.exit(1) # 4. Create ~/.code if not made already if not os.path.isdir(dotdir): if not os.path.isdir(dotdir): try: output.info('Creating database directory in ~/.code...') os.mkdir(dotdir) except Exception as e: output.error('There was a problem creating %s:' % dotdir) output.error(str(e)) output.error('Please fix this and then run code again.') sys.exit(1) # 5: Load The Configuration bot_config = opts.config or 'config.json' # and check if exists if not os.path.isfile(bot_config): output.error( 'Configuration file "%s" does not exist. Please copy ' 'the example.json to config.json then run Code again' % bot_config) sys.exit(1) try: config = parse_json(bot_config) except Exception as e: output.error('The config file has syntax errors. Please fix them and run Code again!\n' + str(e)) sys.exit(1) global threads for server in config['servers']: if server['host'] == 'irc.anotherexample.net': continue id = len(threads) process = Process(target=connect, args=(id, setupServer(server),)) process.daemon = True process.start() threads.append({'id': id, 'config': server, 'process': process}) time.sleep(5) # 6: Begin managing these processes try: # set some temporary variables that we will be using for config # file version checking conf_last_check = int(time.time()) conf_last_mtime = int(os.path.getmtime(bot_config)) while True: time.sleep(1) if (int(time.time()) - conf_last_check) > 10 and int(os.path.getmtime(bot_config)) > conf_last_mtime: conf_last_check = int(time.time()) conf_last_mtime = int(os.path.getmtime(bot_config)) try: # If the new configuration file isn't the same as the last # one that we saved, attempt to re-import it config_new = parse_json(bot_config) if len(config_new['servers']) == len(config['servers']) and len(config_new['servers']) == len(threads): output.success('Configuration file %s has changed! Use the restart command to take affect!' % bot_config) config = config_new for i in range(len(config['servers'])): # Once they reboot that connection, it should autoload # the new config. threads[i]['config'] = config['servers'][i] except Exception as e: # Only spit out errors once per file modification output.error("Configuration file has been changed, however I cannot read it! (%s)" % str(e)) if len(threads) == 0: output.warning('No more processes to manage. Exiting...') sys.exit() for id in range(len(threads)): p = threads[id]['process'] if p.exitcode == 0: # Assume it exited safely. Ignore the termination. p.terminate() output.status('Terminating process ID #%s (%s:%s)' % (id, threads[id]['config']['host'], threads[id]['config']['port'])) del threads[id] break if p.exitcode == 1: # Exited erronously. We'll just assume it wants a reboot. p.terminate() p = Process(target=connect, args=(id, setupServer(threads[id]['config']),)) p.daemon = True delay = threads[id]['config']['connect_delay'] if 'connect_delay' in threads[id]['config'] else 20 output.error('Restarting process id #%s (%s:%s) in %s seconds.' % ( id, threads[id]['config']['host'], threads[id]['config']['port'], str(delay) )) time.sleep(delay) output.status('Regenerating process ID #%s (%s:%s)' % (id, threads[id]['config']['host'], threads[id]['config']['port'])) p.start() threads[id]['process'] = p except KeyboardInterrupt: output.success('Shutting down bot...', 'REQUEST') for id in range(len(threads)): p = threads[id]['process'] output.status('Terminating process ID #%s (%s:%s)' % (id, threads[id]['config']['host'], threads[id]['config']['port'])) p.terminate() time.sleep(1) sys.exit()
def call(self, func, origin, code, input): input.channel = input.sender if input.sender.startswith('#') else False # custom decorators if func.ischannel and not input.channel: if not func.supress: code.say('{b}That can only be used in a channel!') return try: if func.op and not code.chan[input.sender][input.nick]['op']: if not func.supress: code.say('{b}{red}You must be op to use that command!') return if func.voiced and not code.chan[input.sender][input.nick]['voiced']: if not func.supress: code.say('{b}{red}You must be voiced to use that command!') return input.op = code.chan[input.sender][input.nick]['op'] input.voiced = code.chan[input.sender][input.nick]['voiced'] input.chan = code.chan[input.sender] except KeyError: pass if func.admin and not input.admin or func.trusted and not input.trusted: if not func.supress: code.say('{b}{red}You are not authorized to use that command!') return if func.owner and not input.owner: if not func.supress: code.say('{b}{red}You must be owner to use that command!') return if func.args and not input.group(2): msg = '{red}No arguments supplied! Try: ' + \ '"{b}{purple}%shelp %s{b}{r}"' return code.say(msg % (code.prefix, code.doc[func.name]['commands'][0])) if func.selfopped and not code.chan[input.sender][code.nick]['op']: return code.say( '{b}{red}I do not have op. I cannot execute this command.') nick = input.nick.lower() if nick in self.times: # per-command rate limiting if func in self.times[nick]: if not input.admin: if time.time() - self.times[nick][func] < func.rate: self.times[nick][func] = time.time() return else: self.times[nick] = dict() self.times[nick][func] = time.time() try: if self.excludes: if input.sender in self.excludes: if '!' in self.excludes[input.sender]: # block all function calls for this channel return fname = func.func_code.co_filename.split('/')[-1].split('.')[0] if fname in self.excludes[input.sender]: # block function call if channel is blacklisted return output.error( 'Blocks', 'Blocked: %s from %s' % (input.sender, func.name)) except: output.error("Error attempting to block: ", str(func.name)) self.error(origin) try: func_return = func(code, input) if isinstance(func_return, str) or isinstance(func_return, unicode): code.say(func_return) except: if not func.supress: self.error(origin) else: self.error(origin, supress=True)
def handle_error(self): '''Handle any uncaptured error in the core. Overrides asyncore's handle_error''' trace = traceback.format_exc() output.error('Fatal error in core, please review exception below:') output.error('Exception: ' + trace)
def call(self, func, origin, code, input): # custom decorators try: if func.op and not code.chan[input.sender][input.nick]['op']: return code.say('{b}{red}You must be op to use that command!') if func.voiced and not code.chan[input.sender][ input.nick]['voiced']: return code.say( '{b}{red}You must be voiced to use that command!') input.op = code.chan[input.sender][input.nick]['op'] input.voiced = code.chan[input.sender][input.nick]['voiced'] input.chan = code.chan[input.sender] except KeyError: pass if func.admin and not input.admin: return code.say( '{b}{red}You are not authorized to use that command!') if func.owner and not input.owner: return code.say('{b}{red}You must be owner to use that command!') if func.args and not input.group(2): msg = '{red}No arguments supplied! Try: ' + \ '"{b}{purple}%shelp %s{b}{r}"' return code.say(msg % (code.prefix, code.doc[func.name]['commands'][0])) nick = (input.nick).lower() if nick in self.times: if func in self.times[nick]: if not input.admin: if time.time() - self.times[nick][func] < func.rate: self.times[nick][func] = time.time() return else: self.times[nick] = dict() self.times[nick][func] = time.time() try: if hasattr(self, 'excludes'): if input.sender in self.excludes: if '!' in self.excludes[input.sender]: # block all function calls for this channel return fname = func.func_code.co_filename.split('/')[-1].split( '.')[0] if fname in self.excludes[input.sender]: # block function call if channel is blacklisted print('Blocked:', input.sender, func.name, func.func_code.co_filename) return except: output.error("Error attempting to block: ", str(func.name)) self.error(origin) try: func(code, input) except: self.error(origin)
def handle_close(self): self.close() output.error('Disconnected from IRC - Reason unknown!')
output.error('Restarting process id #%s (%s:%s) in %s seconds.' % ( id, threads[id]['config']['host'], threads[id]['config']['port'], str(delay) )) time.sleep(delay) output.status('Regenerating process ID #%s (%s:%s)' % (id, threads[id]['config']['host'], threads[id]['config']['port'])) p.start() threads[id]['process'] = p except KeyboardInterrupt: output.success('Shutting down bot...', 'REQUEST') for id in range(len(threads)): p = threads[id]['process'] output.status('Terminating process ID #%s (%s:%s)' % (id, threads[id]['config']['host'], threads[id]['config']['port'])) p.terminate() time.sleep(1) sys.exit() def connect(id, config): bot.Code(config).run(id, config['host'], config['port']) if __name__ == '__main__': try: main() except SystemExit: sys.exit() except KeyboardInterrupt: sys.exit() except: output.error(traceback.format_exc())