mirror of https://github.com/coddrago/Heroku
534 lines
17 KiB
Python
Executable File
534 lines
17 KiB
Python
Executable File
# Friendly Telegram (telegram userbot)
|
|
# Copyright (C) 2018-2021 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 asyncio
|
|
import inspect
|
|
import logging
|
|
import re
|
|
import sys
|
|
from functools import wraps
|
|
|
|
try:
|
|
import pymongo
|
|
import redis
|
|
except ImportError:
|
|
logging.debug("Unable to load 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 = 6
|
|
|
|
|
|
# // 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.strings = {"name": f"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[f"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(f"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 and args[0] is True:
|
|
# This is the register() function in normal ftg modules
|
|
# Create a fake type, instantiate it with our own self
|
|
return type(
|
|
"RaphielgangShim__" + self.module,
|
|
(self.__RaphielgangShimMod__Base,),
|
|
{},
|
|
)(self)
|
|
|
|
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) == 1:
|
|
logger.debug("Register for %s", args[0])
|
|
return self.instances[args[0]].register(
|
|
True
|
|
) # Passthrough if we have enough info
|
|
elif len(args) != 0:
|
|
logger.error(args)
|
|
raise TypeError("Takes at most one parameter")
|
|
|
|
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):
|
|
self._clients = clients
|