# Friendly Telegram (telegram userbot) # Copyright (C) 2018-2019 The Authors # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. # pylint: disable=R,C,W0613 # This is bad code, just let it be. We will delete it at some point, perhaps? import logging import re import sys import asyncio import inspect from functools import wraps try: import pymongo import redis except ImportError: logging.debug("Unable to load SQL database modules for compat") from .. import loader from .util import get_cmd_name, MarkdownBotPassthrough logger = logging.getLogger(__name__) class RaphielgangConfig(): def __init__(self, clients): self.__all__ = ["bots", "API_KEY", "API_HASH", "CONSOLE_LOGGER_VERBOSE", "LOGS", "BOTLOG_CHATID", "BOTLOG", "PM_AUTO_BAN", "DB_URI", "OCR_SPACE_API_KEY", "REM_BG_API_KEY", "CHROME_DRIVER", "GOOGLE_CHROME_BIN", "OPEN_WEATHER_MAP_APPID", "WELCOME_MUTE", "ANTI_SPAMBOT", "ANTI_SPAMBOT_SHOUT", "YOUTUBE_API_KEY", "CLEAN_WELCOME", "BIO_PREFIX", "DEFAULT_BIO", "LASTFM_API", "LASTFM_SECRET", "LASTFM_USERNAME", "LASTFM_PASSWORD_PLAIN", "LASTFM_PASS", "lastfm", "G_DRIVE_CLIENT_ID", "G_DRIVE_CLIENT_SECRET", "G_DRIVE_AUTH_TOKEN_DATA", "GDRIVE_FOLDER_ID", "TEMP_DOWNLOAD_DIRECTORY", "COUNT_MSG", "USERS", "COUNT_PM", "LASTMSG", "ENABLE_KILLME", "CMD_HELP", "AFKREASON", "ZALG_LIST", "BRAIN_CHECKER", "CURRENCY_API", "SPOTIFY_USERNAME", "SPOTIFY_PASS", "ISAFK", "ALIVE_NAME", "LOGGER_GROUP", "HELPER", "MONGO_URI", "GENIUS_API_TOKEN", "FORCE_REDIS_AVAIL", "FORCE_MONGO_AVAIL"] self.bots = clients # pylint: disable=C0103 # Static 'cos I cba self.API_KEY = 12345 self.API_HASH = "0123456789abcdef0123456789abcdef" self.CONSOLE_LOGGER_VERBOSE = False self.LOGS = logging.getLogger("raphielgang-compat") self.BOTLOG_CHATID = 0 self.BOTLOG = False self.PM_AUTO_BAN = False self.DB_URI = None self.OCR_SPACE_API_KEY = None self.REM_BG_API_KEY = None self.CHROME_DRIVER = None self.GOOGLE_CHROME_BIN = None self.OPEN_WEATHER_MAP_APPID = None self.WELCOME_MUTE = False self.ANTI_SPAMBOT = False self.ANTI_SPAMBOT_SHOUT = False self.YOUTUBE_API_KEY = None self.CLEAN_WELCOME = None self.BIO_PREFIX = None self.DEFAULT_BIO = None self.LASTFM_API = None self.LASTFM_SECRET = None self.LASTFM_USERNAME = None self.LASTFM_PASSWORD_PLAIN = None self.LASTFM_PASS = None self.lastfm = None self.G_DRIVE_CLIENT_ID = None self.G_DRIVE_CLIENT_SECRET = None self.G_DRIVE_AUTH_TOKEN_DATA = None self.GDRIVE_FOLDER_ID = None self.TEMP_DOWNLOAD_DIRECTORY = "./downloads" self.COUNT_MSG = 0 self.USERS = {} self.COUNT_PM = {} self.LASTMSG = {} self.ENABLE_KILLME = False self.CMD_HELP = {} self.AFKREASON = "no reason" self.ZALG_LIST = [[ "̖", " ̗", " ̘", " ̙", " ̜", " ̝", " ̞", " ̟", " ̠", " ̤", " ̥", " ̦", " ̩", " ̪", " ̫", " ̬", " ̭", " ̮", " ̯", " ̰", " ̱", " ̲", " ̳", " ̹", " ̺", " ̻", " ̼", " ͅ", " ͇", " ͈", " ͉", " ͍", " ͎", " ͓", " ͔", " ͕", " ͖", " ͙", " ͚", " ", ], [ " ̍", " ̎", " ̄", " ̅", " ̿", " ̑", " ̆", " ̐", " ͒", " ͗", " ͑", " ̇", " ̈", " ̊", " ͂", " ̓", " ̈́", " ͊", " ͋", " ͌", " ̃", " ̂", " ̌", " ͐", " ́", " ̋", " ̏", " ̽", " ̉", " ͣ", " ͤ", " ͥ", " ͦ", " ͧ", " ͨ", " ͩ", " ͪ", " ͫ", " ͬ", " ͭ", " ͮ", " ͯ", " ̾", " ͛", " ͆", " ̚", ], [ " ̕", " ̛", " ̀", " ́", " ͘", " ̡", " ̢", " ̧", " ̨", " ̴", " ̵", " ̶", " ͜", " ͝", " ͞", " ͟", " ͠", " ͢", " ̸", " ̷", " ͡", ]] self.BRAIN_CHECKER = [] self.is_mongo_alive = lambda: self.MONGO_URI is not None self.is_redis_alive = lambda: self.REDIS.ping() self.CURRENCY_API = None self.SPOTIFY_USERNAME = None self.SPOTIFY_PASS = None self.ISAFK = False self.ALIVE_NAME = "`**PPE bad! Use **[friendly-telegram](https://t.me/friendlytgbot)`." self.GDRIVE_FOLDER = self.GDRIVE_FOLDER_ID self.GENIUS_API_TOKEN = "" # And some for "AliHasan7671" self.LOGGER_GROUP = 0 self.HELPER = {} # What is this even? self.FORCE_REDIS_AVAIL = False self.FORCE_MONGO_AVAIL = False # Databases def is_mongo_alive(): if self.FORCE_MONGO_AVAIL: return True if self.MONGO_URI is None: return False try: return self.MONGOCLIENT.ismongos is not None except pymongo.errors.ServerSelectionTimeoutError: return False self.is_mongo_alive = is_mongo_alive def is_redis_alive(): if self.FORCE_REDIS_AVAIL: return True try: self.REDIS.ping() except redis.exceptions.ConnectionError: return False else: return True self.is_redis_alive = is_redis_alive # pylint: enable=C0103 self.__passthrus = [] self.mongoclient = None @property def bot(self): if not len(self.__passthrus): self.__passthrus += [MarkdownBotPassthrough(self.bots[0] if len(self.bots) else None)] return self.__passthrus[0] @property def MONGOCLIENT(self): if self.MONGO_URI is not None and self.mongoclient is None: self.mongoclient = pymongo.MongoClient(self.MONGO_URI, 27017, serverSelectionTimeoutMS=1) return self.mongoclient @property def MONGO(self): if self.MONGOCLIENT is not None: return self.MONGOCLIENT.userbot @property def REDIS(self): return redis.StrictRedis(host="localhost", port=6379, db=0) async def client_ready(self, client): self.bots += [client] logging.debug(len(self.__passthrus)) logging.debug(len(self.bots)) try: self.__passthrus[len(self.bots) - 1].__under = client # pylint:disable=W0212 # Ewwww, but needed except IndexError: pass # The core machinery will fail to identify any register() function in the module. # So we need to introspect the module and add register(), and a shimmed class to store state # Please don't refactor this class. Even while writing it only God knew how it worked. __hours_wasted_here = 5 # // don't touch class RaphielgangEvents(): def __init__(self, clients): self.instances = {} class RaphielgangEventsSub(): class __RaphielgangShimMod__Base(loader.Module): instance_count = 0 # E1101 is triggered because pylint thinks that inspect.getmro(type(self))[1] means # type(super()), and it's correct, but this is a base class and is never used. As a result, pylint # incorrectly thinks that type(super()) resolves to loader.Module, and can't find .instance_count. # Perhaps there's a way to annotate it? I don't think so. def __init__(self, events_instance): super().__init__() inspect.getmro(type(self))[1].instance_count += 1 # pylint: disable=E1101 # See above self.instance_id = inspect.getmro(type(self))[1].instance_count # pylint: disable=E1101 # See above self._events = events_instance self.commands = events_instance.commands for func in self.commands.values(): if hasattr(func, "__self__"): func.__self__.__module__ = events_instance.module else: func.__self__ = self self.name = "RaphielGang" + str(self.instance_id) self.unknowns = events_instance.unknowns self.__module__ = events_instance.module async def watcher(self, message): for watcher in self._events.watchers: await watcher(message) def __init__(self): self.module = None self._setup_complete = False self.watchers = [] self.commands = {} self.unknowns = [] self.instance_id = -1 def _ensure_unknowns(self): self.commands["raphcmd" + str(self.instance_id)] = self._unknown_command def _unknown_command(self, message): """A command that could not be understood by the compat system, you must put the raw command after.""" message.message = message.message[len("raphcmd" + str(self.instance_id)) + 1:] return asyncio.gather(*[uk(message, "") for uk in self.unknowns]) def register(self, *args, **kwargs): # noqa: C901 # legacy code that works fine if len(args) >= 1: # This is the register() function in normal ftg modules # Create a fake type, instantiate it with our own self args[0](type("RaphielgangShim__" + self.module, (self.__RaphielgangShimMod__Base,), dict())(self)) return def subreg(func): # ALWAYS return func. logger.debug(kwargs) sys.modules[func.__module__].__dict__["registration"] = self.register if not self._setup_complete: self.module = func.__module__ self._setup_complete = True if kwargs.get("outgoing", False) or not kwargs.get("incoming", False): # Command-based thing use_unknown = False if "pattern" not in kwargs.keys(): self._ensure_unknowns() use_unknown = True else: cmd = get_cmd_name(kwargs["pattern"]) if not cmd: self._ensure_unknowns() use_unknown = True @wraps(func) def commandhandler(message, pre="."): """Closure to execute command when handler activated and regex matched""" logger.debug("Command triggered") # Framework strips prefix, give them a generic one message.message = pre + message.message match = re.match(kwargs.get("pattern", r"^\b$"), message.message, re.I) if "pattern" not in kwargs or match: logger.debug("and matched") event = MarkdownBotPassthrough(message) # Try to emulate the expected format for an event event.pattern_match = match event.message = message return func(event) # Return a coroutine else: logger.debug("but not matched " + message.message + " / " + kwargs.get("pattern", "None")) return asyncio.gather() # passthru coro if use_unknown: self.unknowns += [commandhandler] else: if commandhandler.__doc__ is None: commandhandler.__doc__ = "Undocumented external command" self.commands[cmd] = commandhandler # Add to list of commands so we can call later elif kwargs.get("incoming", False): # Watcher-based thing @wraps(func) def subwatcher(message): """Closure to execute watcher when handler activated and regex matched""" match = re.match(message.message, kwargs.get("pattern", r"^\b$"), re.I) if "pattern" not in kwargs or match: event = message # Try to emulate the expected format for an event event = MarkdownBotPassthrough(message) # Try to emulate the expected format for an event event.pattern_match = match event.message = MarkdownBotPassthrough(message) return func(event) # Return a coroutine return asyncio.gather() self.watchers += [subwatcher] # Add to list of watchers so we can call later. else: logger.error("event not incoming or outgoing or neither or both") return func return func self.instance_id = kwargs["__instance_number"] return subreg def register(self, *args, **kwargs): if len(args) == 2: logger.debug("Register2 for %s", args[1]) self.instances[args[1]].register(args[0]) # Passthrough if we have enough info logger.debug("Completed register2") elif len(args) != 0: logger.error(args) raise TypeError("Takes exactly 2 or 0 params") def subreg(func): if func.__module__ not in self.instances: self.instances[func.__module__] = self.RaphielgangEventsSub() kwargs["__instance_number"] = list(self.instances.keys()).index(func.__module__) return self.instances[func.__module__].register(**kwargs)(func) return subreg def errors_handler(self, func): """Do nothing as this is handled by ftg framework by default""" return func async def client_ready(self, client): pass class RaphielgangDatabase: @staticmethod def __new__(cls, *args, **kwargs): try: from . import dbhelper except ImportError: return super().__new__(cls) else: return dbhelper def __init__(self, clients): pass