class MemosPlugin(plugin.PluginObject): commands = None events = None storage = None # data = None # SQLite for a change def setup(self): self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() # self.data = self.storage.get_file(self, "data", SQLITE, # "plugins/memos/memos.sqlite") # with self.data as c: # # Multiline strings because of an IDE bug # c.execute("""CREATE TABLE IF NOT EXISTS memos # (to TEXT, from TEXT, memo TEXT)""") self.events.add_callback("PreMessageReceived", self, self.message_received, 0) self.commands.register_command("memo", self.memo_command, self, "memo.send", default=True) def save_memo(self, sender, recipient, memo): recipient = recipient.lower() # with self.data as c: # c.execute("""INSERT INTO memos VALUES (?, ?, ?)""", # (recipient, sender, memo)) def get_memos(self, recipient): recipient = recipient.lower() # with self.data as c: # c.execute("""SELECT * FROM memos WHERE from=?""", (recipient,)) # d = c.fetchall() # return d def message_received(self, event=PreMessageReceived): user = event.source target = event.target if isinstance(event.target, Channel) else user memos = self.get_memos(user.name) if memos: for memo in memos: sender = memo[1] text = memo[2] target.respond("Memo for %s (from %s): %s" % (user.name, sender, text)) def memo_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature raise NotImplementedError("This isn't done yet.")
class WebhooksPlugin(plugin.PluginObject): config = None data = None commands = None events = None plugins = None storage = None @property def web(self): """ :rtype: plugins.web.WebPlugin """ return self.plugins.get_plugin("Web") def setup(self): self.logger.trace("Entered setup method.") self.commands = CommandManager() self.events = EventManager() self.plugins = PluginManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/webhooks.yml") except Exception: self.logger.exception("Error loading configuration!") return self._disable_self() else: if not self.config.exists: self.logger.error("Unable to find config/plugins/webhooks.yml") return self._disable_self() self._load() self.config.add_callback(self._load) self.events.add_callback("Web/ServerStartedEvent", self, self.add_routes, 0) def _load(self): pass def add_routes(self, _=None): self.web.add_navbar_entry("Webhooks", "/webhooks", "url") self.web.add_handler("/webhooks", "plugins.webhooks.routes.webhooks.Route") self.logger.info("Registered route: /webhooks") pass # So the regions work in PyCharm
class WebhooksPlugin(plugin.PluginObject): config = None data = None commands = None events = None plugins = None storage = None @property def web(self): """ :rtype: plugins.web.WebPlugin """ return self.plugins.get_plugin("Web") def setup(self): self.logger.trace("Entered setup method.") self.commands = CommandManager() self.events = EventManager() self.plugins = PluginManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/webhooks.yml") except Exception: self.logger.exception("Error loading configuration!") return self._disable_self() else: if not self.config.exists: self.logger.error("Unable to find config/plugins/webhooks.yml") return self._disable_self() self._load() self.config.add_callback(self._load) self.events.add_callback("Web/ServerStartedEvent", self, self.add_routes, 0) def _load(self): pass def add_routes(self, _=None): self.web.add_navbar_entry("Webhooks", "/webhooks", "url") self.web.add_handler("/webhooks", "plugins.webhooks.routes.webhooks.Route") self.logger.info("Registered route: /webhooks") pass # So the regions work in PyCharm
class AssPlugin(plugin.PluginObject): events = None regex = None def setup(self): self.events = EventManager() self.regex = re.compile(r"(\w+)-ass (\w+)") self.events.add_callback("MessageReceived", self, self.ass_swap, 1) def ass_swap(self, event=MessageReceived): source = event.source target = event.target message = event.message if re.search(self.regex, message) is None: return result = re.sub(self.regex, r"\1 ass-\2", message) target.respond("%s: %s" % (source.nickname, result))
class URLsPlugin(plugin.PluginObject): """ URLs plugin object """ catcher = None channels = None commands = None config = None events = None shortened = None storage = None blacklist = [] handlers = {} shorteners = {} spoofing = {} content_types = ["text/html", "text/webviewhtml", "message/rfc822", "text/x-server-parsed-html", "application/xhtml+xml"] def setup(self): """ Called when the plugin is loaded. Performs initial setup. """ self.logger.trace(_("Entered setup method.")) self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/urls.yml") except Exception: self.logger.exception(_("Error loading configuration!")) else: if not self.config.exists: self.logger.warn(_("Unable to find config/plugins/urls.yml")) else: self.content_types = self.config["content_types"] self.spoofing = self.config["spoofing"] self.logger.debug(_("Spoofing: %s") % self.spoofing) self.channels = self.storage.get_file(self, "data", YAML, "plugins/urls/channels.yml") self.shortened = self.storage.get_file( self, "data", DBAPI, "sqlite3:data/plugins/urls/shortened.sqlite", "data/plugins/urls/shortened.sqlite", check_same_thread=False ) self.commands = CommandManager() self.events = EventManager() self.reload() def message_event_filter(event=MessageReceived): target = event.target type_ = event.type return type_ == "message" \ or isinstance(target, Channel) \ or isinstance(target, User) self.add_shortener("tinyurl", self.tinyurl) self.events.add_callback("MessageReceived", self, self.message_handler, 1, message_event_filter) self.commands.register_command("urls", self.urls_command, self, "urls.manage") self.commands.register_command("shorten", self.shorten_command, self, "urls.shorten", default=True) def reload(self): """ Reload files and create tables as necessary """ self.shortened.runQuery("CREATE TABLE IF NOT EXISTS urls (" "url TEXT, " "shortener TEXT, " "result TEXT)") self.catcher = Catcher(self, self.config, self.storage, self.logger) self.blacklist = [] blacklist = self.config.get("blacklist", []) for element in blacklist: try: self.blacklist.append(re.compile(element)) except Exception: self.logger.exception("Unable to compile regex '%s'" % element) def check_blacklist(self, url): """ Check whether a URL is in the user-defined blacklist :param url: The URL to check :type url: str :return: Whether the URL is in the blacklist :rtype: bool """ for pattern in self.blacklist: try: self.logger.debug(_("Checking pattern '%s' against URL '%s'") % (pattern, url)) if re.match(pattern, url): return True except Exception as e: self.logger.debug(_("Error in pattern matching: %s") % e) return False return False @run_async_threadpool def message_handler(self, event=MessageReceived): """ Event handler for general messages """ protocol = event.caller source = event.source target = event.target message = event.message allowed = self.commands.perm_handler.check("urls.title", source, target, protocol) if not allowed: return # PEP = wat sometimes if self.channels.get(protocol.name, {}).get(target.name, {}).get("status", "on") == "off": return # Strip formatting characters if possible message_stripped = message try: message_stripped = event.caller.utils.strip_formatting(message) except AttributeError: pass for word in message_stripped.split(" "): pos = word.lower().find("http://") if pos == -1: pos = word.lower().find("https://") if pos > -1: end = word.lower().find(" ", pos) if end > -1: url = word[pos:end] else: url = word[pos:] if url in ["http://", "https://"]: self.logger.trace(_("URL is not actually a URL, just %s" % url)) return if self.check_blacklist(url): self.logger.debug(_("Not parsing, URL is blacklisted.")) return if isinstance(target, Channel): try: self.catcher.insert_url(url, source.nickname, target.name, protocol.name) except Exception: self.logger.exception(_("Error catching URL")) title, domain = self.parse_title(url) self.logger.trace(_("Title: %s") % title) if isinstance(target, Channel): if protocol.name not in self.channels: with self.channels: self.channels[protocol.name] = { target.name: {"last": url, "status": "on", "shortener": "tinyurl"} } if target.name not in self.channels[protocol.name]: with self.channels: self.channels[protocol.name][target.name] = { "last": url, "status": "on", "shortener": "tinyurl" } else: with self.channels: self.channels[protocol.name][target.name]["last"] \ = url if title is None: return if domain is not None and "/" in domain: domain = domain.split("/")[0] if domain is None: target.respond(title) else: target.respond("\"%s\" at %s" % (title, domain)) elif isinstance(target, User): if title is None: return if domain is not None and "/" in domain: domain = domain.split("/")[0] if domain is None: source.respond(title) else: source.respond("\"%s\" at %s" % (title, domain)) else: self.logger.warn(_("Unknown target type: %s [%s]") % (target, target.__class__)) def urls_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the urls command """ args = raw_args.split() # Quick fix for new command handler signature if not isinstance(source, Channel): caller.respond(__("This command can only be used in a channel.")) return if len(args) < 2: caller.respond(__("Usage: {CHARS}urls <setting> <value>")) caller.respond(__("Operations: set <on/off> - Enable or disable " "title parsing for the current channel")) caller.respond(" %s" % __("shortener <name> - Set " "which URL shortener to use " "for the current channel")) caller.respond(" %s" % __("Shorteners: %s") % ", ".join(self.shorteners.keys())) return operation = args[0].lower() value = args[1].lower() if protocol.name not in self.channels: with self.channels: self.channels[protocol.name] = { source.name: { "status": "on", "last": "", "shortener": "tinyurl" } } if source.name not in self.channels[protocol.name]: with self.channels: self.channels[protocol.name][source.name] = { "status": "on", "last": "", "shortener": "tinyurl" } if operation == "set": if value not in [__("on"), __("off")]: caller.respond(__("Usage: {CHARS}urls set <on|off>")) else: with self.channels: if value == __("on"): value = "on" elif value == __("off"): value = "off" self.channels[protocol.name][source.name]["status"] = value caller.respond(__("Title passing for %s turned %s.") % (source.name, __(value))) elif operation == "shortener": if value.lower() in self.shorteners: with self.channels: self.channels[protocol.name][source.name]["shortener"] \ = value.lower() caller.respond(__("URL shortener for %s set to %s.") % (source.name, value)) else: caller.respond(__("Unknown shortener: %s") % value) else: caller.respond(__("Unknown operation: '%s'.") % operation) def _respond_shorten(self, result, source, handler): """ Respond to a shorten command, after a successful Deferred """ if result is not None: return source.respond(result) return source.respond(__("Unable to shorten using handler %s. Poke the" "bot owner!") % handler) def _respond_shorten_fail(self, failure, source, handler): """ Respond to a shorten command, after a failed Deferred """ return source.respond(__("Error shortening url with handler %s: %s") % (handler, failure)) def shorten_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the shorten command """ args = parsed_args # Quick fix for new command handler signature if not isinstance(source, Channel): if len(args) == 0: caller.respond(__("Usage: {CHARS}shorten [url]")) return else: handler = "tinyurl" url = args[0] try: d = self.shorten_url(url, handler) d.addCallbacks(self._respond_shorten, self._respond_shorten_fail, callbackArgs=(source, handler), errbackArgs=(source, handler)) except Exception as e: self.logger.exception(_("Error fetching short URL.")) caller.respond(__("Error: %s") % e) return else: if protocol.name not in self.channels \ or source.name not in self.channels[protocol.name] \ or not len(self.channels[protocol.name][source.name]["last"]): caller.respond(__("Nobody's pasted a URL here yet!")) return handler = self.channels[protocol.name][source.name]["shortener"] if len(handler) == 0: with self.channels: self.channels[protocol.name][source.name]["shortener"]\ = "tinyurl" handler = "tinyurl" if handler not in self.shorteners: caller.respond(__("Shortener '%s' not found - please set a " "new one!") % handler) return url = self.channels[protocol.name][source.name]["last"] if len(args) > 0: url = args[0] try: d = self.shorten_url(url, handler) d.addCallbacks(self._respond_shorten, self._respond_shorten_fail, callbackArgs=(source, handler), errbackArgs=(source, handler)) except Exception as e: self.logger.exception(_("Error fetching short URL.")) caller.respond(__("Error: %s") % e) return def tinyurl(self, url): """ Shorten a URL with TinyURL. Don't use this directly. """ return urllib2.urlopen("http://tinyurl.com/api-create.php?url=" + urllib.quote_plus(url)).read() def parse_title(self, url, use_handler=True): """ Get and return the page title for a URL, or the title from a specialized handler, if one is registered. This function returns a tuple which may be one of these forms.. * (title, None) if the title was fetched by a specialized handler * (title, domain) if the title was parsed from the HTML * (None, None) if fetching the title was entirely unsuccessful. This occurs in each of the following cases.. * When a portscan is detected and stopped * When the page simply has no title * When there is an exception in the chain somewhere :param url: The URL to check :param use_handler: Whether to use specialized handlers :type url: str :type use_handler: bool :returns: A tuple containing the result :rtype: tuple(None, None), tuple(str, str), tuple(str, None) """ domain = "" self.logger.trace(_("Url: %s") % url) try: parsed = urlparse.urlparse(url) domain = parsed.hostname ip = socket.gethostbyname(domain) matches = all_matching_cidrs(ip, ["10.0.0.0/8", "0.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"]) if matches: self.logger.warn(_("Prevented a portscan: %s") % url) return None, None if domain.startswith("www."): domain = domain[4:] if use_handler: for pattern in self.handlers: if fnmatch.fnmatch(domain, pattern): try: result = self.handlers[domain](url) if result: return to_unicode(result), None except Exception: self.logger.exception(_("Error running handler, " "parsing title normally.")) self.logger.trace(_("Parsed domain: %s") % domain) request = urllib2.Request(url) if domain in self.spoofing: self.logger.debug(_("Custom spoofing for this domain found.")) user_agent = self.spoofing[domain] if user_agent: self.logger.debug(_("Spoofing user-agent: %s") % user_agent) request.add_header("User-agent", user_agent) else: self.logger.debug(_("Not spoofing user-agent.")) else: self.logger.debug(_("Spoofing Firefox as usual.")) request.add_header('User-agent', 'Mozilla/5.0 (X11; U; Linux ' 'i686; en-US; rv:1.9.0.1) ' 'Gecko/2008071615 Fedora/3.0.' '1-1.fc9-1.fc9 Firefox/3.0.1') # Deal with Accept-Language language_value = None language = self.config.get("accept_language", {}) language_domains = language.get("domains", {}) if domain in language_domains: language_value = language_domains[domain] elif domain.lower() in language_domains: language_value = language_domains[domain.lower()] elif "default" in language: language_value = language["default"] if language_value is not None: request.add_header("Accept-Language", language_value) response = urllib2.urlopen(request) self.logger.trace(_("Info: %s") % response.info()) headers = response.info().headers new_url = response.geturl() _domain = domain parsed = urlparse.urlparse(new_url) domain = parsed.hostname if _domain != domain: self.logger.info(_("URL: %s") % new_url) self.logger.info(_("Domain: %s") % domain) if self.check_blacklist(new_url): self.logger.debug(_("Not parsing, URL is blacklisted.")) return ip = socket.gethostbyname(domain) matches = all_matching_cidrs(ip, ["10.0.0.0/8", "0.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"]) if matches: self.logger.warn(_("Prevented a portscan: %s") % new_url) return None, None if domain.startswith("www."): domain = domain[4:] if domain in self.handlers and use_handler: try: result = self.handlers[domain](new_url) if result: return to_unicode(result), None except Exception: self.logger.exception(_("Error running handler," " parsing title normally.")) headers_dict = {} for x in headers: k, v = x.split(": ", 1) headers_dict[k.lower()] = v.strip("\r\n") status_code = response.getcode() if status_code in [301, 302, 303, 307, 308]: return self.parse_title(headers["location"]) ct = headers_dict["content-type"] if ";" in ct: ct = ct.split(";")[0] self.logger.trace(_("Content-type: %s") % repr(ct)) if ct not in self.content_types: self.logger.debug(_("Content-type is not allowed.")) return None, None page = response.read() soup = BeautifulSoup(page) if soup.title and soup.title.string: title = soup.title.string.strip() title = re.sub("\s+", " ", title) title = to_unicode(title) domain = to_unicode(domain) return title, domain else: return None, None except Exception as e: if not str(e).lower() == "not viewing html": self.logger.exception(_("Error parsing title.")) return str(e), domain return None, None def _shorten(self, txn, url, handler): """ Shorten a URL, checking the database in case it's already been done. This is a database interaction and uses Deferreds. """ txn.execute("SELECT * FROM urls WHERE url=? AND shortener=?", (url, handler.lower())) r = txn.fetchone() self.logger.trace(_("Result (SQL): %s") % repr(r)) if r is not None: return r[2] if handler in self.shorteners: result = self.shorteners[handler](url) txn.execute("INSERT INTO urls VALUES (?, ?, ?)", (url, handler.lower(), result)) return result return None def shorten_url(self, url, handler): """ Shorten a URL using the specified handler. This returns a Deferred. :param url: The URL to shorten :param handler: The name of the handler to shorten with :type url: str :type handler: str :returns: Deferred which will fire with the result or None :rtype: Deferred """ self.logger.trace(_("URL: %s") % url) self.logger.trace(_("Handler: %s") % handler) return self.shortened.runInteraction(self._shorten, url, handler) def add_handler(self, domain, handler): """ API method to add a specialized URL handler. This will fail if there's already a handler there for that domain. :param domain: The domain to handle, without the 'www.'. :param handler: The callable handler :type domain: str :type handler: callable :returns: Whether the handler was registered :rtype: bool """ if domain.startswith("www."): raise ValueError(_("Domain should not start with 'www.'")) if domain not in self.handlers: self.logger.trace(_("Handler registered for '%s': %s") % (domain, handler)) self.handlers[domain] = handler return True return False def add_shortener(self, name, handler): """ API method to add a URL shortener. This is the same as `add_handler`, but for URL shortening. """ if name not in self.shorteners: self.logger.trace(_("Shortener '%s' registered: %s") % (name, handler)) self.shorteners[name] = handler return True return False def remove_handler(self, domain): if domain.startswith("www."): raise ValueError(_("Domain should not start with 'www.'")) if domain in self.handlers: del self.handlers[domain] return True return False def remove_shortener(self, name): if name in self.shorteners: del self.shorteners[name] return True return False
class WebPlugin(PluginObject): """ Web plugin object """ api_log = None api_keys = None api_key_data = {} config = {} data = {} namespace = {} # Global, not used right now handlers = {} # Cyclone handlers interface = "" # Listening interface listen_port = 8080 running = False application = None # Cyclone application port = None # Twisted's server port storage = None template_loader = None navbar_items = {} ## Stuff plugins might find useful commands = None events = None packages = None plugins = None sessions = None stats = None ## Internal(ish) functions def setup(self): self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/web.yml") self.logger.debug("Config loaded") except Exception: self.logger.exception(_("Error loading configuration")) self._disable_self() return if not self.config.exists: self.logger.error(_("Unable to find config/plugins/web.yml")) self._disable_self() return try: self.data = self.storage.get_file(self, "data", JSON, "plugins/web/data.json") self.logger.debug("Data loaded") except Exception: self.logger.exception("Error loading data file!") return self._disable_self() try: _sessions = self.storage.get_file(self, "data", JSON, "plugins/web/sessions.json") self.logger.debug("Sessions loaded") except Exception: self.logger.exception("Error loading sessions file!") return self._disable_self() try: self.api_log = open("logs/api.log", "w") except Exception: self.logger.exception("Unable to open api log file!") return self._disable_self() try: self.api_key_data = self.storage.get_file( self, "data", JSON, "plugins/web/apikeys.json" ) self.logger.debug("Sessions loaded") except Exception: self.logger.exception("Error loading API keys!") return self._disable_self() try: self.api_log = open("logs/api.log", "w") except Exception: self.logger.exception("Unable to open api log file!") return self._disable_self() self.config.add_callback(self.restart) self.data.add_callback(self.restart) # Index page self.add_handler(r"/", "plugins.web.routes.index.Route") # Login-related self.add_handler(r"/login", "plugins.web.routes.login.Route") self.add_handler(r"/logout", "plugins.web.routes.logout.Route") self.add_handler( r"/login/reset", "plugins.web.routes.login-reset.Route" ) # Accounts-related self.add_handler(r"/account", "plugins.web.routes.account.index.Route") self.add_handler( r"/account/password/change", "plugins.web.routes.account.password.change.Route" ) self.add_handler( r"/account/apikeys/create", "plugins.web.routes.account.apikeys.create.Route" ) self.add_handler( r"/account/apikeys/delete", "plugins.web.routes.account.apikeys.delete.Route" ) self.add_handler( r"/account/users/logout", "plugins.web.routes.account.users.logout.Route" ) # Admin-related self.add_handler( r"/admin", "plugins.web.routes.admin.index.Route" ) self.add_handler( r"/admin/files", "plugins.web.routes.admin.files.Route" ) self.add_handler( r"/admin/files/(config|data)/(.*)", "plugins.web.routes.admin.file.Route" ) self.add_handler( r"/api/admin/get_stats", "plugins.web.routes.api.admin.get_stats.Route" ) self.add_navbar_entry("admin", "/admin", "settings") # API routes self.add_api_handler( r"/plugins/web/get_username", "plugins.web.routes.api.plugins.web.get_username.Route" ) # Stuff routes might find useful self.api_keys = APIKeys(self, self.api_key_data) self.commands = CommandManager() self.events = EventManager() self.packages = Packages(False) self.plugins = PluginManager() self.sessions = Sessions(self, _sessions) self.stats = Stats() # Load 'er up! r = self.load() if not r: self._disable_self() return if not self.factory_manager.running: self.events.add_callback( "ReactorStarted", self, self.start, 0 ) else: self.start() def load(self): if "secret" not in self.data: self.logger.warn("Generating secret. DO NOT SHARE IT WITH ANYONE!") self.logger.warn("It's stored in data/plugins/web/data.json - " "keep this file secure!") with self.data: self.data["secret"] = mkpasswd(60, 20, 20, 20) self.template_loader = TemplateLoader(self) if self.config.get("output_requests", True): log_function = self.log_request else: log_function = self.null_log self.application = Application( list(self.handlers.items()), # Handler list ## General settings xheaders=True, log_function=log_function, gzip=True, # Are there browsers that don't support this now? # error_handler=ErrorHandler, ## Security settings cookie_secret=self.data["secret"], login_url="/login", ## Template settings template_loader=self.template_loader, ## Static file settings static_path="web/static" ) if self.config.get("hosted", False): hosted = self.config["hosted"] if isinstance(hosted, dict): self.interface = os.environ.get(hosted["hostname"], False) self.port = os.environ.get(hosted["port"], False) if not self.interface: self.logger.error( "Unknown env var: %s" % hosted["hostname"] ) return False if not self.port: self.logger.error( "Unknown env var: %s" % hosted["port"] ) return False else: if hosted in ["openshift"]: self.interface = os.environ.get("OPENSHIFT__IP", False) self.port = os.environ.get("OPENSHIFT__PORT", False) if not self.interface: self.logger.error( "Unknown env var: OPENSHIFT__IP - Are you on " "OpenShift?" ) return False if not self.port: self.logger.error( "Unknown env var: OPENSHIFT__PORT - Are you on " "OpenShift?" ) return False else: self.logger.error("Unknown hosted service: %s" % hosted) return False else: if self.config.get("hostname", "0.0.0.0").strip() == "0.0.0.0": self.interface = "" else: self.interface = self.config.get("hostname") self.listen_port = self.config.get("port", 8080) return True def start(self, _=None): self.stats.start() self.port = reactor.listenTCP( self.listen_port, self.application, interface=self.interface ) self.logger.info("Server started") self.running = True self.events.run_callback( "Web/ServerStartedEvent", ServerStartedEvent(self, self.application) ) def stop(self): self.application.doStop() d = self.port.stopListening() d.addCallback(lambda _: self.logger.info("Server stopped")) d.addCallback(lambda _: setattr(self, "running", False)) d.addCallback(lambda _: self.events.run_callback( "Web/ServerStopped", ServerStoppedEvent(self) )) d.addErrback(lambda f: self.logger.error("Failed to stop: %s" % f)) self.stats.stop() return d def restart(self): d = self.stop() d.addCallback(lambda _: [self.load(), self.start()]) def deactivate(self): d = self.stop() self.handlers.clear() self.navbar_items.clear() return d def log_request(self, request): log = self.logger.info status_code = request.get_status() if status_code >= 500: log = self.logger.error elif status_code >= 400: log = self.logger.warn path = request.request.path # Check if this is an API method and hide the key if so matched = re.match(r"/api/v[0-9]/([a-zA-Z0-9]+)/.*", path) if matched: key = matched.groups()[0] user = self.api_keys.get_username(key) if user: path = path.replace(key, "<API: %s>" % user) else: path = path.replace(key, "<API: Invalid key>") log( "[%s] %s %s -> HTTP %s" % ( request.request.remote_ip, request.request.method, path, request.get_status() ) ) def null_log(self, *args, **kwargs): pass ## Public API functions def add_api_handler(self, pattern, handler, version=1): if not pattern.startswith("/"): pattern = "/%s" % pattern pattern = "/api/v%s/([A-Za-z0-9]+)%s" % (version, pattern) return self.add_handler(pattern, handler) def add_handler(self, pattern, handler): self.logger.debug("Adding route: %s -> %s" % (pattern, handler)) if pattern in self.handlers: self.logger.debug("Route already exists.") return False self.handlers[pattern] = handler if self.application is not None: self.application.add_handlers(r".*$", [(pattern, handler)]) self.logger.debug("Handlers list: %s" % list(self.handlers.values())) def add_navbar_entry(self, title, url, icon="question"): if title in self.navbar_items: return False self.logger.debug("Adding navbar entry: %s -> %s" % (title, url)) self.navbar_items[title] = {"url": url, "active": False, "icon": icon} return True def check_permission(self, perm, session=None): if session is None: username = None elif isinstance(session, str) or isinstance(session, unicode): username = session else: username = session["username"] return self.commands.perm_handler.check( perm, username, "web", "plugin-web" ) def remove_api_handlers(self, *names, **kwargs): """ :param names: :param version: :return: """ version = kwargs.get("version", 1) patterns = [] for pattern in names: if not pattern.startswith("/"): pattern = "/%s" % pattern pattern = "/api/v%s/([A-Za-z0-9]+)%s" % (version, pattern) patterns.append(pattern) return self.remove_handlers(*patterns) def remove_handlers(self, *names): found = False for name in names: if name in self.handlers: found = True del self.handlers[name] if found: self.restart() def write_api_log(self, address, key, username, message): self.api_log.write( "%s | %s (%s) | %s\n" % (address, key, username, message) )
class DrunkPlugin(plugin.PluginObject): commands = None config = None storage = None def setup(self): ### Grab important shit self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() ### Initial config load try: self.config = self.storage.get_file(self, "config", YAML, "plugins/drunkoctopus.yml") except Exception: self.logger.exception("Error loading configuration!") self.logger.error("Disabling...") self._disable_self() return if not self.config.exists: self.logger.error("Unable to find config/plugins/drunkoctopus.yml") self.logger.error("Disabling...") self._disable_self() return ### Create vars and stuff self._sobering_call = None self._drunktalk = DrunkTalk() ### Load options from config self._load() self.config.add_callback(self._load) ### Register events and commands self.events.add_callback("MessageSent", self, self.outgoing_message_handler, 1) self.commands.register_command("drunkenness", self.drunkenness_command, self, "drunkoctopus.drunkenness", default=True) self.commands.register_command("drink", self.drink_command, self, "drunkoctopus.drink") def reload(self): try: self.config.reload() except Exception: self.logger.exception("Error reloading configuration!") return False return True def _load(self): self._drunktalk.drunkenness = self.config["drunkenness"] self._cooldown_enabled = self.config["cooldown"]["enabled"] self._cooldown_time = self.config["cooldown"]["time"] self._cooldown_amount = self.config["cooldown"]["amount"] self._drinks = self.config["drinks"] # Sort out the sobering deferred as necessary if self._cooldown_enabled: if self._sobering_call is None: self.logger.trace("Starting sobering call due to config " "change") self._sobering_call = reactor.callLater(self._cooldown_time, self._sober_up) else: if self._sobering_call is not None: self.logger.trace("Cancelling sobering call due to config " "change") self._sobering_call.cancel() def _sober_up(self): self.logger.trace("Sobering up") drunk = self._drunktalk.drunkenness drunk -= self._cooldown_amount if drunk < 0: drunk = 0 self._drunktalk.drunkenness = drunk if self._cooldown_enabled: reactor.callLater(self._cooldown_time, self._sober_up) def drunkenness_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if len(args) == 0: caller.respond("Drunkenness level: %s" % self._drunktalk.drunkenness) return elif len(args) == 1: try: new_drunk = int(args[0]) self._drunktalk.drunkenness = new_drunk caller.respond("New drunkenness level: %s" % self._drunktalk.drunkenness) return except: caller.respond("Invalid drunkenness level (use without " "arguments for usage)") else: caller.respond("Usage: {CHARS}drunkenness [percent level]") def drink_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if len(args) == 0: caller.respond("Usage: {CHARS}drink <type of drink>") return drink = " ".join(args) drinkl = drink.lower() if drinkl in self._drinks: protocol.send_action(source, "drinks {}".format(drink)) self._drunktalk.drunkenness += self._drinks[drinkl] else: caller.respond("I don't have any of that.") def outgoing_message_handler(self, event): """ :type event: MessageSent """ self.logger.trace("RECEIVED %s EVENT: %s" % (event.type, event.message)) event.message = self._drunktalk.drunk_typing(event.message)
class AuthPlugin(plugin.PluginObject): """ Auth plugin. In charge of logins and permissions. """ config = None passwords = None permissions = None blacklist = None commands = None events = None storage = None auth_h = None perms_h = None def setup(self): """ Called when the plugin is loaded. Performs initial setup. """ reload(auth_handler) reload(permissions_handler) self.logger.trace(_("Entered setup method.")) self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/auth.yml") except Exception: self.logger.exception(_("Error loading configuration!")) self.logger.error(_("Disabling..")) self._disable_self() return if not self.config.exists: self.logger.error(_("Unable to find config/plugins/auth.yml")) self.logger.error(_("Disabling..")) self._disable_self() return self.commands = CommandManager() self.events = EventManager() if self.config["use-permissions"]: try: self.permissions = self.storage.get_file( self, "data", YAML, "plugins/auth/" # PEP "permissions.yml") except Exception: self.logger.exception( _("Unable to load permissions. They " "will be unavailable!")) else: self.perms_h = permissions_handler.permissionsHandler( self, self.permissions) result = self.commands.set_permissions_handler(self.perms_h) if not result: self.logger.warn(_("Unable to set permissions handler!")) if self.config["use-auth"]: try: self.passwords = self.storage.get_file( self, "data", YAML, "plugins/auth/" # PEP! "passwords.yml") self.blacklist = self.storage.get_file( self, "data", YAML, "plugins/auth/" # PEP! "blacklist.yml") except Exception: self.logger.exception( _("Unable to load user accounts. They " "will be unavailable!")) else: self.auth_h = auth_handler.authHandler(self, self.passwords, self.blacklist) result = self.commands.set_auth_handler(self.auth_h) if not result: self.logger.warn(_("Unable to set auth handler!")) self.logger.debug(_("Registering commands.")) self.commands.register_command("login", self.login_command, self, "auth.login", default=True) self.commands.register_command("logout", self.logout_command, self, "auth.login", default=True) self.commands.register_command("register", self.register_command, self, "auth.register", default=True) self.commands.register_command("passwd", self.passwd_command, self, "auth.passwd", default=True) self.events.add_callback("PreCommand", self, self.pre_command, 10000) def pre_command(self, event=PreCommand): """ Pre-command hook to remove passwords from the log output. """ self.logger.trace(_("Command: %s") % event.command) if event.command.lower() in ["login", "register"]: if len(event.args) >= 2: split_ = event.printable.split("%s " % event.command) second_split = split_[1].split() second_split[1] = _("[REDACTED]") split_[1] = " ".join(second_split) donestr = "%s " % event.command done = donestr.join(split_) event.printable = done elif event.command.lower() == "passwd": split_ = event.printable.split("%s " % event.command) second_split = split_[1].split() dsplit = [] for x in second_split: dsplit.append(_("[REDACTED]")) split_[1] = " ".join(dsplit) donestr = "%s " % event.command done = donestr.join(split_) event.printable = done def login_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the login command - for logging users in. """ args = raw_args.split() # Quick fix for new command handler signature if len(args) < 2: caller.respond(__("Usage: {CHARS}login <username> <password>")) else: if self.auth_h.authorized(caller, source, protocol): caller.respond( __("You're already logged in. " "Try logging out first!")) return username = args[0] password = args[1] result = self.auth_h.login(caller, protocol, username, password) if not result: self.logger.warn( _("%s failed to login as %s") % (caller.nickname, username)) caller.respond(__("Invalid username or password!")) else: self.logger.info( _("%s logged in as %s") % (caller.nickname, username)) caller.respond(__("You are now logged in as %s.") % username) def logout_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the logout command - for logging users out. """ if self.auth_h.authorized(caller, source, protocol): self.auth_h.logout(caller, protocol) caller.respond(__("You have been logged out successfully.")) else: caller.respond(__("You're not logged in.")) def register_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the register command - for creating new user accounts. """ args = raw_args.split() # Quick fix for new command handler signature if len(args) < 2: caller.respond(__("Usage: {CHARS}register <username> <password>")) return username = args[0] password = args[1] if isinstance(source, Channel): source.respond(__("You can't create an account in a channel.")) caller.respond(__("Don't use this command in a channel!")) caller.respond(__("You should only use it in a private message.")) caller.respond( __("For your security, the password you used has " "been blacklisted.")) self.auth_h.blacklist_password(password, username) return if self.auth_h.user_exists(username): caller.respond(__("That username already exists!")) return if self.auth_h.password_backlisted(password, username): caller.respond( __("That password has been blacklisted. " "Try another!")) return if self.auth_h.create_user(username, password): caller.respond( __("Your account has been created and you will now " "be logged in. Thanks for registering!")) self.perms_h.create_user(username) self.login_command(caller, source, [username, password], protocol, raw_args, parsed_args) else: caller.respond( __("Something went wrong when creating your " "account! You should ask the bot operators " "about this.")) def passwd_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the passwd command - for changing passwords. """ args = raw_args.split() # Quick fix for new command handler signature if len(args) < 2: caller.respond( __("Usage: {CHARS}passwd <old password> " "<new password>")) return if not self.auth_h.authorized(caller, source, protocol): caller.respond( __("You must be logged in to change your " "password.")) return username = caller.auth_name old = args[0] new = args[1] if self.auth_h.password_backlisted(new, username): caller.respond( __("That password has been blacklisted. Try " "another!")) return if self.auth_h.change_password(username, old, new): caller.respond(__("Your password has been changed successfully.")) else: caller.respond(__("Old password incorrect - please try again!")) self.logger.warn( _("User %s failed to change the password for %s") % (caller, username)) def get_auth_handler(self): """ API function for getting the auth handler. This will return None if no handler is registered. """ if self.config["use-auth"]: return self.auth_h return None def get_permissions_handler(self): """ API function for getting the permissions handler. This will return None if no handler is registered. """ if self.config["use-permissions"]: return self.perms_h return None def deactivate(self): """ Called when the plugin is deactivated. Does nothing right now. """ if self.config["use-auth"]: if isinstance(self.commands.auth_handler, auth_handler.authHandler): self.commands.auth_handler = None if self.config["use-permissions"]: if isinstance(self.commands.perm_handler, permissions_handler.permissionsHandler): self.commands.perm_handler = None
class LastseenPlugin(plugin.PluginObject): commands = None events = None storage = None data = None # SQLite for a change def setup(self): self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() self.data = self.storage.get_file( self, "data", DBAPI, "sqlite3:data/plugins/lastseen/users.sqlite", "data/plugins/lastseen/users.sqlite", check_same_thread=False) self.data.runQuery("CREATE TABLE IF NOT EXISTS users (" "user TEXT, " "protocol TEXT, " "at INTEGER)") self.commands.register_command("seen", self.seen_command, self, "seen.seen", default=True) # General events self.events.add_callback("PreMessageReceived", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_source_caller]) self.events.add_callback("PreCommand", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_source_caller]) self.events.add_callback("NameChanged", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("UserDisconnected", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) # Mumble events self.events.add_callback("Mumble/UserRemove", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserJoined", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserMoved", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserSelfMuteToggle", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserSelfDeafToggle", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserRecordingToggle", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) def _get_user_txn(self, txn, user, protocol): user = user.lower() user = to_unicode(user) txn.execute(u"SELECT * FROM users WHERE user=? AND protocol=?", (user, protocol)) r = txn.fetchone() return r def _get_user_callback(self, result, user, protocol, source): if result is None: source.respond("User '%s' not found." % user) else: then = math.floor(result[2]) now = math.floor(time.time()) seconds = now - then m, s = divmod(seconds, 60) h, m = divmod(m, 60) d, h = divmod(h, 24) s = int(s) m = int(m) h = int(h) d = int(d) if (s + m + h + d) == 0: source.respond("'%s' was seen just now!" % user) else: constructed = "'%s' was seen" % user to_append = [] if d > 0: to_append.append("%s days" % d) if h > 0: to_append.append("%s hours" % h) if m > 0: to_append.append("%s minutes" % m) if s > 0: to_append.append("%s seconds" % s) length = len(to_append) i = 1 for x in to_append: if length - i == 0: if i != 1: constructed += " and %s" % x i += 1 continue if i != 1: constructed += ", %s" % x else: constructed += " %s" % x i += 1 constructed += " ago." source.respond(constructed) def _get_user_callback_fail(self, failure, user, protocol, source): source.respond("Error while finding user %s: %s" % (user, failure)) def get_user(self, user, protocol): return self.data.runInteraction(self._get_user_txn, user, protocol) def _insert_or_update_user(self, txn, user, protocol): user = user.lower() user = to_unicode(user) txn.execute("SELECT * FROM users WHERE user=? AND protocol=?", (user, protocol)) r = txn.fetchone() now = time.time() if r is None: txn.execute("INSERT INTO users VALUES (?, ?, ?)", (user, protocol, now)) return False else: txn.execute("UPDATE users SET at=? WHERE user=? AND protocol=?", (now, user, protocol)) return True def insert_or_update_user(self, user, protocol): self.data.runInteraction(self._insert_or_update_user, user, protocol) def seen_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if not args: caller.respond("Usage: {CHARS}seen <username>") else: user = "******".join(args) if user.lower() == protocol.ourselves.nickname.lower(): source.respond("I'm right here, smartass.") return if user.lower() == caller.nickname.lower(): source.respond("Having a bit of an out-of-body experience, " "%s?" % caller.nickname) return d = self.get_user(user, protocol.name) d.addCallbacks(self._get_user_callback, self._get_user_callback_fail, callbackArgs=(user.lower(), protocol.name, source), errbackArgs=(user.lower(), protocol.name, source)) def event_handler(self, event, handler): """ This is a generic function so that other plugins can catch events and cause a user's last seen value to update. The handler should return (username, protocol name) as a tuple, or a list of tuples if it needs to do more than one update. """ data = handler(event) if not isinstance(data, list): data = [data] for element in data: user, proto = element self.insert_or_update_user(user, proto) def event_source_caller(self, event): user = event.source.nickname proto = event.caller.name return user, proto def event_user_caller(self, event): user = event.user.nickname proto = event.caller.name return user, proto
class URLsPlugin(plugin.PluginObject): """ URLs plugin object """ catcher = None channels = None commands = None config = None events = None shortened = None storage = None blacklist = [] handlers = {} shorteners = {} spoofing = {} content_types = [ "text/html", "text/webviewhtml", "message/rfc822", "text/x-server-parsed-html", "application/xhtml+xml" ] def setup(self): """ Called when the plugin is loaded. Performs initial setup. """ self.logger.trace(_("Entered setup method.")) self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/urls.yml") except Exception: self.logger.exception(_("Error loading configuration!")) else: if not self.config.exists: self.logger.warn(_("Unable to find config/plugins/urls.yml")) else: self.content_types = self.config["content_types"] self.spoofing = self.config["spoofing"] self.logger.debug(_("Spoofing: %s") % self.spoofing) self.channels = self.storage.get_file(self, "data", YAML, "plugins/urls/channels.yml") self.shortened = self.storage.get_file( self, "data", DBAPI, "sqlite3:data/plugins/urls/shortened.sqlite", "data/plugins/urls/shortened.sqlite", check_same_thread=False) self.commands = CommandManager() self.events = EventManager() self.reload() def message_event_filter(event=MessageReceived): target = event.target type_ = event.type return type_ == "message" \ or isinstance(target, Channel) \ or isinstance(target, User) self.add_shortener("tinyurl", self.tinyurl) self.events.add_callback("MessageReceived", self, self.message_handler, 1, message_event_filter) self.commands.register_command("urls", self.urls_command, self, "urls.manage") self.commands.register_command("shorten", self.shorten_command, self, "urls.shorten", default=True) def reload(self): """ Reload files and create tables as necessary """ self.shortened.runQuery("CREATE TABLE IF NOT EXISTS urls (" "url TEXT, " "shortener TEXT, " "result TEXT)") self.catcher = Catcher(self, self.config, self.storage, self.logger) self.blacklist = [] blacklist = self.config.get("blacklist", []) for element in blacklist: try: self.blacklist.append(re.compile(element)) except Exception: self.logger.exception("Unable to compile regex '%s'" % element) def check_blacklist(self, url): """ Check whether a URL is in the user-defined blacklist :param url: The URL to check :type url: str :return: Whether the URL is in the blacklist :rtype: bool """ for pattern in self.blacklist: try: self.logger.debug( _("Checking pattern '%s' against URL '%s'") % (pattern, url)) if re.match(pattern, url): return True except Exception as e: self.logger.debug(_("Error in pattern matching: %s") % e) return False return False @run_async_threadpool def message_handler(self, event=MessageReceived): """ Event handler for general messages """ protocol = event.caller source = event.source target = event.target message = event.message allowed = self.commands.perm_handler.check("urls.title", source, target, protocol) if not allowed: return # PEP = wat sometimes if self.channels.get(protocol.name, {}).get(target.name, {}).get("status", "on") == "off": return # Strip formatting characters if possible message_stripped = message try: message_stripped = event.caller.utils.strip_formatting(message) except AttributeError: pass for word in message_stripped.split(" "): pos = word.lower().find("http://") if pos == -1: pos = word.lower().find("https://") if pos > -1: end = word.lower().find(" ", pos) if end > -1: url = word[pos:end] else: url = word[pos:] if url in ["http://", "https://"]: self.logger.trace( _("URL is not actually a URL, just %s" % url)) return if self.check_blacklist(url): self.logger.debug(_("Not parsing, URL is blacklisted.")) return if isinstance(target, Channel): try: self.catcher.insert_url(url, source.nickname, target.name, protocol.name) except Exception: self.logger.exception(_("Error catching URL")) title, domain = self.parse_title(url) self.logger.trace(_("Title: %s") % title) if isinstance(target, Channel): if protocol.name not in self.channels: with self.channels: self.channels[protocol.name] = { target.name: { "last": url, "status": "on", "shortener": "tinyurl" } } if target.name not in self.channels[protocol.name]: with self.channels: self.channels[protocol.name][target.name] = { "last": url, "status": "on", "shortener": "tinyurl" } else: with self.channels: self.channels[protocol.name][target.name]["last"] \ = url if title is None: return if domain is not None and "/" in domain: domain = domain.split("/")[0] if domain is None: target.respond(title) else: target.respond("\"%s\" at %s" % (title, domain)) elif isinstance(target, User): if title is None: return if domain is not None and "/" in domain: domain = domain.split("/")[0] if domain is None: source.respond(title) else: source.respond("\"%s\" at %s" % (title, domain)) else: self.logger.warn( _("Unknown target type: %s [%s]") % (target, target.__class__)) def urls_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the urls command """ args = raw_args.split() # Quick fix for new command handler signature if not isinstance(source, Channel): caller.respond(__("This command can only be used in a channel.")) return if len(args) < 2: caller.respond(__("Usage: {CHARS}urls <setting> <value>")) caller.respond( __("Operations: set <on/off> - Enable or disable " "title parsing for the current channel")) caller.respond(" %s" % __("shortener <name> - Set " "which URL shortener to use " "for the current channel")) caller.respond(" %s" % __("Shorteners: %s") % ", ".join(self.shorteners.keys())) return operation = args[0].lower() value = args[1].lower() if protocol.name not in self.channels: with self.channels: self.channels[protocol.name] = { source.name: { "status": "on", "last": "", "shortener": "tinyurl" } } if source.name not in self.channels[protocol.name]: with self.channels: self.channels[protocol.name][source.name] = { "status": "on", "last": "", "shortener": "tinyurl" } if operation == "set": if value not in [__("on"), __("off")]: caller.respond(__("Usage: {CHARS}urls set <on|off>")) else: with self.channels: if value == __("on"): value = "on" elif value == __("off"): value = "off" self.channels[protocol.name][source.name]["status"] = value caller.respond( __("Title passing for %s turned %s.") % (source.name, __(value))) elif operation == "shortener": if value.lower() in self.shorteners: with self.channels: self.channels[protocol.name][source.name]["shortener"] \ = value.lower() caller.respond( __("URL shortener for %s set to %s.") % (source.name, value)) else: caller.respond(__("Unknown shortener: %s") % value) else: caller.respond(__("Unknown operation: '%s'.") % operation) def _respond_shorten(self, result, source, handler): """ Respond to a shorten command, after a successful Deferred """ if result is not None: return source.respond(result) return source.respond( __("Unable to shorten using handler %s. Poke the" "bot owner!") % handler) def _respond_shorten_fail(self, failure, source, handler): """ Respond to a shorten command, after a failed Deferred """ return source.respond( __("Error shortening url with handler %s: %s") % (handler, failure)) def shorten_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the shorten command """ args = parsed_args # Quick fix for new command handler signature if not isinstance(source, Channel): if len(args) == 0: caller.respond(__("Usage: {CHARS}shorten [url]")) return else: handler = "tinyurl" url = args[0] try: d = self.shorten_url(url, handler) d.addCallbacks(self._respond_shorten, self._respond_shorten_fail, callbackArgs=(source, handler), errbackArgs=(source, handler)) except Exception as e: self.logger.exception(_("Error fetching short URL.")) caller.respond(__("Error: %s") % e) return else: if protocol.name not in self.channels \ or source.name not in self.channels[protocol.name] \ or not len(self.channels[protocol.name][source.name]["last"]): caller.respond(__("Nobody's pasted a URL here yet!")) return handler = self.channels[protocol.name][source.name]["shortener"] if len(handler) == 0: with self.channels: self.channels[protocol.name][source.name]["shortener"]\ = "tinyurl" handler = "tinyurl" if handler not in self.shorteners: caller.respond( __("Shortener '%s' not found - please set a " "new one!") % handler) return url = self.channels[protocol.name][source.name]["last"] if len(args) > 0: url = args[0] try: d = self.shorten_url(url, handler) d.addCallbacks(self._respond_shorten, self._respond_shorten_fail, callbackArgs=(source, handler), errbackArgs=(source, handler)) except Exception as e: self.logger.exception(_("Error fetching short URL.")) caller.respond(__("Error: %s") % e) return def tinyurl(self, url): """ Shorten a URL with TinyURL. Don't use this directly. """ return urllib2.urlopen("http://tinyurl.com/api-create.php?url=" + urllib.quote_plus(url)).read() def parse_title(self, url, use_handler=True): """ Get and return the page title for a URL, or the title from a specialized handler, if one is registered. This function returns a tuple which may be one of these forms.. * (title, None) if the title was fetched by a specialized handler * (title, domain) if the title was parsed from the HTML * (None, None) if fetching the title was entirely unsuccessful. This occurs in each of the following cases.. * When a portscan is detected and stopped * When the page simply has no title * When there is an exception in the chain somewhere :param url: The URL to check :param use_handler: Whether to use specialized handlers :type url: str :type use_handler: bool :returns: A tuple containing the result :rtype: tuple(None, None), tuple(str, str), tuple(str, None) """ domain = "" self.logger.trace(_("Url: %s") % url) try: parsed = urlparse.urlparse(url) domain = parsed.hostname ip = socket.gethostbyname(domain) matches = all_matching_cidrs(ip, [ "10.0.0.0/8", "0.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8" ]) if matches: self.logger.warn(_("Prevented a portscan: %s") % url) return None, None if domain.startswith("www."): domain = domain[4:] if use_handler: for pattern in self.handlers: if fnmatch.fnmatch(domain, pattern): try: result = self.handlers[domain](url) if result: return to_unicode(result), None except Exception: self.logger.exception( _("Error running handler, " "parsing title normally.")) self.logger.trace(_("Parsed domain: %s") % domain) request = urllib2.Request(url) if domain in self.spoofing: self.logger.debug(_("Custom spoofing for this domain found.")) user_agent = self.spoofing[domain] if user_agent: self.logger.debug( _("Spoofing user-agent: %s") % user_agent) request.add_header("User-agent", user_agent) else: self.logger.debug(_("Not spoofing user-agent.")) else: self.logger.debug(_("Spoofing Firefox as usual.")) request.add_header( 'User-agent', 'Mozilla/5.0 (X11; U; Linux ' 'i686; en-US; rv:1.9.0.1) ' 'Gecko/2008071615 Fedora/3.0.' '1-1.fc9-1.fc9 Firefox/3.0.1') # Deal with Accept-Language language_value = None language = self.config.get("accept_language", {}) language_domains = language.get("domains", {}) if domain in language_domains: language_value = language_domains[domain] elif domain.lower() in language_domains: language_value = language_domains[domain.lower()] elif "default" in language: language_value = language["default"] if language_value is not None: request.add_header("Accept-Language", language_value) response = urllib2.urlopen(request) self.logger.trace(_("Info: %s") % response.info()) headers = response.info().headers new_url = response.geturl() _domain = domain parsed = urlparse.urlparse(new_url) domain = parsed.hostname if _domain != domain: self.logger.info(_("URL: %s") % new_url) self.logger.info(_("Domain: %s") % domain) if self.check_blacklist(new_url): self.logger.debug(_("Not parsing, URL is blacklisted.")) return ip = socket.gethostbyname(domain) matches = all_matching_cidrs(ip, [ "10.0.0.0/8", "0.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8" ]) if matches: self.logger.warn(_("Prevented a portscan: %s") % new_url) return None, None if domain.startswith("www."): domain = domain[4:] if domain in self.handlers and use_handler: try: result = self.handlers[domain](new_url) if result: return to_unicode(result), None except Exception: self.logger.exception( _("Error running handler," " parsing title normally.")) headers_dict = {} for x in headers: k, v = x.split(": ", 1) headers_dict[k.lower()] = v.strip("\r\n") status_code = response.getcode() if status_code in [301, 302, 303, 307, 308]: return self.parse_title(headers["location"]) ct = headers_dict["content-type"] if ";" in ct: ct = ct.split(";")[0] self.logger.trace(_("Content-type: %s") % repr(ct)) if ct not in self.content_types: self.logger.debug(_("Content-type is not allowed.")) return None, None page = response.read() soup = BeautifulSoup(page) if soup.title and soup.title.string: title = soup.title.string.strip() title = re.sub("\s+", " ", title) title = to_unicode(title) domain = to_unicode(domain) return title, domain else: return None, None except Exception as e: if not str(e).lower() == "not viewing html": self.logger.exception(_("Error parsing title.")) return str(e), domain return None, None def _shorten(self, txn, url, handler): """ Shorten a URL, checking the database in case it's already been done. This is a database interaction and uses Deferreds. """ txn.execute("SELECT * FROM urls WHERE url=? AND shortener=?", (url, handler.lower())) r = txn.fetchone() self.logger.trace(_("Result (SQL): %s") % repr(r)) if r is not None: return r[2] if handler in self.shorteners: result = self.shorteners[handler](url) txn.execute("INSERT INTO urls VALUES (?, ?, ?)", (url, handler.lower(), result)) return result return None def shorten_url(self, url, handler): """ Shorten a URL using the specified handler. This returns a Deferred. :param url: The URL to shorten :param handler: The name of the handler to shorten with :type url: str :type handler: str :returns: Deferred which will fire with the result or None :rtype: Deferred """ self.logger.trace(_("URL: %s") % url) self.logger.trace(_("Handler: %s") % handler) return self.shortened.runInteraction(self._shorten, url, handler) def add_handler(self, domain, handler): """ API method to add a specialized URL handler. This will fail if there's already a handler there for that domain. :param domain: The domain to handle, without the 'www.'. :param handler: The callable handler :type domain: str :type handler: callable :returns: Whether the handler was registered :rtype: bool """ if domain.startswith("www."): raise ValueError(_("Domain should not start with 'www.'")) if domain not in self.handlers: self.logger.trace( _("Handler registered for '%s': %s") % (domain, handler)) self.handlers[domain] = handler return True return False def add_shortener(self, name, handler): """ API method to add a URL shortener. This is the same as `add_handler`, but for URL shortening. """ if name not in self.shorteners: self.logger.trace( _("Shortener '%s' registered: %s") % (name, handler)) self.shorteners[name] = handler return True return False def remove_handler(self, domain): if domain.startswith("www."): raise ValueError(_("Domain should not start with 'www.'")) if domain in self.handlers: del self.handlers[domain] return True return False def remove_shortener(self, name): if name in self.shorteners: del self.shorteners[name] return True return False
class TwilioPlugin(plugin.PluginObject): config = None data = None commands = None events = None plugins = None storage = None twilio = None @property def web(self): """ :rtype: WebPlugin """ return self.plugins.get_plugin("Web") def setup(self): self.logger.trace("Entered setup method.") self.commands = CommandManager() self.events = EventManager() self.plugins = PluginManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/twilio.yml") except Exception: self.logger.exception("Error loading configuration!") return self._disable_self() else: if not self.config.exists: self.logger.error("Unable to find config/plugins/twilio.yml") return self._disable_self() try: self.data = self.storage.get_file(self, "data", JSON, "plugins/twilio/contacts.json") except Exception: self.logger.exception("Error loading data!") self.logger.error("This data file is required. Shutting down...") return self._disable_self() self._load() self.config.add_callback(self._load) self.events.add_callback("Web/ServerStartedEvent", self, self.add_routes, 0) self.commands.register_command("sms", self.sms_command, self, "twilio.sms") self.commands.register_command("mms", self.mms_command, self, "twilio.mms") self.commands.register_command("tw", self.tw_command, self, "twilio.tw") def _load(self): self.twilio = TwilioRestClient( self.config["identification"]["sid"], self.config["identification"]["token"] ) account = self.twilio.accounts.get( self.config["identification"]["sid"] ) self.logger.info("Logged in as [%s] %s." % (account.type, account.friendly_name)) def add_routes(self, event): self.web.add_handler( r"/twilio/%s" % self.config["security"]["api_key"], "plugins.twilio.route.Route" ) self.logger.info("Registered route: /twilio/<apikey>") def tw_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split(" ") if len(args) < 1: caller.respond("Usage: {CHARS}tw <contact/reload> [<set/del/get> " "<[name] [number]>]") return action = args[0].lower() if action == "reload": self.config.reload() self.data.reload() source.respond("Files reloaded.") return if action != "contact": caller.respond("Unknown action: %s" % action) return if len(args) < 3: caller.respond("Usage: {CHARS}tw contact <set/del/get> " "<[name] [number]>") return operation = args[1].lower() target = args[2] for case, default in switch(operation): # I was bored, okay? if case("set"): if len(args) < 4: caller.respond("Usage: {CHARS}tw contact set <name>" " <number>") break try: self.save_contact(name=target, number=args[3]) except Exception as e: source.respond("Error saving contact: %s" % e) else: source.respond("Contact '%s' saved." % target) break if case("del"): try: if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if not c: source.respond("No contact found for '%s'" % target) return r = self.del_contact(contac_=c) if not r: source.respond("No contact found for '%s'" % target) else: source.respond("Contact for '%s' deleted." % target) except Exception as e: source.respond("Error deleting contact: %s" % e) break if case("get"): try: if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if not c: source.respond("No contact found for '%s'" % target) else: source.respond("CONTACT | %s -> %s" % (c.name, c.number)) except Exception as e: source.respond("Error loading contact: %s" % e) break if default: caller.respond("Unknown operation: %s" % operation) def sms_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split(" ") if len(args) < 2: caller.respond("Usage: {CHARS}sms <name/number> <message>") return sent = self.config["formatting"].get("sms-sent", "SMS | {TO} | Message sent.") error = self.config["formatting"].get("sms-error", "SMS | ERROR | {ERROR}") target = args[0] message = "<%s> %s" % (caller.nickname, " ".join(args[1:])) if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if c is None: name = target if not target.startswith("+"): source.respond( error.replace( "{ERROR}", "Numbers must start with a '+'" ) ) return sent = sent.replace("{TO}", name) try: self.send_sms(name, message) except TwilioRestException as e: source.respond( error.replace( "{ERROR}", str(e.msg) ) ) except Exception as e: source.respond( error.replace( "{ERROR}", str(e) ) ) else: source.respond(sent) else: name = c.name sent = sent.replace("{TO}", name) try: self.send_sms(c, message) except TwilioRestException as e: if "has not been enabled for MMS" in e.msg: e.msg = "Twilio number has not been enabled for MMS." elif "Please use only valid http and https urls" in e.msg: e.msg = "Media URL is invalid - please use a link to a " \ "media file." source.respond( error.replace( "{ERROR}", str(e.msg) ) ) except Exception as e: source.respond( error.replace( "{ERROR}", str(e).replace( "\r", "" ).replace( "\n", " " ).replace( " ", " " ) ) ) self.logger.exception("Error sending SMS") else: source.respond(sent) def mms_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split(" ") if len(args) < 3: caller.respond("Usage: {CHARS}mms <name/number> <url> <message>") return sent = self.config["formatting"].get("mms-sent", "MMS | {TO} | Message sent.") error = self.config["formatting"].get("mms-error", "MMS | ERROR | {ERROR}") target = args[0] url = args[1] message = "<%s> %s" % (caller.nickname, " ".join(args[2:])) if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if c is None: name = target if not target.startswith("+"): source.respond( error.replace( "{ERROR}", "Numbers must start with a '+'" ) ) return sent = sent.replace("{TO}", name) try: self.send_sms(name, message, media_url=url) except TwilioRestException as e: if "has not been enabled for MMS" in e.msg: e.msg = "Twilio number has not been enabled for MMS." elif "Please use only valid http and https urls" in e.msg: e.msg = "Media URL is invalid - please use a link to a " \ "media file." source.respond( error.replace( "{ERROR}", str(e.msg) ) ) except Exception as e: source.respond( error.replace( "{ERROR}", str(e).replace( "\r", "" ).replace( "\n", " " ).replace( " ", " " ) ) ) self.logger.exception("Error sending MMS") else: source.respond(sent) else: name = c.name sent = sent.replace("{TO}", name) try: self.send_sms(c, message, media_url=url) except TwilioRestException as e: source.respond( error.replace( "{ERROR}", str(e.msg) ) ) except Exception as e: source.respond( error.replace( "{ERROR}", str(e) ) ) else: source.respond(sent) def do_targets(self, sender, message): sender = str(sender).strip() c = self.load_contact(number=sender) name = "default" if c is not None: name = c.name if name not in self.config["targetting"]: name = "default" if name not in self.config["targetting"]: self.logger.trace("No default target found.") return targets = self.config["targetting"][name] f_str = self.config["formatting"].get( "sms", "SMS | {FROM} | {MESSAGE}" ) from_ = name if c is not None: from_ = c.name elif from_ == "default": from_ = sender message = str(message).replace("\r", "") message = message.replace("\n", " ") f_str = f_str.replace("{FROM}", from_) f_str = f_str.replace("{MESSAGE}", message) self.logger.info(f_str) for target in targets: try: p_str = target["protocol"] p = self.factory_manager.get_protocol(p_str) if p is None: self.logger.warn("No such protocol: %s" % p_str) continue p.send_msg(target["target"], f_str, target_type=target["target-type"]) except Exception: self.logger.exception("Error relaying SMS message") continue def send_sms(self, target, message, media_url=None): if isinstance(target, Contact): target = target.number msg = self.twilio.messages.create( body=message, to=target, from_=self.config["identification"]["number"], media_url=media_url) return msg def load_contact(self, contac_=None, name=None, number=None): if contac_ is not None: return contac_ if name is not None: number = self.data.get(name, None) if number is not None: return Contact(number, name, self) if number is not None: for k in self.data.keys(): if number == self.data[k]: return Contact(number, k, self) return None def save_contact(self, contac_=None, name=None, number=None): if contac_ is not None: with self.data: self.data[contac_.name] = contac_.number return contac_ elif name is not None and number is not None: contac_ = Contact(number, name, self) with self.data: self.data[contac_.name] = contac_.number return contac_ raise ValueError("You need to give either a contact, or a name and " "number to save.") def del_contact(self, contac_=None, name=None, number=None): if contac_ is not None: with self.data: if contac_.name in self.data: del self.data[contac_.name] return True return False elif name is not None: with self.data: if name in self.data: del self.data[name] return True return False elif number is not None: for k in self.data.keys(): if number == self.data[k]: del self.data[k] return True return False raise ValueError("You need to give either a contact, name or " "number to delete.") pass # So the regions work in PyCharm
class BridgePlugin(plugin.PluginObject): """ Message bridging plugin object """ config = None events = None commands = None storage = None rules = {} @property def rules(self): """ The list of bridging rules """ return self.config["rules"] def setup(self): """ Called when the plugin is loaded. Performs initial setup. """ self.logger.trace(_("Entered setup method.")) self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/bridge.yml") except Exception: self.logger.exception(_("Error loading configuration!")) self.logger.error(_("Disabling..")) self._disable_self() return if not self.config.exists: self.logger.error(_("Unable to find config/plugins/bridge.yml")) self.logger.error(_("Disabling..")) self._disable_self() return self.commands = CommandManager() self.events = EventManager() # General self.events.add_callback("PreMessageReceived", self, self.handle_msg, 1000) self.events.add_callback("MessageSent", self, self.handle_msg_sent, 0) # self.events.add_callback("PreCommand", self, self.handle_command, # 1000) self.events.add_callback("ActionSent", self, self.handle_action_sent, 0) self.events.add_callback("UserDisconnected", self, self.handle_disconnect, 1000) # IRC self.events.add_callback("IRC/UserJoined", self, self.handle_irc_join, 1000) self.events.add_callback("IRC/UserParted", self, self.handle_irc_part, 1000) self.events.add_callback("IRC/UserKicked", self, self.handle_irc_kick, 1000) self.events.add_callback("IRC/UserQuit", self, self.handle_irc_quit, 1000) self.events.add_callback("IRC/CTCPQueryReceived", self, self.handle_irc_action, 1000) # Mumble self.events.add_callback("Mumble/UserRemove", self, self.handle_mumble_remove, 1000) self.events.add_callback("Mumble/UserJoined", self, self.handle_mumble_join, 1000) self.events.add_callback("Mumble/UserMoved", self, self.handle_mumble_move, 1000) def handle_irc_join(self, event=UserJoinedEvent): """ Event handler for IRC join events """ self.do_rules("", event.caller, event.user, event.channel, f_str=["general", "join"], tokens={"CHANNEL": event.channel.name}) def handle_irc_part(self, event=UserPartedEvent): """ Event handler for IRC part events """ self.do_rules("", event.caller, event.user, event.channel, f_str=["general", "part"], tokens={"CHANNEL": event.channel.name}) def handle_irc_kick(self, event=UserKickedEvent): """ Event handler for IRC kick events """ self.do_rules(event.reason, event.caller, event.user, event.channel, f_str=["general", "kick"], tokens={ "CHANNEL": event.channel.name, "KICKER": event.kicker.nickname }) def handle_irc_quit(self, event=UserQuitEvent): """ Event handler for IRC quit events """ self.do_rules(event.message, event.caller, event.user, event.user, f_str=["irc", "disconnect"], tokens={"USER": event.user.nickname}) def handle_irc_action(self, event=CTCPQueryEvent): """ Event handler for IRC CTCP query events """ if event.action == "ACTION": self.do_rules(event.data, event.caller, event.user, event.channel, f_str=["general", "action"]) def handle_disconnect(self, event=UserDisconnected): """ Event handler for general disconnect events """ self.do_rules("", event.caller, event.user, event.user, f_str=["general", "disconnect"], tokens={"USER": event.user.nickname}) def handle_mumble_join(self, event=UserJoined): """ Event handler for Mumble join events """ self.do_rules("", event.caller, event.user, event.user, f_str=["mumble", "connect"]) def handle_mumble_move(self, event=UserMoved): """ Event handler for Mumble move events """ # Moving /from/ the configured channel to another one self.do_rules("", event.caller, event.user, event.old_channel, f_str=["mumble", "moved-from"], tokens={"CHANNEL": event.channel.name}) # Moving /to/ the configured channel self.do_rules("", event.caller, event.user, event.channel, f_str=["mumble", "moved-to"], tokens={"CHANNEL": event.channel.name}) def handle_mumble_remove(self, event=UserRemove): """ Event handler for Mumble remove events """ self.do_rules(event.reason, event.caller, event.user, event.user, f_str=["mumble", "remove"], tokens={ "KICKER": event.kicker, "BANNED?": "banned" if event.ban else "kicked" }) def handle_msg(self, event=MessageReceived): """ Event handler for general message events """ self.do_rules(event.message, event.caller, event.source, event.target) def handle_msg_sent(self, event=MessageSent): """ Event handler for general message sent events """ self.do_rules(event.message, event.caller, event.caller.ourselves, event.target) def handle_action_sent(self, event=ActionSent): """ Event handler for general action sent events """ self.do_rules(event.message, event.caller, event.caller.ourselves, event.target, f_str=["general", "action"]) def handle_command(self, event=PreCommand): """ Event handler for general pre-command events """ if event.printable: self.do_rules(event.message, event.caller, event.source, event.target) def do_rules(self, msg, caller, source, target, from_user=True, to_user=True, f_str=None, tokens=None, use_event=False): """ Action the bridge ruleset based on input. :param msg: Message to relay :param caller: User that sent the message :param source: Protocol the message relates to :param target: User or Channel the message was sent to :param from_user: Whether to relay from a PM :param to_user: Whether to relay to a PM :param f_str: Definition of the formatting string to use. For example, ["general", "action"] or ["irc", "quit"] :param tokens: Dict of extra tokens to replace :param use_event: Whether to throw a MessageSent event :type msg: str :type caller: User :type source: Protocol :type target: User, Channel :type from_user: bool :type to_user: bool :type f_str: list :type tokens: dict :type use_event: bool """ if not caller: return if not source: return if not target: return if not tokens: tokens = {} if not f_str: f_str = ["general", "message"] c_name = caller.name.lower() # Protocol s_name = source.nickname # User if isinstance(target, Channel): t_name = target.name # Channel else: t_name = target.nickname for rule, data in self.rules.items(): self.logger.debug(_("Checking rule: %s - %s") % (rule, data)) from_ = data["from"] to_ = data["to"] if c_name != from_["protocol"].lower(): self.logger.trace(_("Protocol doesn't match.")) continue if not self.factory_manager.get_protocol(to_["protocol"]): self.logger.trace(_("Target protocol doesn't exist.")) continue if isinstance(target, User): # We ignore the source name since there can only ever be one # user: us. if not from_user: self.logger.trace( _("Function was called with relaying " "from users disabled.")) continue if from_["source-type"].lower() != "user": self.logger.trace(_("Target type isn't a user.")) continue elif isinstance(target, Channel): if from_["source-type"].lower() != "channel": self.logger.trace(_("Target type isn't a Channel.")) continue if from_["source"].lower() != "*" \ and from_["source"].lower() != t_name.lower(): self.logger.trace( _("Target name doesn't match the " "source.")) continue else: self.logger.trace(_("Target isn't a known type.")) continue if to_["target"] == "user" and not to_user: self.logger.trace( _("Function was called with relaying to " "users disabled.")) continue # If we get this far, we've matched the incoming rule. format_string = None formatting = data["formatting"] if f_str[0] in formatting: if f_str[1] in formatting[f_str[0]]: format_string = formatting[f_str[0]][f_str[1]] if not format_string: self.logger.trace( _("Not relaying message as the format " "string was empty or missing.")) continue else: sf_name = s_name tf_name = t_name if from_.get("obfuscate-names", False): sf_name = s_name[:-1] + "_" + s_name[-1] if "USER" in tokens: _u = tokens["USER"] tokens["USER"] = _u[:-1] + "_" + _u[-1] if to_.get("obfuscate-names", False): tf_name = t_name[:-1] + "_" + t_name[-1] if "TARGET" in tokens: _u = tokens["TARGET"] tokens["TARGET"] = _u[:-1] + "_" + _u[-1] if "disconnected" in format_string: pass for line in msg.strip("\r").split("\n"): format_string = formatting[f_str[0]][f_str[1]] for k, v in tokens.items(): format_string = format_string.replace("{%s}" % k, v) format_string = format_string.replace("{MESSAGE}", line) format_string = format_string.replace("{USER}", sf_name) format_string = format_string.replace("{TARGET}", tf_name) format_string = format_string.replace( "{PROTOCOL}", caller.name) prot = self.factory_manager.get_protocol(to_["protocol"]) prot.send_msg(to_["target"], format_string, target_type=to_["target-type"], use_event=use_event)
class DialectizerPlugin(plugin.PluginObject): """Dialectizer plugin object""" commands = None data = None events = None storage = None dialectizers = { "chef": Chef(), "fudd": Fudd(), "lower": Lower(), "off": Dialectizer(), "olde": Olde(), "reverse": Reverse(), "upper": Upper() } def setup(self): """The list of bridging rules""" self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() self.data = self.storage.get_file(self, "data", YAML, "plugins/dialectizer/settings.yml") self.events.add_callback("MessageSent", self, self.handle_msg_sent, 1) self.commands.register_command("dialectizer", self.dialectizer_command, self, "dialectizer.set", aliases=["dialectiser"]) def handle_msg_sent(self, event=MessageSent): """Handler for general message sent event""" if isinstance(event.target, User): return name = event.caller.name target = event.target.name with self.data: if name not in self.data: self.data[name] = {} if target not in self.data[name]: self.data[name][target] = "off" subber = self.dialectizers[self.data[name][target]] message = event.message message = subber.sub(message) event.message = message def dialectizer_command(self, protocol, caller, source, command, raw_args, parsed_args): """Handler for the dialectizer command""" args = raw_args.split() # Quick fix for new command handler signature if isinstance(source, User): caller.respond(__("This command only applies to channels.")) return if not len(args) > 0: caller.respond(__("Usage: {CHARS}dialectizer <dialectizer>")) caller.respond( __("Available dialectizers: %s") % ", ".join(self.dialectizers.keys())) return with self.data: if protocol.name not in self.data: self.data[protocol.name] = {} if source.name not in self.data[protocol.name]: self.data[protocol.name][source.name] = "off" setting = args[0].lower() if setting not in self.dialectizers: caller.respond(__("Unknown dialectizer: %s") % setting) return self.data[protocol.name][source.name] = setting caller.respond(__("Dialectizer set to '%s'") % setting)
class TwilioPlugin(plugin.PluginObject): config = None data = None commands = None events = None plugins = None storage = None twilio = None @property def web(self): """ :rtype: WebPlugin """ return self.plugins.get_plugin("Web") def setup(self): self.logger.trace("Entered setup method.") self.commands = CommandManager() self.events = EventManager() self.plugins = PluginManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/twilio.yml") except Exception: self.logger.exception("Error loading configuration!") return self._disable_self() else: if not self.config.exists: self.logger.error("Unable to find config/plugins/twilio.yml") return self._disable_self() try: self.data = self.storage.get_file(self, "data", JSON, "plugins/twilio/contacts.json") except Exception: self.logger.exception("Error loading data!") self.logger.error("This data file is required. Shutting down...") return self._disable_self() self._load() self.config.add_callback(self._load) self.events.add_callback("Web/ServerStartedEvent", self, self.add_routes, 0) self.commands.register_command("sms", self.sms_command, self, "twilio.sms") self.commands.register_command("mms", self.mms_command, self, "twilio.mms") self.commands.register_command("tw", self.tw_command, self, "twilio.tw") def _load(self): self.twilio = TwilioRestClient(self.config["identification"]["sid"], self.config["identification"]["token"]) account = self.twilio.accounts.get( self.config["identification"]["sid"]) self.logger.info("Logged in as [%s] %s." % (account.type, account.friendly_name)) def add_routes(self, event): self.web.add_handler( r"/twilio/%s" % self.config["security"]["api_key"], "plugins.twilio.route.Route") self.logger.info("Registered route: /twilio/<apikey>") def tw_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split(" ") if len(args) < 1: caller.respond("Usage: {CHARS}tw <contact/reload> [<set/del/get> " "<[name] [number]>]") return action = args[0].lower() if action == "reload": self.config.reload() self.data.reload() source.respond("Files reloaded.") return if action != "contact": caller.respond("Unknown action: %s" % action) return if len(args) < 3: caller.respond("Usage: {CHARS}tw contact <set/del/get> " "<[name] [number]>") return operation = args[1].lower() target = args[2] for case, default in switch(operation): # I was bored, okay? if case("set"): if len(args) < 4: caller.respond("Usage: {CHARS}tw contact set <name>" " <number>") break try: self.save_contact(name=target, number=args[3]) except Exception as e: source.respond("Error saving contact: %s" % e) else: source.respond("Contact '%s' saved." % target) break if case("del"): try: if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if not c: source.respond("No contact found for '%s'" % target) return r = self.del_contact(contac_=c) if not r: source.respond("No contact found for '%s'" % target) else: source.respond("Contact for '%s' deleted." % target) except Exception as e: source.respond("Error deleting contact: %s" % e) break if case("get"): try: if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if not c: source.respond("No contact found for '%s'" % target) else: source.respond("CONTACT | %s -> %s" % (c.name, c.number)) except Exception as e: source.respond("Error loading contact: %s" % e) break if default: caller.respond("Unknown operation: %s" % operation) def sms_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split(" ") if len(args) < 2: caller.respond("Usage: {CHARS}sms <name/number> <message>") return sent = self.config["formatting"].get("sms-sent", "SMS | {TO} | Message sent.") error = self.config["formatting"].get("sms-error", "SMS | ERROR | {ERROR}") target = args[0] message = "<%s> %s" % (caller.nickname, " ".join(args[1:])) if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if c is None: name = target if not target.startswith("+"): source.respond( error.replace("{ERROR}", "Numbers must start with a '+'")) return sent = sent.replace("{TO}", name) try: self.send_sms(name, message) except TwilioRestException as e: source.respond(error.replace("{ERROR}", str(e.msg))) except Exception as e: source.respond(error.replace("{ERROR}", str(e))) else: source.respond(sent) else: name = c.name sent = sent.replace("{TO}", name) try: self.send_sms(c, message) except TwilioRestException as e: if "has not been enabled for MMS" in e.msg: e.msg = "Twilio number has not been enabled for MMS." elif "Please use only valid http and https urls" in e.msg: e.msg = "Media URL is invalid - please use a link to a " \ "media file." source.respond(error.replace("{ERROR}", str(e.msg))) except Exception as e: source.respond( error.replace( "{ERROR}", str(e).replace("\r", "").replace("\n", " ").replace(" ", " "))) self.logger.exception("Error sending SMS") else: source.respond(sent) def mms_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split(" ") if len(args) < 3: caller.respond("Usage: {CHARS}mms <name/number> <url> <message>") return sent = self.config["formatting"].get("mms-sent", "MMS | {TO} | Message sent.") error = self.config["formatting"].get("mms-error", "MMS | ERROR | {ERROR}") target = args[0] url = args[1] message = "<%s> %s" % (caller.nickname, " ".join(args[2:])) if target.startswith("+"): c = self.load_contact(number=target) else: c = self.load_contact(name=target) if c is None: name = target if not target.startswith("+"): source.respond( error.replace("{ERROR}", "Numbers must start with a '+'")) return sent = sent.replace("{TO}", name) try: self.send_sms(name, message, media_url=url) except TwilioRestException as e: if "has not been enabled for MMS" in e.msg: e.msg = "Twilio number has not been enabled for MMS." elif "Please use only valid http and https urls" in e.msg: e.msg = "Media URL is invalid - please use a link to a " \ "media file." source.respond(error.replace("{ERROR}", str(e.msg))) except Exception as e: source.respond( error.replace( "{ERROR}", str(e).replace("\r", "").replace("\n", " ").replace(" ", " "))) self.logger.exception("Error sending MMS") else: source.respond(sent) else: name = c.name sent = sent.replace("{TO}", name) try: self.send_sms(c, message, media_url=url) except TwilioRestException as e: source.respond(error.replace("{ERROR}", str(e.msg))) except Exception as e: source.respond(error.replace("{ERROR}", str(e))) else: source.respond(sent) def do_targets(self, sender, message): sender = str(sender).strip() c = self.load_contact(number=sender) name = "default" if c is not None: name = c.name if name not in self.config["targetting"]: name = "default" if name not in self.config["targetting"]: self.logger.trace("No default target found.") return targets = self.config["targetting"][name] f_str = self.config["formatting"].get("sms", "SMS | {FROM} | {MESSAGE}") from_ = name if c is not None: from_ = c.name elif from_ == "default": from_ = sender message = str(message).replace("\r", "") message = message.replace("\n", " ") f_str = f_str.replace("{FROM}", from_) f_str = f_str.replace("{MESSAGE}", message) self.logger.info(f_str) for target in targets: try: p_str = target["protocol"] p = self.factory_manager.get_protocol(p_str) if p is None: self.logger.warn("No such protocol: %s" % p_str) continue p.send_msg(target["target"], f_str, target_type=target["target-type"]) except Exception: self.logger.exception("Error relaying SMS message") continue def send_sms(self, target, message, media_url=None): if isinstance(target, Contact): target = target.number msg = self.twilio.messages.create( body=message, to=target, from_=self.config["identification"]["number"], media_url=media_url) return msg def load_contact(self, contac_=None, name=None, number=None): if contac_ is not None: return contac_ if name is not None: number = self.data.get(name, None) if number is not None: return Contact(number, name, self) if number is not None: for k in self.data.keys(): if number == self.data[k]: return Contact(number, k, self) return None def save_contact(self, contac_=None, name=None, number=None): if contac_ is not None: with self.data: self.data[contac_.name] = contac_.number return contac_ elif name is not None and number is not None: contac_ = Contact(number, name, self) with self.data: self.data[contac_.name] = contac_.number return contac_ raise ValueError("You need to give either a contact, or a name and " "number to save.") def del_contact(self, contac_=None, name=None, number=None): if contac_ is not None: with self.data: if contac_.name in self.data: del self.data[contac_.name] return True return False elif name is not None: with self.data: if name in self.data: del self.data[name] return True return False elif number is not None: for k in self.data.keys(): if number == self.data[k]: del self.data[k] return True return False raise ValueError("You need to give either a contact, name or " "number to delete.") pass # So the regions work in PyCharm
class BlowfishPlugin(plugin.PluginObject): """ Blowfish-CBC support, as seen in Eggdrop and mIRC scripts """ config = None events = None storage = None def get_target(self, protocol, target): return self.config.get( # PEP! \o/ protocol, {}).get("targets", {}).get(target, None) def get_global(self, protocol): return self.config.get(protocol, {}).get("global", None) def setup(self): self.events = EventManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/blowfish.yml") except Exception: self.logger.exception("Error loading configuration!") self._disable_self() return if not self.config.exists: self.logger.error("Unable to find config/plugins/blowfish.yml") self._disable_self() return self.events.add_callback("PreMessageReceived", self, self.pre_message, 10001) self.events.add_callback("MessageSent", self, self.message_sent, 10001) self.events.add_callback("ActionReceived", self, self.message_sent, 10001) self.events.add_callback("ActionSent", self, self.message_sent, 10001) def pre_message(self, event=PreMessageReceived): if not event.caller: return target = event.target if not target: target = event.source key = self.get_target(event.caller.name, target) if not key: key = self.get_global(event.caller.name) if key: if event.message.startswith("+OK "): message = event.message[4:] try: result = self.decode(key, message) except Exception: self.logger.exception("Unable to decode message") event.cancelled = True else: event.message = result else: event.cancelled = True def message_sent(self, event=MessageSent): if not event.caller: return key = self.get_target(event.caller.name, event.target.name) if not key: key = self.get_global(event.caller.name) if key: message = event.message try: result = self.encode(key, message) except Exception: self.logger.exception("Unable to encode message") event.cancelled = True else: event.message = "+OK %s" % result def action_received(self, event=ActionReceived): if not event.caller: return target = event.target if not target: target = event.source key = self.get_target(event.caller.name, target) if not key: key = self.get_global(event.caller.name) if key: if event.message.startswith("+OK "): message = event.message[4:] try: result = self.decode(key, message) except Exception: self.logger.exception("Unable to decode message") event.cancelled = True else: event.message = result else: event.cancelled = True def action_sent(self, event=ActionSent): if not event.caller: return key = self.get_target(event.caller.name, event.target.name) if not key: key = self.get_global(event.caller.name) if key: message = event.message try: result = self.encode(key, message) except Exception: self.logger.exception("Unable to encode message") event.cancelled = True else: event.message = "+OK %s" % result def decode(self, key, text): binary = base64.b64decode(text) iv = binary[:8] encrypted = binary[8:] cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv) decrypted = cipher.decrypt(encrypted) return decrypted.rstrip("\0") def encode(self, key, text): iv = "%s" % "".join( [random.choice(string.printable) for x in range(8)]) cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv) length = len(text) text += "\0" * abs((length % 8) - 8) binary = cipher.encrypt(text) return base64.b64encode("%s%s" % (iv, binary))
class TriggersPlugin(plugin.PluginObject): commands = None events = None storage = None _config = None def setup(self): ### Grab important shit self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() ### Initial config load try: self._config = self.storage.get_file(self, "config", YAML, "plugins/triggers.yml") except Exception: self.logger.exception("Error loading configuration!") self.logger.error("Disabling...") self._disable_self() return if not self._config.exists: self.logger.error("Unable to find config/plugins/triggers.yml") self.logger.error("Disabling...") self._disable_self() return ### Register event handlers def _message_event_filter(event=MessageReceived): return isinstance(event.target, Channel) self.events.add_callback("MessageReceived", self, self.message_handler, 1, _message_event_filter) self.events.add_callback("ActionReceived", self, self.action_handler, 1, _message_event_filter) def reload(self): try: self._config.reload() except Exception: self.logger.exception("Error reloading configuration!") return False return True @property def _triggers(self): return self._config.get("triggers", {}) def message_handler(self, event=MessageReceived): self.event_handler( event.caller, event.source, event.target, event.message, event.type ) def action_handler(self, event=ActionReceived): self.event_handler( event.caller, event.source, event.target, event.message, "action" ) def event_handler(self, protocol, source, target, message, e_type): """ Event handler for general messages """ allowed = self.commands.perm_handler.check("triggers.trigger", source, target, protocol) if not allowed: return # TODO: Rewrite this when Matcher is finished # TODO: We check the types of half of these - do the rest global_triggers = self._triggers.get("global", []) proto_trigger_block = self._triggers.get("protocols", {}) proto_triggers = proto_trigger_block.get(protocol.name, {}) if not isinstance(proto_triggers, dict): self.logger.error( "Invalid triggers for protocol '%s'" % protocol.name ) return proto_triggers_global = proto_triggers.get("global", []) channel_triggers_block = proto_triggers.get("channels", {}) channel_triggers = [] _channel = None for _channel, _triggers in channel_triggers_block.iteritems(): if protocol.get_channel(_channel) == target: channel_triggers = _triggers break if not isinstance(channel_triggers, list): self.logger.error( "Invalid triggers for channel '%s' in protocol '%s'" % ( _channel, protocol.name ) ) return for trigger in itertools.chain(channel_triggers, proto_triggers_global, global_triggers): try: trigger_regex = trigger["trigger"] responses = trigger["response"] chance = trigger.get("chance", 100) flags = trigger.get("flags", "") trigger_types = trigger.get("trigger_types", {"message": True}) response_type = trigger.get("response_type", "message") if not trigger_types.get(e_type, False): continue if random.random() * 100 >= chance: continue response = random.choice(responses) response_type = response_type.lower() flags_parsed = 0 for flag in flags.lower(): if flag == "i": flags_parsed += re.I elif flag == "u": flags_parsed += re.U elif flag == "l": flags_parsed += re.L elif flag == "m": flags_parsed += re.M elif flag == "x": flags_parsed += re.X elif flag == "s": flags_parsed += re.S elif flag == "d": flags_parsed += re.DEBUG else: self.log.warning("Unknown regex flag '%s'" % flag) # TODO: Rate limiting # re caches compiled patterns internally, so we don't have to match = re.search(trigger_regex, message, flags_parsed) if match: # Hack to get around the fact that regex groups start at # one, but formatting args start at 0 format_args = [""] format_args.extend(match.groups()) format_kwargs = {} for k, v in match.groupdict().iteritems(): format_kwargs[k] = v format_kwargs["channel"] = _channel format_kwargs["source"] = source format_kwargs["target"] = target format_kwargs["message"] = message format_kwargs["protocol"] = protocol.name response_formatted = response.format( *format_args, **format_kwargs ) if response_type == "message": protocol.send_msg(target, response_formatted) elif response_type == "action": protocol.send_action(target, response_formatted) elif response_type == "notice": if hasattr(protocol, "send_notice"): protocol.send_notice(target, response_formatted) else: self.logger.error( "Cannot respond with notice on protocol: '%s'" % protocol.name ) else: self.logger.error( "Invalid response_type '%s'" % response_type ) except Exception: self.logger.exception( "Invalid trigger for channel '%s' in protocol '%s'" % ( _channel, protocol.name ) )
class AuthPlugin(plugin.PluginObject): """ Auth plugin. In charge of logins and permissions. """ config = None passwords = None permissions = None blacklist = None commands = None events = None storage = None auth_h = None perms_h = None def setup(self): """ Called when the plugin is loaded. Performs initial setup. """ reload(auth_handler) reload(permissions_handler) self.logger.trace(_("Entered setup method.")) self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/auth.yml") except Exception: self.logger.exception(_("Error loading configuration!")) self.logger.error(_("Disabling..")) self._disable_self() return if not self.config.exists: self.logger.error(_("Unable to find config/plugins/auth.yml")) self.logger.error(_("Disabling..")) self._disable_self() return self.commands = CommandManager() self.events = EventManager() if self.config["use-permissions"]: try: self.permissions = self.storage.get_file(self, "data", YAML, "plugins/auth/" # PEP "permissions.yml") except Exception: self.logger.exception(_("Unable to load permissions. They " "will be unavailable!")) else: self.perms_h = permissions_handler.permissionsHandler( self, self.permissions) result = self.commands.set_permissions_handler(self.perms_h) if not result: self.logger.warn(_("Unable to set permissions handler!")) if self.config["use-auth"]: try: self.passwords = self.storage.get_file(self, "data", YAML, "plugins/auth/" # PEP! "passwords.yml") self.blacklist = self.storage.get_file(self, "data", YAML, "plugins/auth/" # PEP! "blacklist.yml") except Exception: self.logger.exception(_("Unable to load user accounts. They " "will be unavailable!")) else: self.auth_h = auth_handler.authHandler(self, self.passwords, self.blacklist) result = self.commands.set_auth_handler(self.auth_h) if not result: self.logger.warn(_("Unable to set auth handler!")) self.logger.debug(_("Registering commands.")) self.commands.register_command("login", self.login_command, self, "auth.login", default=True) self.commands.register_command("logout", self.logout_command, self, "auth.login", default=True) self.commands.register_command("register", self.register_command, self, "auth.register", default=True) self.commands.register_command("passwd", self.passwd_command, self, "auth.passwd", default=True) self.events.add_callback("PreCommand", self, self.pre_command, 10000) def pre_command(self, event=PreCommand): """ Pre-command hook to remove passwords from the log output. """ self.logger.trace(_("Command: %s") % event.command) if event.command.lower() in ["login", "register"]: if len(event.args) >= 2: split_ = event.printable.split("%s " % event.command) second_split = split_[1].split() second_split[1] = _("[REDACTED]") split_[1] = " ".join(second_split) donestr = "%s " % event.command done = donestr.join(split_) event.printable = done elif event.command.lower() == "passwd": split_ = event.printable.split("%s " % event.command) second_split = split_[1].split() dsplit = [] for x in second_split: dsplit.append(_("[REDACTED]")) split_[1] = " ".join(dsplit) donestr = "%s " % event.command done = donestr.join(split_) event.printable = done def login_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the login command - for logging users in. """ args = raw_args.split() # Quick fix for new command handler signature if len(args) < 2: caller.respond(__("Usage: {CHARS}login <username> <password>")) else: if self.auth_h.authorized(caller, source, protocol): caller.respond(__("You're already logged in. " "Try logging out first!")) return username = args[0] password = args[1] result = self.auth_h.login(caller, protocol, username, password) if not result: self.logger.warn(_("%s failed to login as %s") % (caller.nickname, username)) caller.respond(__("Invalid username or password!")) else: self.logger.info(_("%s logged in as %s") % (caller.nickname, username)) caller.respond(__("You are now logged in as %s.") % username) def logout_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the logout command - for logging users out. """ if self.auth_h.authorized(caller, source, protocol): self.auth_h.logout(caller, protocol) caller.respond(__("You have been logged out successfully.")) else: caller.respond(__("You're not logged in.")) def register_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the register command - for creating new user accounts. """ args = raw_args.split() # Quick fix for new command handler signature if len(args) < 2: caller.respond(__("Usage: {CHARS}register <username> <password>")) return username = args[0] password = args[1] if isinstance(source, Channel): source.respond(__("You can't create an account in a channel.")) caller.respond(__("Don't use this command in a channel!")) caller.respond(__("You should only use it in a private message.")) caller.respond(__("For your security, the password you used has " "been blacklisted.")) self.auth_h.blacklist_password(password, username) return if self.auth_h.user_exists(username): caller.respond(__("That username already exists!")) return if self.auth_h.password_backlisted(password, username): caller.respond(__("That password has been blacklisted. " "Try another!")) return if self.auth_h.create_user(username, password): caller.respond(__("Your account has been created and you will now " "be logged in. Thanks for registering!")) self.perms_h.create_user(username) self.login_command(caller, source, [username, password], protocol, raw_args, parsed_args) else: caller.respond(__("Something went wrong when creating your " "account! You should ask the bot operators " "about this.")) def passwd_command(self, protocol, caller, source, command, raw_args, parsed_args): """ Command handler for the passwd command - for changing passwords. """ args = raw_args.split() # Quick fix for new command handler signature if len(args) < 2: caller.respond(__("Usage: {CHARS}passwd <old password> " "<new password>")) return if not self.auth_h.authorized(caller, source, protocol): caller.respond(__("You must be logged in to change your " "password.")) return username = caller.auth_name old = args[0] new = args[1] if self.auth_h.password_backlisted(new, username): caller.respond(__("That password has been blacklisted. Try " "another!")) return if self.auth_h.change_password(username, old, new): caller.respond(__("Your password has been changed successfully.")) else: caller.respond(__("Old password incorrect - please try again!")) self.logger.warn(_("User %s failed to change the password for %s") % (caller, username)) def get_auth_handler(self): """ API function for getting the auth handler. This will return None if no handler is registered. """ if self.config["use-auth"]: return self.auth_h return None def get_permissions_handler(self): """ API function for getting the permissions handler. This will return None if no handler is registered. """ if self.config["use-permissions"]: return self.perms_h return None def deactivate(self): """ Called when the plugin is deactivated. Does nothing right now. """ if self.config["use-auth"]: if isinstance( self.commands.auth_handler, auth_handler.authHandler ): self.commands.auth_handler = None if self.config["use-permissions"]: if isinstance( self.commands.perm_handler, permissions_handler.permissionsHandler ): self.commands.perm_handler = None
class ReplPlugin(plugin.PluginObject): commands = None events = None storage = None config = None proto = None channel = None formatting = None def __init__(self): self.protocol_events = { "general": [ # This is basically just *args. ["MessageReceived", self, self.message_received, 0] ], "inter": [ ["Inter/PlayerConnected", self, self.inter_player_connected, 0], ["Inter/PlayerDisconnected", self, self.inter_player_disconnected, 0], ["Inter/ServerConnected", self, self.inter_server_connected, 0], ["Inter/ServerDisconnected", self, self.inter_server_disconnected, 0] ] } super(ReplPlugin, self).__init__() def setup(self): self.logger.trace("Entered setup method.") self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/inter.yml") except Exception: self.logger.exception("Error loading configuration!") self.logger.error(_("Disabling..")) self._disable_self() return if not self.config.exists: self.logger.error("Unable to find config/plugins/inter.yml") self.logger.error(_("Disabling..")) self._disable_self() return self.config.add_callback(self.reload) self.commands.register_command("players", self.players_command, self, "inter.players", default=True) if not reactor.running: self.events.add_callback( "ReactorStarted", self, self.first_load, 0 ) else: self.first_load() def first_load(self, _=None): if not self.reload(): self.logger.error(_("Disabling..")) self._disable_self() def reload(self): self.events.remove_callbacks_for_plugin(self.info.name) proto = self.factory_manager.get_protocol(self.config["protocol"]) if proto is None: self.logger.error(_("Unknown protocol: %s") % self.config["protocol"]) return False if proto.TYPE == "inter": self.logger.error(_("You cannot relay between two Inter " "protocols!")) return False self.proto = proto self.channel = self.config["channel"] self.formatting = self.config["formatting"] for event in self.protocol_events["general"]: self.events.add_callback(*event) for event in self.protocol_events["inter"]: self.events.add_callback(*event) if proto.TYPE in self.protocol_events: for event in self.protocol_events[proto.TYPE]: self.events.add_callback(*event) return True def get_inters(self): inters = {} for key in self.factory_manager.factories.keys(): if self.factory_manager.get_protocol(key).TYPE == "inter": inters[key] = self.factory_manager.get_protocol(key) return inters def players_command(self, protocol, caller, source, command, raw_args, args): if protocol.TYPE == "inter": caller.respond("This command cannot be used via Inter.") return inters = self.get_inters() if len(inters) < 1: caller.respond("No Inter protocols were found.") elif len(inters) == 1: servers = inters[inters.keys()[0]].inter_servers for key in servers.keys(): formatting = self.formatting["player"]["list"] done = formatting["message"] _done = [] for x in servers[key]: _done.append(str(x)) if len(_done): players = formatting["join"].join(_done) else: players = "No players online." done = done.replace("{SERVER}", key) done = done.replace("{PLAYERS}", players) source.respond(done) else: if len(args) < 1: caller.respond("Usage: {CHARS}%s <inter server>") caller.respond("Servers: %s" % ", ".join(inters.keys())) return srv = args[1] if srv not in inters: caller.respond("Unknown inter server: %s" % srv) caller.respond("Servers: %s" % ", ".join(inters.keys())) return servers = inters[srv].inter_servers for key in servers.keys(): formatting = self.formatting["player"]["list"] done = formatting["message"] _done = [] for x in servers[key]: _done.append(str(x)) if len(_done): players = formatting["join"].join(_done) else: players = "No players online." done = done.replace("{SERVER}", key) done = done.replace("{PLAYERS}", players) source.respond(done) def message_received(self, event=MessageReceived): caller = event.caller user = event.source message = event.message target = event.target if target is None: return if caller is None: return if isinstance(caller, Protocol): if caller.TYPE == "inter": f_str = self.formatting["player"]["message"] f_str = f_str.replace("{SERVER}", user.server) f_str = f_str.replace("{USER}", str(user)) f_str = f_str.replace("{MESSAGE}", message) self.proto.send_msg(self.channel, f_str) else: if caller.name == self.proto.name: if target.name.lower() == self.channel.lower(): inters = self.get_inters() for proto in inters.values(): proto.send_msg_other(user, message) def inter_server_connected(self, event=InterServerConnected): f_str = self.formatting["server"]["connected"] f_str = f_str.replace("{SERVER}", event.name) self.proto.send_msg(self.channel, f_str) def inter_server_disconnected(self, event=InterServerDisonnected): f_str = self.formatting["server"]["disconnected"] f_str = f_str.replace("{SERVER}", event.name) self.proto.send_msg(self.channel, f_str) def inter_player_connected(self, event=InterPlayerConnected): f_str = self.formatting["player"]["connected"] f_str = f_str.replace("{SERVER}", event.user.server) f_str = f_str.replace("{USER}", str(event.user)) self.proto.send_msg(self.channel, f_str) def inter_player_disconnected(self, event=InterPlayerDisonnected): f_str = self.formatting["player"]["disconnected"] f_str = f_str.replace("{SERVER}", event.user.server) f_str = f_str.replace("{USER}", str(event.user)) self.proto.send_msg(self.channel, f_str)
class LastseenPlugin(plugin.PluginObject): commands = None events = None storage = None data = None # SQLite for a change def setup(self): self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() self.data = self.storage.get_file( self, "data", DBAPI, "sqlite3:data/plugins/lastseen/users.sqlite", "data/plugins/lastseen/users.sqlite", check_same_thread=False ) self.data.runQuery("CREATE TABLE IF NOT EXISTS users (" "user TEXT, " "protocol TEXT, " "at INTEGER)") self.commands.register_command("seen", self.seen_command, self, "seen.seen", default=True) # General events self.events.add_callback("PreMessageReceived", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_source_caller]) self.events.add_callback("PreCommand", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_source_caller]) self.events.add_callback("NameChanged", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("UserDisconnected", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) # Mumble events self.events.add_callback("Mumble/UserRemove", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserJoined", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserMoved", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserSelfMuteToggle", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserSelfDeafToggle", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) self.events.add_callback("Mumble/UserRecordingToggle", self, self.event_handler, 0, cancelled=True, extra_args=[self.event_user_caller]) def _get_user_txn(self, txn, user, protocol): user = user.lower() txn.execute("SELECT * FROM users WHERE user=? AND protocol=?", (user, protocol)) r = txn.fetchone() return r def _get_user_callback(self, result, user, protocol, source): if result is None: source.respond("User '%s' not found." % user) else: then = math.floor(result[2]) now = math.floor(time.time()) seconds = now - then m, s = divmod(seconds, 60) h, m = divmod(m, 60) d, h = divmod(h, 24) s = int(s) m = int(m) h = int(h) d = int(d) if (s + m + h + d) == 0: source.respond("'%s' was seen just now!" % user) else: constructed = "'%s' was seen" % user to_append = [] if d > 0: to_append.append("%s days" % d) if h > 0: to_append.append("%s hours" % h) if m > 0: to_append.append("%s minutes" % m) if s > 0: to_append.append("%s seconds" % s) length = len(to_append) i = 1 for x in to_append: if length - i == 0: if i != 1: constructed += " and %s" % x i += 1 continue if i != 1: constructed += ", %s" % x else: constructed += " %s" % x i += 1 constructed += " ago." source.respond(constructed) def _get_user_callback_fail(self, failure, user, protocol, source): source.respond("Error while finding user %s: %s" % (user, failure)) def get_user(self, user, protocol): return self.data.runInteraction(self._get_user_txn, user, protocol) def _insert_or_update_user(self, txn, user, protocol): user = user.lower() txn.execute("SELECT * FROM users WHERE user=? AND protocol=?", (user, protocol)) r = txn.fetchone() now = time.time() if r is None: txn.execute( "INSERT INTO users VALUES (?, ?, ?)", (user, protocol, now) ) return False else: txn.execute( "UPDATE users SET at=? WHERE user=? AND protocol=?", (now, user, protocol) ) return True def insert_or_update_user(self, user, protocol): self.data.runInteraction(self._insert_or_update_user, user, protocol) def seen_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if not args: caller.respond("Usage: {CHARS}seen <username>") else: user = "******".join(args) if user.lower() == protocol.ourselves.nickname.lower(): source.respond("I'm right here, smartass.") return if user.lower() == caller.nickname.lower(): source.respond("Having a bit of an out-of-body experience, " "%s?" % caller.nickname) return d = self.get_user(user, protocol.name) d.addCallbacks(self._get_user_callback, self._get_user_callback_fail, callbackArgs=(user.lower(), protocol.name, source), errbackArgs=(user.lower(), protocol.name, source)) def event_handler(self, event, handler): """ This is a generic function so that other plugins can catch events and cause a user's last seen value to update. The handler should return (username, protocol name) as a tuple, or a list of tuples if it needs to do more than one update. """ data = handler(event) if not isinstance(data, list): data = [data] for element in data: user, proto = element self.insert_or_update_user(user, proto) def event_source_caller(self, event): user = event.source.nickname proto = event.caller.name return user, proto def event_user_caller(self, event): user = event.user.nickname proto = event.caller.name return user, proto
class BlowfishPlugin(plugin.PluginObject): """ Blowfish-CBC support, as seen in Eggdrop and mIRC scripts """ config = None events = None storage = None def get_target(self, protocol, target): return self.config.get( # PEP! \o/ protocol, {} ).get( "targets", {} ).get( target, None ) def get_global(self, protocol): return self.config.get(protocol, {}).get("global", None) def setup(self): self.events = EventManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/blowfish.yml") except Exception: self.logger.exception("Error loading configuration!") self._disable_self() return if not self.config.exists: self.logger.error("Unable to find config/plugins/blowfish.yml") self._disable_self() return self.events.add_callback( "PreMessageReceived", self, self.pre_message, 10001 ) self.events.add_callback( "MessageSent", self, self.message_sent, 10001 ) self.events.add_callback( "ActionReceived", self, self.message_sent, 10001 ) self.events.add_callback( "ActionSent", self, self.message_sent, 10001 ) def pre_message(self, event=PreMessageReceived): if not event.caller: return target = event.target if not target: target = event.source key = self.get_target(event.caller.name, target) if not key: key = self.get_global(event.caller.name) if key: if event.message.startswith("+OK "): message = event.message[4:] try: result = self.decode(key, message) except Exception: self.logger.exception("Unable to decode message") event.cancelled = True else: event.message = result else: event.cancelled = True def message_sent(self, event=MessageSent): if not event.caller: return key = self.get_target(event.caller.name, event.target.name) if not key: key = self.get_global(event.caller.name) if key: message = event.message try: result = self.encode(key, message) except Exception: self.logger.exception("Unable to encode message") event.cancelled = True else: event.message = "+OK %s" % result def action_received(self, event=ActionReceived): if not event.caller: return target = event.target if not target: target = event.source key = self.get_target(event.caller.name, target) if not key: key = self.get_global(event.caller.name) if key: if event.message.startswith("+OK "): message = event.message[4:] try: result = self.decode(key, message) except Exception: self.logger.exception("Unable to decode message") event.cancelled = True else: event.message = result else: event.cancelled = True def action_sent(self, event=ActionSent): if not event.caller: return key = self.get_target(event.caller.name, event.target.name) if not key: key = self.get_global(event.caller.name) if key: message = event.message try: result = self.encode(key, message) except Exception: self.logger.exception("Unable to encode message") event.cancelled = True else: event.message = "+OK %s" % result def decode(self, key, text): binary = base64.b64decode(text) iv = binary[:8] encrypted = binary[8:] cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv) decrypted = cipher.decrypt(encrypted) return decrypted.rstrip("\0") def encode(self, key, text): iv = "%s" % "".join( [random.choice( string.printable ) for x in range(8)] ) cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv) length = len(text) text += "\0" * abs((length % 8) - 8) binary = cipher.encrypt(text) return base64.b64encode("%s%s" % (iv, binary))
class DrunkPlugin(plugin.PluginObject): commands = None config = None storage = None def setup(self): ### Grab important shit self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() ### Initial config load try: self.config = self.storage.get_file(self, "config", YAML, "plugins/drunkoctopus.yml") except Exception: self.logger.exception("Error loading configuration!") self.logger.error("Disabling...") self._disable_self() return if not self.config.exists: self.logger.error("Unable to find config/plugins/drunkoctopus.yml") self.logger.error("Disabling...") self._disable_self() return ### Create vars and stuff self._sobering_call = None self._drunktalk = DrunkTalk() ### Load options from config self._load() self.config.add_callback(self._load) ### Register events and commands self.events.add_callback("MessageSent", self, self.outgoing_message_handler, 1) self.commands.register_command("drunkenness", self.drunkenness_command, self, "drunkoctopus.drunkenness", default=True) self.commands.register_command("drink", self.drink_command, self, "drunkoctopus.drink") def reload(self): try: self.config.reload() except Exception: self.logger.exception("Error reloading configuration!") return False return True def _load(self): self._drunktalk.drunkenness = self.config["drunkenness"] self._cooldown_enabled = self.config["cooldown"]["enabled"] self._cooldown_time = self.config["cooldown"]["time"] self._cooldown_amount = self.config["cooldown"]["amount"] self._drinks = self.config["drinks"] # Sort out the sobering deferred as necessary if self._cooldown_enabled: if self._sobering_call is None: self.logger.trace("Starting sobering call due to config " "change") self._sobering_call = reactor.callLater( self._cooldown_time, self._sober_up) else: if self._sobering_call is not None: self.logger.trace("Cancelling sobering call due to config " "change") self._sobering_call.cancel() def _sober_up(self): self.logger.trace("Sobering up") drunk = self._drunktalk.drunkenness drunk -= self._cooldown_amount if drunk < 0: drunk = 0 self._drunktalk.drunkenness = drunk if self._cooldown_enabled: reactor.callLater(self._cooldown_time, self._sober_up) def drunkenness_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if len(args) == 0: caller.respond("Drunkenness level: %s" % self._drunktalk.drunkenness) return elif len(args) == 1: try: new_drunk = int(args[0]) self._drunktalk.drunkenness = new_drunk caller.respond("New drunkenness level: %s" % self._drunktalk.drunkenness) return except: caller.respond("Invalid drunkenness level (use without " "arguments for usage)") else: caller.respond("Usage: {CHARS}drunkenness [percent level]") def drink_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if len(args) == 0: caller.respond("Usage: {CHARS}drink <type of drink>") return drink = " ".join(args) drinkl = drink.lower() if drinkl in self._drinks: protocol.send_action(source, "drinks {}".format(drink)) self._drunktalk.drunkenness += self._drinks[drinkl] else: caller.respond("I don't have any of that.") def outgoing_message_handler(self, event): """ :type event: MessageSent """ self.logger.trace("RECEIVED %s EVENT: %s" % (event.type, event.message)) event.message = self._drunktalk.drunk_typing(event.message)
class DialectizerPlugin(plugin.PluginObject): """Dialectizer plugin object""" commands = None data = None events = None storage = None dialectizers = {"chef": Chef(), "fudd": Fudd(), "lower": Lower(), "off": Dialectizer(), "olde": Olde(), "reverse": Reverse(), "upper": Upper()} def setup(self): """The list of bridging rules""" self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() self.data = self.storage.get_file(self, "data", YAML, "plugins/dialectizer/settings.yml") self.events.add_callback("MessageSent", self, self.handle_msg_sent, 1) self.commands.register_command("dialectizer", self.dialectizer_command, self, "dialectizer.set", aliases=["dialectiser"]) def handle_msg_sent(self, event=MessageSent): """Handler for general message sent event""" if isinstance(event.target, User): return name = event.caller.name target = event.target.name with self.data: if name not in self.data: self.data[name] = {} if target not in self.data[name]: self.data[name][target] = "off" subber = self.dialectizers[self.data[name][target]] message = event.message message = subber.sub(message) event.message = message def dialectizer_command(self, protocol, caller, source, command, raw_args, parsed_args): """Handler for the dialectizer command""" args = raw_args.split() # Quick fix for new command handler signature if isinstance(source, User): caller.respond(__("This command only applies to channels.")) return if not len(args) > 0: caller.respond(__("Usage: {CHARS}dialectizer <dialectizer>")) caller.respond(__("Available dialectizers: %s") % ", ".join(self.dialectizers.keys())) return with self.data: if protocol.name not in self.data: self.data[protocol.name] = {} if source.name not in self.data[protocol.name]: self.data[protocol.name][source.name] = "off" setting = args[0].lower() if setting not in self.dialectizers: caller.respond(__("Unknown dialectizer: %s") % setting) return self.data[protocol.name][source.name] = setting caller.respond(__("Dialectizer set to '%s'") % setting)
class TriggersPlugin(plugin.PluginObject): commands = None events = None storage = None _config = None def setup(self): ### Grab important shit self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() ### Initial config load try: self._config = self.storage.get_file(self, "config", YAML, "plugins/triggers.yml") except Exception: self.logger.exception("Error loading configuration!") self.logger.error("Disabling...") self._disable_self() return if not self._config.exists: self.logger.error("Unable to find config/plugins/triggers.yml") self.logger.error("Disabling...") self._disable_self() return ### Register event handlers def _message_event_filter(event=MessageReceived): return isinstance(event.target, Channel) self.events.add_callback("MessageReceived", self, self.message_handler, 1, _message_event_filter) self.events.add_callback("ActionReceived", self, self.action_handler, 1, _message_event_filter) def reload(self): try: self._config.reload() except Exception: self.logger.exception("Error reloading configuration!") return False return True @property def _triggers(self): return self._config.get("triggers", {}) def message_handler(self, event=MessageReceived): self.event_handler(event.caller, event.source, event.target, event.message, event.type) def action_handler(self, event=ActionReceived): self.event_handler(event.caller, event.source, event.target, event.message, "action") def event_handler(self, protocol, source, target, message, e_type): """ Event handler for general messages """ allowed = self.commands.perm_handler.check("triggers.trigger", source, target, protocol) if not allowed: return # TODO: Rewrite this when Matcher is finished # TODO: We check the types of half of these - do the rest global_triggers = self._triggers.get("global", []) proto_trigger_block = self._triggers.get("protocols", {}) proto_triggers = proto_trigger_block.get(protocol.name, {}) if not isinstance(proto_triggers, dict): self.logger.error("Invalid triggers for protocol '%s'" % protocol.name) return proto_triggers_global = proto_triggers.get("global", []) channel_triggers_block = proto_triggers.get("channels", {}) channel_triggers = [] _channel = None for _channel, _triggers in channel_triggers_block.iteritems(): if protocol.get_channel(_channel) == target: channel_triggers = _triggers break if not isinstance(channel_triggers, list): self.logger.error( "Invalid triggers for channel '%s' in protocol '%s'" % (_channel, protocol.name)) return for trigger in itertools.chain(channel_triggers, proto_triggers_global, global_triggers): try: trigger_regex = trigger["trigger"] responses = trigger["response"] chance = trigger.get("chance", 100) flags = trigger.get("flags", "") trigger_types = trigger.get("trigger_types", {"message": True}) response_type = trigger.get("response_type", "message") if not trigger_types.get(e_type, False): continue if random.random() * 100 >= chance: continue response = random.choice(responses) if isinstance(response, dict): response_type = response.get("type", response_type) response = response["response"] response_type = response_type.lower() flags_parsed = 0 for flag in flags.lower(): if flag == "i": flags_parsed += re.I elif flag == "u": flags_parsed += re.U elif flag == "l": flags_parsed += re.L elif flag == "m": flags_parsed += re.M elif flag == "x": flags_parsed += re.X elif flag == "s": flags_parsed += re.S elif flag == "d": flags_parsed += re.DEBUG else: self.log.warning("Unknown regex flag '%s'" % flag) # TODO: Rate limiting # re caches compiled patterns internally, so we don't have to match = re.search(trigger_regex, message, flags_parsed) if match: # Hack to get around the fact that regex groups start at # one, but formatting args start at 0 format_args = [""] format_args.extend(match.groups()) format_kwargs = {} for k, v in match.groupdict().iteritems(): format_kwargs[k] = v format_kwargs["channel"] = _channel format_kwargs["source"] = source format_kwargs["target"] = target format_kwargs["message"] = message format_kwargs["protocol"] = protocol.name response_formatted = response.format( *format_args, **format_kwargs) if response_type == "message": protocol.send_msg(target, response_formatted) elif response_type == "action": protocol.send_action(target, response_formatted) elif response_type == "notice": if hasattr(protocol, "send_notice"): protocol.send_notice(target, response_formatted) else: self.logger.error( "Cannot respond with notice on protocol: '%s'" % protocol.name) elif response_type == "channel_kick": attempted = protocol.channel_kick( source, channel=_channel, reason=response_formatted) if not attempted: self.logger.warning("Couldn't kick %s from %s" % (source, _channel)) elif response_type == "channel_ban": attempted = protocol.channel_ban( source, channel=_channel, reason=response_formatted) if not attempted: self.logger.warning("Couldn't ban %s from %s" % (source, _channel)) elif response_type == "global_kick": attempted = protocol.global_kick( source, reason=response_formatted) if not attempted: self.logger.warning("Couldn't global kick %s" % source) elif response_type == "global_ban": attempted = protocol.global_ban( source, reason=response_formatted) if not attempted: self.logger.warning("Couldn't global ban %s" % source) else: self.logger.error("Invalid response_type '%s'" % response_type) except Exception: self.logger.exception( "Invalid trigger for channel '%s' in protocol '%s'" % (_channel, protocol.name))
class FactoidsPlugin(plugin.PluginObject): CHANNEL = "channel" PROTOCOL = "protocol" GLOBAL = "global" PERM_ADD = "factoids.add.%s" PERM_SET = "factoids.set.%s" PERM_DEL = "factoids.delete.%s" PERM_GET = "factoids.get.%s" (RES_INVALID_LOCATION, RES_INVALID_METHOD, # _FOR_LOCATION - i.e. CHANNEL in PM RES_NO_PERMS, RES_MISSING_FACTOID) = xrange(4) def setup(self): # ## Grab important shit self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() self.plugman = PluginManager() # ## Set up database self.database = self.storage.get_file( self, "data", DBAPI, "sqlite3:data/plugins/factoids.sqlite", "data/plugins/factoids.sqlite", check_same_thread=False ) self.database.add_callback(self.reload) self.reload() # ## Register commands # We have multiple possible permissions per command, so we have to do # permission handling ourselves self.commands.register_command("addfactoid", self.factoid_add_command, self, None) self.commands.register_command("setfactoid", self.factoid_set_command, self, None) self.commands.register_command("deletefactoid", self.factoid_delete_command, self, None, ["delfactoid"]) self.commands.register_command("getfactoid", self.factoid_get_command, self, None, default=True) # ## Register events self.events.add_callback("MessageReceived", self, self.message_handler, 1) self.events.add_callback("Web/ServerStartedEvent", self, self.web_routes, 1) def reload(self): with self.database as db: db.runQuery("CREATE TABLE IF NOT EXISTS factoids (" "factoid_key TEXT, " "location TEXT, " "protocol TEXT, " "channel TEXT, " "factoid_name TEXT, " "info TEXT, " "UNIQUE(factoid_key, location, protocol, channel) " "ON CONFLICT REPLACE)") # region Util functions def __check_perm(self, perm, caller, source, protocol): self.logger.trace(_("Checking for permission: '%s'"), perm) allowed = self.commands.perm_handler.check(perm, caller, source, protocol) return allowed def _parse_args(self, raw_args): """ Grabs the location, factoid name, and info from a raw_args string """ pos = raw_args.find(" ") if pos < 0: raise ValueError(_("Invalid args")) location = raw_args[:pos] pos2 = raw_args.find(" ", pos + 1) if pos2 < 0: raise ValueError(_("Invalid args")) factoid = raw_args[pos + 1:pos2] # pos3 = raw_args.find(" ", pos2 + 1) info = raw_args[pos2 + 1:] if info == "": raise ValueError(_("Invalid args")) return location, factoid, info def valid_location(self, location, source=None): """ Checks if a given location is one of channel, protocol or global, and if it's a channel request, that it's in a channel. """ location = location.lower() result = location in (self.CHANNEL, self.PROTOCOL, self.GLOBAL) if not result: raise InvalidLocationError(_("'%s' is not a valid location") % location) if source is not None: if location == self.CHANNEL and not isinstance(source, Channel): raise InvalidMethodError(_("'channel' location can only be " "used inside a channel")) return True # endregion # region API functions to access factoids def _add_factoid_interaction(self, txn, factoid_key, location, protocol, channel, factoid, info): """ Appends a factoid to an existing one if there, otherwise creates it. :return: True if already exists, otherwise False """ txn.execute("SELECT * FROM factoids WHERE " "factoid_key = ? AND location = ? AND " "protocol = ? AND channel = ?", ( to_unicode(factoid_key), to_unicode(location), to_unicode(protocol), to_unicode(channel) )) results = txn.fetchall() if len(results) == 0: # Factoid doesn't exist yet, create it txn.execute("INSERT INTO factoids VALUES(?, ?, ?, ?, ?, ?)", ( to_unicode(factoid_key), to_unicode(location), to_unicode(protocol), to_unicode(channel), to_unicode(factoid), to_unicode(info) )) e = FactoidAddedEvent(self, factoid_key, factoid) self.events.run_callback("Factoids/Added", e, from_thread=True) return False else: # Factoid already exists, append txn.execute("INSERT INTO factoids VALUES(?, ?, ?, ?, ?, ?)", ( to_unicode(results[0][0]), to_unicode(results[0][1]), to_unicode(results[0][2]), to_unicode(results[0][3]), to_unicode(results[0][4]), results[0][5] + u"\n" + to_unicode(info) )) e = FactoidUpdatedEvent(self, factoid_key, factoid) self.events.run_callback("Factoids/Updated", e, from_thread=True) return True def _delete_factoid_interaction(self, txn, factoid_key, location, protocol, channel): """ Deletes a factoid if it exists, otherwise raises MissingFactoidError """ self.logger.trace("DELETE | Key: %s | Loc: %s | Pro: %s | Cha: %s" % (factoid_key, location, protocol, channel)) if location == self.CHANNEL: txn.execute("DELETE FROM factoids WHERE factoid_key = ? AND " "location = ? AND protocol = ? AND channel = ?", ( to_unicode(factoid_key), to_unicode(location), to_unicode(protocol), to_unicode(channel) )) else: txn.execute("DELETE FROM factoids WHERE factoid_key = ? AND " "location = ? AND protocol = ?", ( to_unicode(factoid_key), to_unicode(location), to_unicode(protocol) )) if txn.rowcount == 0: raise MissingFactoidError(_("Factoid '%s' does not exist") % factoid_key) e = FactoidDeletedEvent(self, factoid_key) self.events.run_callback("Factoids/Deleted", e, from_thread=True) def _get_factoid_interaction(self, txn, factoid_key, location, protocol, channel): """ Gets a factoid if it exists, otherwise raises MissingFactoidError :return: (factoid_name, [entry, entry, ...]) """ self.logger.trace(_("Getting factoid params: factoid_key = '%s', " "location = '%s', protocol = '%s', " "channel = '%s'"), factoid_key, location, protocol, channel) if location is None: self.logger.trace(_("Location is None - getting all factoids with " "key '%s'"), factoid_key) txn.execute("SELECT location, protocol, channel, factoid_name, " "info FROM factoids WHERE factoid_key = ?", ( to_unicode(factoid_key), )) results = txn.fetchall() if len(results) > 0: # Check for channel match for row in results: if ((row[0] == self.CHANNEL and row[1] == protocol and row[2] == channel)): self.logger.trace(_("Match found (channel)!")) return (row[3], row[4].split("\n")) # Check for protocol match for row in results: if row[0] == self.PROTOCOL and row[1] == protocol: self.logger.trace(_("Match found (protocol)!")) return (row[3], row[4].split("\n")) # Check for global match for row in results: if row[0] == self.GLOBAL: self.logger.trace(_("Match found (global)!")) return (row[3], row[4].split("\n")) else: txn.execute("SELECT location, protocol, channel, factoid_name, " "info FROM factoids WHERE factoid_key = ? AND " "location = ? AND protocol = ? AND channel = ?", ( to_unicode(factoid_key), to_unicode(location), to_unicode(protocol), to_unicode(channel) )) results = txn.fetchall() if len(results) > 0: return (results[0][3], results[0][4].split("\n")) raise MissingFactoidError(_("Factoid '%s' does not exist") % factoid_key) def _get_all_factoids_interaction(self, txn): """ Gets all factoids :return: (factoid_name, [entry, entry, ...]) """ self.logger.trace("Getting all factoids.") txn.execute("SELECT location, protocol, channel, factoid_name, " "info FROM factoids") results = txn.fetchall() return results def get_all_factoids(self): with self.database as db: return db.runInteraction(self._get_all_factoids_interaction) def add_factoid(self, caller, source, protocol, location, factoid, info): location = location.lower() factoid_key = factoid.lower() protocol_key = protocol.name.lower() channel_key = source.name.lower() try: location is None or self.valid_location(location, source) except Exception as ex: return defer.fail(ex) if not self.__check_perm(self.PERM_ADD % location, caller, source, protocol): return defer.fail( NoPermissionError(_("User does not have required permission")) ) with self.database as db: return db.runInteraction(self._add_factoid_interaction, factoid_key, location, protocol_key, channel_key, factoid, info) def set_factoid(self, caller, source, protocol, location, factoid, info): location = location.lower() factoid_key = factoid.lower() protocol_key = protocol.name.lower() channel_key = source.name.lower() try: location is None or self.valid_location(location, source) except Exception as ex: return defer.fail(ex) if not self.__check_perm(self.PERM_SET % location, caller, source, protocol): return defer.fail( NoPermissionError(_("User does not have required permission")) ) with self.database as db: return db.runQuery( "INSERT INTO factoids VALUES(?, ?, ?, ?, ?, ?)", ( to_unicode(factoid_key), to_unicode(location), to_unicode(protocol_key), to_unicode(channel_key), to_unicode(factoid), to_unicode(info) )) def delete_factoid(self, caller, source, protocol, location, factoid): location = location.lower() factoid_key = factoid.lower() protocol_key = protocol.name.lower() channel_key = source.name.lower() try: location is None or self.valid_location(location, source) except Exception as ex: return defer.fail(ex) if not self.__check_perm(self.PERM_DEL % location, caller, source, protocol): return defer.fail( NoPermissionError(_("User does not have required permission")) ) with self.database as db: return db.runInteraction(self._delete_factoid_interaction, factoid_key, location, protocol_key, channel_key) def get_factoid(self, caller, source, protocol, location, factoid): if location is not None: location = location.lower() factoid_key = factoid.lower() protocol_key = protocol.name.lower() channel_key = source.name.lower() try: location is None or self.valid_location(location, source) except Exception as ex: return defer.fail(ex) if not self.__check_perm(self.PERM_GET % location, caller, source, protocol): return defer.fail( NoPermissionError(_("User does not have required permission")) ) with self.database as db: return db.runInteraction(self._get_factoid_interaction, factoid_key, location, protocol_key, channel_key) # endregion # region Command handlers for interacting with factoids def _factoid_command_fail(self, caller, failure): """ :type failure: twisted.python.failure.Failure """ if failure.check(InvalidLocationError): caller.respond(__("Invalid location given - possible locations " "are: channel, protocol, global")) elif failure.check(InvalidMethodError): caller.respond(__("You must do that in a channel")) elif failure.check(NoPermissionError): caller.respond(__("You don't have permission to do that")) elif failure.check(MissingFactoidError): caller.respond(__("That factoid doesn't exist")) else: # TODO: We should probably handle this failure.raiseException() def _factoid_get_command_success(self, source, result, args=None): if not args: args = [] for line in result[1]: # _tokens = tokens.find_tokens(line) _numerical = tokens.find_numerical_tokens(line) for i, arg in enumerate(args): line = line.replace("{%d}" % i, arg) for token in _numerical: line = line.replace(token, "") # TODO: Token handlers source.respond("(%s) %s" % (result[0], line)) def factoid_add_command(self, protocol, caller, source, command, raw_args, parsed_args): try: location, factoid, info = self._parse_args(raw_args) except Exception: caller.respond(__("Usage: %s <location> <factoid> <info>") % command) return d = self.add_factoid(caller, source, protocol, location, factoid, info) d.addCallbacks( lambda r: caller.respond(__("Factoid added")), lambda f: self._factoid_command_fail(caller, f) ) def factoid_set_command(self, protocol, caller, source, command, raw_args, parsed_args): try: location, factoid, info = self._parse_args(raw_args) except Exception: caller.respond(__("Usage: %s <location> <factoid> <info>") % command) return d = self.set_factoid(caller, source, protocol, location, factoid, info) d.addCallbacks( lambda r: caller.respond(__("Factoid set")), lambda f: self._factoid_command_fail(caller, f) ) def factoid_delete_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if len(args) != 2: caller.respond(__("Usage: %s <location> <factoid>") % command) return location = args[0] factoid = args[1] d = self.delete_factoid(caller, source, protocol, location, factoid) d.addCallbacks( lambda r: caller.respond(__("Factoid deleted")), lambda f: self._factoid_command_fail(caller, f) ) def factoid_get_command(self, protocol, caller, source, command, raw_args, parsed_args): args = raw_args.split() # Quick fix for new command handler signature if len(args) == 1: factoid = args[0] location = None elif len(args) == 2: location = args[0] factoid = args[1] else: caller.respond(__("Usage: %s [location] <factoid>") % command) return d = self.get_factoid(caller, source, protocol, location, factoid) d.addCallbacks( lambda r: self._factoid_get_command_success(source, r), lambda f: self._factoid_command_fail(caller, f) ) # endregion def _print_query(self, result): from pprint import pprint pprint(result) def web_routes(self, event=None): self.logger.info(_("Registering web route..")) #: :type: WebPlugin web = self.plugman.get_plugin("Web") if web is None: self.logger.debug("Web plugin not found.") return web.add_handler(r"/factoids", "plugins.factoids.route.Route") web.add_handler(r"/factoids/", "plugins.factoids.route.Route") web.add_navbar_entry("factoids", "/factoids", "text file outline") def message_handler(self, event): """ Handle ??-style factoid "commands" :type event: MessageReceived """ handlers = { "??": self._message_handler_get, "?<": self._message_handler_get_self, "??<": self._message_handler_get_self, "?>": self._message_handler_get_other, "??>": self._message_handler_get_other, "??+": self._message_handler_add, "??~": self._message_handler_set, "??-": self._message_handler_delete, "!?+": self._message_handler_add_global, "!?~": self._message_handler_set_global, "!?-": self._message_handler_delete_global, "@?+": self._message_handler_add_protocol, "@?~": self._message_handler_set_protocol, "@?-": self._message_handler_delete_protocol } msg = event.message command = None factoid = "" args = "" pos = msg.find(" ") split = msg.split(" ") if pos < 0: command = msg else: command = msg[:pos] pos2 = msg.find(" ", pos + 1) if pos2 < 0: factoid = msg[pos + 1:].strip() else: factoid = msg[pos + 1:pos2].strip() args = msg[pos2 + 1:].strip() if command in handlers: handlers[command](command, factoid, args, event, split) # ## Getting "commands" def _message_handler_get(self, command, factoid, args, event, split): """ Handle ?? factoid "command" :type event: MessageReceived """ if not factoid: event.source.respond(__("Usage: ?? <factoid>")) return d = self.get_factoid(event.source, event.target, event.caller, None, factoid) d.addCallbacks( lambda r: self._factoid_get_command_success(event.target, r, split[2:]), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_get_self(self, command, factoid, args, event, split): """ Handle ?< factoid "command" :type event: MessageReceived """ if not factoid: event.source.respond(__("Usage: ?< <factoid>")) return d = self.get_factoid(event.source, event.target, event.caller, None, factoid) d.addCallbacks( lambda r: self._factoid_get_command_success(event.source, r, split[2:]), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_get_other(self, command, factoid, args, event, split): """ Handle ?> factoid "command" :type event: MessageReceived """ if not len(split) > 2: event.source.respond(__("Usage: ?> <user> <factoid>")) return wanted = split[1] factoid = split[2] user = event.caller.get_user(wanted) if user is None: event.source.respond(__("Unable to find that user.")) return d = self.get_factoid(event.source, event.target, event.caller, None, factoid) d.addCallbacks( lambda r: self._factoid_get_command_success(user, r, split[3:]), lambda f: self._factoid_command_fail(event.source, f) ) # ## Channel "commands" def _message_handler_add(self, command, factoid, args, event, split): """ Handle ??+ factoid "command" :type event: MessageReceived """ if not factoid or not args: event.source.respond(__("Usage: ??+ <factoid> <info>")) return d = self.add_factoid(event.source, event.target, event.caller, self.CHANNEL, factoid, args) d.addCallbacks( lambda r: event.source.respond(__("Factoid added")), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_set(self, command, factoid, args, event, split): """ Handle ??~ factoid "command" :type event: MessageReceived """ if not factoid or not args: event.source.respond(__("Usage: ??~ <factoid> <info>")) return d = self.set_factoid(event.source, event.target, event.caller, self.CHANNEL, factoid, args) d.addCallbacks( lambda r: event.source.respond(__("Factoid set")), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_delete(self, command, factoid, args, event, split): """ Handle ??- factoid "command" :type event: MessageReceived """ if factoid is None: event.source.respond(__("Usage: ??- <factoid>")) return d = self.delete_factoid(event.source, event.target, event.caller, self.CHANNEL, factoid) d.addCallbacks( lambda r: event.source.respond(__("Factoid deleted")), lambda f: self._factoid_command_fail(event.source, f) ) # ## Global "commands" def _message_handler_add_global(self, command, factoid, args, event, split): """ Handle !?+ factoid "command" :type event: MessageReceived """ if not factoid or not args: event.source.respond(__("Usage: !?+ <factoid> <info>")) return d = self.add_factoid(event.source, event.target, event.caller, self.GLOBAL, factoid, args) d.addCallbacks( lambda r: event.source.respond(__("Factoid added")), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_set_global(self, command, factoid, args, event, split): """ Handle !?~ factoid "command" :type event: MessageReceived """ if not factoid or not args: event.source.respond(__("Usage: !?~ <factoid> <info>")) return d = self.set_factoid(event.source, event.target, event.caller, self.GLOBAL, factoid, args) d.addCallbacks( lambda r: event.source.respond(__("Factoid set")), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_delete_global(self, command, factoid, args, event, split): """ Handle !?- factoid "command" :type event: MessageReceived """ if factoid is None: event.source.respond(__("Usage: !?- <factoid>")) return d = self.delete_factoid(event.source, event.target, event.caller, self.GLOBAL, factoid) d.addCallbacks( lambda r: event.source.respond(__("Factoid deleted")), lambda f: self._factoid_command_fail(event.source, f) ) # ## Protocol-specific "commands" def _message_handler_add_protocol(self, command, factoid, args, event, split): """ Handle @?+ factoid "command" :type event: MessageReceived """ if not factoid or not args: event.source.respond(__("Usage: @?+ <factoid> <info>")) return d = self.add_factoid(event.source, event.target, event.caller, self.PROTOCOL, factoid, args) d.addCallbacks( lambda r: event.source.respond(__("Factoid added")), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_set_protocol(self, command, factoid, args, event, split): """ Handle @?~ factoid "command" :type event: MessageReceived """ if not factoid or not args: event.source.respond(__("Usage: @?~ <factoid> <info>")) return d = self.set_factoid(event.source, event.target, event.caller, self.PROTOCOL, factoid, args) d.addCallbacks( lambda r: event.source.respond(__("Factoid set")), lambda f: self._factoid_command_fail(event.source, f) ) def _message_handler_delete_protocol(self, command, factoid, args, event, split): """ Handle @?- factoid "command" :type event: MessageReceived """ if factoid is None: event.source.respond(__("Usage: @?- <factoid>")) return d = self.delete_factoid(event.source, event.target, event.caller, self.PROTOCOL, factoid) d.addCallbacks( lambda r: event.source.respond(__("Factoid deleted")), lambda f: self._factoid_command_fail(event.source, f) )
class ReplPlugin(plugin.PluginObject): commands = None events = None storage = None config = None proto = None channel = None formatting = None def __init__(self): self.protocol_events = { "general": [ # This is basically just *args. ["MessageReceived", self, self.message_received, 0] ], "inter": [ ["Inter/PlayerConnected", self, self.inter_player_connected, 0], ["Inter/PlayerDisconnected", self, self.inter_player_disconnected, 0], ["Inter/ServerConnected", self, self.inter_server_connected, 0], ["Inter/ServerDisconnected", self, self.inter_server_disconnected, 0] ] } super(ReplPlugin, self).__init__() def setup(self): self.logger.trace("Entered setup method.") self.commands = CommandManager() self.events = EventManager() self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/inter.yml") except Exception: self.logger.exception("Error loading configuration!") self.logger.error(_("Disabling..")) self._disable_self() return if not self.config.exists: self.logger.error("Unable to find config/plugins/inter.yml") self.logger.error(_("Disabling..")) self._disable_self() return self.config.add_callback(self.reload) self.commands.register_command("players", self.players_command, self, "inter.players", default=True) self.events.add_callback("ReactorStarted", self, self.first_load, 0) def first_load(self, event): if not self.reload(): self.logger.error(_("Disabling..")) self._disable_self() def reload(self): self.events.remove_callbacks_for_plugin(self.info.name) proto = self.factory_manager.get_protocol(self.config["protocol"]) if proto is None: self.logger.error(_("Unknown protocol: %s") % self.config["protocol"]) return False if proto.TYPE == "inter": self.logger.error(_("You cannot relay between two Inter " "protocols!")) return False self.proto = proto self.channel = self.config["channel"] self.formatting = self.config["formatting"] for event in self.protocol_events["general"]: self.events.add_callback(*event) for event in self.protocol_events["inter"]: self.events.add_callback(*event) if proto.TYPE in self.protocol_events: for event in self.protocol_events[proto.TYPE]: self.events.add_callback(*event) return True def get_inters(self): inters = {} for key in self.factory_manager.factories.keys(): if self.factory_manager.get_protocol(key).TYPE == "inter": inters[key] = self.factory_manager.get_protocol(key) return inters def players_command(self, protocol, caller, source, command, raw_args, args): if protocol.TYPE == "inter": caller.respond("This command cannot be used via Inter.") return inters = self.get_inters() if len(inters) < 1: caller.respond("No Inter protocols were found.") elif len(inters) == 1: servers = inters[inters.keys()[0]].inter_servers for key in servers.keys(): formatting = self.formatting["player"]["list"] done = formatting["message"] _done = [] for x in servers[key]: _done.append(str(x)) if len(_done): players = formatting["join"].join(_done) else: players = "No players online." done = done.replace("{SERVER}", key) done = done.replace("{PLAYERS}", players) source.respond(done) else: if len(args) < 1: caller.respond("Usage: {CHARS}%s <inter server>") caller.respond("Servers: %s" % ", ".join(inters.keys())) return srv = args[1] if srv not in inters: caller.respond("Unknown inter server: %s" % srv) caller.respond("Servers: %s" % ", ".join(inters.keys())) return servers = inters[srv].inter_servers for key in servers.keys(): formatting = self.formatting["player"]["list"] done = formatting["message"] _done = [] for x in servers[key]: _done.append(str(x)) if len(_done): players = formatting["join"].join(_done) else: players = "No players online." done = done.replace("{SERVER}", key) done = done.replace("{PLAYERS}", players) source.respond(done) def message_received(self, event=MessageReceived): caller = event.caller user = event.source message = event.message target = event.target if target is None: return if caller is None: return if isinstance(caller, Protocol): if caller.TYPE == "inter": f_str = self.formatting["player"]["message"] f_str = f_str.replace("{SERVER}", user.server) f_str = f_str.replace("{USER}", str(user)) f_str = f_str.replace("{MESSAGE}", message) self.proto.send_msg(self.channel, f_str) else: if caller.name == self.proto.name: if target.name.lower() == self.channel.lower(): inters = self.get_inters() for proto in inters.values(): proto.send_msg_other(user, message) def inter_server_connected(self, event=InterServerConnected): f_str = self.formatting["server"]["connected"] f_str = f_str.replace("{SERVER}", event.name) self.proto.send_msg(self.channel, f_str) def inter_server_disconnected(self, event=InterServerDisonnected): f_str = self.formatting["server"]["disconnected"] f_str = f_str.replace("{SERVER}", event.name) self.proto.send_msg(self.channel, f_str) def inter_player_connected(self, event=InterPlayerConnected): f_str = self.formatting["player"]["connected"] f_str = f_str.replace("{SERVER}", event.user.server) f_str = f_str.replace("{USER}", str(event.user)) self.proto.send_msg(self.channel, f_str) def inter_player_disconnected(self, event=InterPlayerDisonnected): f_str = self.formatting["player"]["disconnected"] f_str = f_str.replace("{SERVER}", event.user.server) f_str = f_str.replace("{USER}", str(event.user)) self.proto.send_msg(self.channel, f_str)
class BridgePlugin(plugin.PluginObject): """ Message bridging plugin object """ config = None events = None commands = None storage = None rules = {} @property def rules(self): """ The list of bridging rules """ return self.config["rules"] def setup(self): """ Called when the plugin is loaded. Performs initial setup. """ self.logger.trace(_("Entered setup method.")) self.storage = StorageManager() try: self.config = self.storage.get_file(self, "config", YAML, "plugins/bridge.yml") except Exception: self.logger.exception(_("Error loading configuration!")) self.logger.error(_("Disabling..")) self._disable_self() return if not self.config.exists: self.logger.error(_("Unable to find config/plugins/bridge.yml")) self.logger.error(_("Disabling..")) self._disable_self() return self.commands = CommandManager() self.events = EventManager() # General self.events.add_callback("PreMessageReceived", self, self.handle_msg, 1000) self.events.add_callback("MessageSent", self, self.handle_msg_sent, 0) # self.events.add_callback("PreCommand", self, self.handle_command, # 1000) self.events.add_callback("ActionSent", self, self.handle_action_sent, 0) self.events.add_callback("UserDisconnected", self, self.handle_disconnect, 1000) # IRC self.events.add_callback("IRC/UserJoined", self, self.handle_irc_join, 1000) self.events.add_callback("IRC/UserParted", self, self.handle_irc_part, 1000) self.events.add_callback("IRC/UserKicked", self, self.handle_irc_kick, 1000) self.events.add_callback("IRC/UserQuit", self, self.handle_irc_quit, 1000) self.events.add_callback("IRC/CTCPQueryReceived", self, self.handle_irc_action, 1000) # Mumble self.events.add_callback("Mumble/UserRemove", self, self.handle_mumble_remove, 1000) self.events.add_callback("Mumble/UserJoined", self, self.handle_mumble_join, 1000) self.events.add_callback("Mumble/UserMoved", self, self.handle_mumble_move, 1000) def handle_irc_join(self, event=UserJoinedEvent): """ Event handler for IRC join events """ self.do_rules("", event.caller, event.user, event.channel, f_str=["general", "join"], tokens={"CHANNEL": event.channel.name}) def handle_irc_part(self, event=UserPartedEvent): """ Event handler for IRC part events """ self.do_rules("", event.caller, event.user, event.channel, f_str=["general", "part"], tokens={"CHANNEL": event.channel.name}) def handle_irc_kick(self, event=UserKickedEvent): """ Event handler for IRC kick events """ self.do_rules(event.reason, event.caller, event.user, event.channel, f_str=["general", "kick"], tokens={"CHANNEL": event.channel.name, "KICKER": event.kicker.nickname}) def handle_irc_quit(self, event=UserQuitEvent): """ Event handler for IRC quit events """ self.do_rules(event.message, event.caller, event.user, event.user, f_str=["irc", "disconnect"], tokens={"USER": event.user.nickname}) def handle_irc_action(self, event=CTCPQueryEvent): """ Event handler for IRC CTCP query events """ if event.action == "ACTION": self.do_rules(event.data, event.caller, event.user, event.channel, f_str=["general", "action"]) def handle_disconnect(self, event=UserDisconnected): """ Event handler for general disconnect events """ self.do_rules("", event.caller, event.user, event.user, f_str=["general", "disconnect"], tokens={"USER": event.user.nickname}) def handle_mumble_join(self, event=UserJoined): """ Event handler for Mumble join events """ self.do_rules("", event.caller, event.user, event.user, f_str=["mumble", "connect"]) def handle_mumble_move(self, event=UserMoved): """ Event handler for Mumble move events """ # Moving /from/ the configured channel to another one self.do_rules("", event.caller, event.user, event.old_channel, f_str=["mumble", "moved-from"], tokens={"CHANNEL": event.channel.name}) # Moving /to/ the configured channel self.do_rules("", event.caller, event.user, event.channel, f_str=["mumble", "moved-to"], tokens={"CHANNEL": event.channel.name}) def handle_mumble_remove(self, event=UserRemove): """ Event handler for Mumble remove events """ self.do_rules(event.reason, event.caller, event.user, event.user, f_str=["mumble", "remove"], tokens={"KICKER": event.kicker, "BANNED?": "banned" if event.ban else "kicked"}) def handle_msg(self, event=MessageReceived): """ Event handler for general message events """ self.do_rules(event.message, event.caller, event.source, event.target) def handle_msg_sent(self, event=MessageSent): """ Event handler for general message sent events """ self.do_rules(event.message, event.caller, event.caller.ourselves, event.target) def handle_action_sent(self, event=ActionSent): """ Event handler for general action sent events """ self.do_rules(event.message, event.caller, event.caller.ourselves, event.target, f_str=["general", "action"]) def handle_command(self, event=PreCommand): """ Event handler for general pre-command events """ if event.printable: self.do_rules(event.message, event.caller, event.source, event.target) def do_rules(self, msg, caller, source, target, from_user=True, to_user=True, f_str=None, tokens=None, use_event=False): """ Action the bridge ruleset based on input. :param msg: Message to relay :param caller: User that sent the message :param source: Protocol the message relates to :param target: User or Channel the message was sent to :param from_user: Whether to relay from a PM :param to_user: Whether to relay to a PM :param f_str: Definition of the formatting string to use. For example, ["general", "action"] or ["irc", "quit"] :param tokens: Dict of extra tokens to replace :param use_event: Whether to throw a MessageSent event :type msg: str :type caller: User :type source: Protocol :type target: User, Channel :type from_user: bool :type to_user: bool :type f_str: list :type tokens: dict :type use_event: bool """ if not caller: return if not source: return if not target: return if not tokens: tokens = {} if not f_str: f_str = ["general", "message"] c_name = caller.name.lower() # Protocol s_name = source.nickname # User if isinstance(target, Channel): t_name = target.name # Channel else: t_name = target.nickname for rule, data in self.rules.items(): self.logger.debug(_("Checking rule: %s - %s") % (rule, data)) from_ = data["from"] to_ = data["to"] if c_name != from_["protocol"].lower(): self.logger.trace(_("Protocol doesn't match.")) continue if not self.factory_manager.get_protocol(to_["protocol"]): self.logger.trace(_("Target protocol doesn't exist.")) continue if isinstance(target, User): # We ignore the source name since there can only ever be one # user: us. if not from_user: self.logger.trace(_("Function was called with relaying " "from users disabled.")) continue if from_["source-type"].lower() != "user": self.logger.trace(_("Target type isn't a user.")) continue elif isinstance(target, Channel): if from_["source-type"].lower() != "channel": self.logger.trace(_("Target type isn't a Channel.")) continue if from_["source"].lower() != "*" \ and from_["source"].lower() != t_name.lower(): self.logger.trace(_("Target name doesn't match the " "source.")) continue else: self.logger.trace(_("Target isn't a known type.")) continue if to_["target"] == "user" and not to_user: self.logger.trace(_("Function was called with relaying to " "users disabled.")) continue # If we get this far, we've matched the incoming rule. format_string = None formatting = data["formatting"] if f_str[0] in formatting: if f_str[1] in formatting[f_str[0]]: format_string = formatting[f_str[0]][f_str[1]] if not format_string: self.logger.trace(_("Not relaying message as the format " "string was empty or missing.")) continue else: sf_name = s_name tf_name = t_name if from_.get("obfuscate-names", False): sf_name = s_name[:-1] + "_" + s_name[-1] if "USER" in tokens: _u = tokens["USER"] tokens["USER"] = _u[:-1] + "_" + _u[-1] if to_.get("obfuscate-names", False): tf_name = t_name[:-1] + "_" + t_name[-1] if "TARGET" in tokens: _u = tokens["TARGET"] tokens["TARGET"] = _u[:-1] + "_" + _u[-1] if "disconnected" in format_string: pass for line in msg.strip("\r").split("\n"): format_string = formatting[f_str[0]][f_str[1]] for k, v in tokens.items(): format_string = format_string.replace("{%s}" % k, v) format_string = format_string.replace("{MESSAGE}", line) format_string = format_string.replace("{USER}", sf_name) format_string = format_string.replace("{TARGET}", tf_name) format_string = format_string.replace("{PROTOCOL}", caller.name) prot = self.factory_manager.get_protocol(to_["protocol"]) prot.send_msg(to_["target"], format_string, target_type=to_["target-type"], use_event=use_event)