├── runtime.txt ├── _config.yml ├── restart.bat ├── Procfile ├── FUNDING.yml ├── bot ├── elevated_users.json ├── modules │ ├── sql │ │ ├── __init__.py │ │ ├── blacklistusers_sql.py │ │ ├── userinfo_sql.py │ │ ├── log_channel_sql.py │ │ ├── disable_sql.py │ │ ├── users_sql.py │ │ ├── connection_sql.py │ │ ├── locks_sql.py │ │ └── cust_filters_sql.py │ ├── helper_funcs │ │ ├── alternate.py │ │ ├── cas_api.py │ │ ├── filters.py │ │ ├── git_api.py │ │ ├── handlers.py │ │ ├── misc.py │ │ ├── msg_types.py │ │ ├── extraction.py │ │ ├── string_handling.py │ │ └── chat_status.py │ ├── __init__.py │ ├── shell.py │ ├── users.py │ ├── whois.py │ ├── dbcleanup.py │ ├── log_channel.py │ ├── misc.py │ ├── disable.py │ ├── devpromoter.py │ ├── cust_filters.py │ └── connection.py ├── sample_config.py ├── __init__.py └── __main__.py ├── start.bat ├── requirements.txt ├── start_service.bat ├── app.json └── README.md /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.1 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /restart.bat: -------------------------------------------------------------------------------- 1 | start cmd.exe /c start_service.bat 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 -m bot 2 | ps:scale worker=1 3 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: www.telegram.dog/D_ar_k_Angel 2 | github: [Jijinr] 3 | -------------------------------------------------------------------------------- /bot/elevated_users.json: -------------------------------------------------------------------------------- 1 | { 2 | "devs": [809546777], 3 | "supports": [], 4 | "whitelists": [], 5 | "sudos": [809546777] 6 | } 7 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | TITLE FilterBot 3 | rem This next line removes any fban csv files if they exist in root when bot restarts. 4 | del *.csv 5 | py -3.7 --version 6 | IF "%ERRORLEVEL%" == "0" ( 7 | py -3.7 -m bot 8 | ) ELSE ( 9 | py -m bot 10 | ) 11 | 12 | pause 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | future 2 | beautifulsoup4 3 | requests 4 | sqlalchemy 5 | tswift 6 | coffeehouse 7 | python-telegram-bot==11.1.0 8 | numpy 9 | pyowm 10 | kbbi 11 | feedparser 12 | gtts 13 | alphabet_detector 14 | psycopg2-binary 15 | feedparser 16 | tswift 17 | pynewtonmath 18 | #memes vvv 19 | spongemock 20 | zalgo-text 21 | geopy 22 | nltk 23 | psutil 24 | bleach 25 | aiohttp>=2.2.5 26 | Pillow>=4.2.0 27 | jikanpy 28 | pytz 29 | parsel 30 | hurry.filesize 31 | spamwatch 32 | emoji 33 | -------------------------------------------------------------------------------- /bot/modules/sql/__init__.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.ext.declarative import declarative_base 3 | from sqlalchemy.orm import sessionmaker, scoped_session 4 | 5 | from bot import DB_URI 6 | 7 | 8 | def start() -> scoped_session: 9 | engine = create_engine(DB_URI, client_encoding="utf8") 10 | BASE.metadata.bind = engine 11 | BASE.metadata.create_all(engine) 12 | return scoped_session(sessionmaker(bind=engine, autoflush=False)) 13 | 14 | 15 | BASE = declarative_base() 16 | SESSION = start() 17 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/alternate.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | 4 | from functools import wraps 5 | from typing import Optional 6 | 7 | from telegram import User, Chat, ChatMember, Update, Bot 8 | from telegram import error 9 | 10 | from bot import DEL_CMDS, SUDO_USERS, WHITELIST_USERS 11 | 12 | 13 | def send_message(message, text, *args,**kwargs): 14 | try: 15 | return message.reply_text(text, *args,**kwargs) 16 | except error.BadRequest as err: 17 | if str(err) == "Reply message not found": 18 | return message.reply_text(text, quote=False, *args,**kwargs) 19 | -------------------------------------------------------------------------------- /start_service.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: BatchGotAdmin 4 | :------------------------------------- 5 | REM --> Check for permissions 6 | >nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" 7 | 8 | REM --> If error flag set, we do not have admin. 9 | if '%errorlevel%' NEQ '0' ( 10 | echo Requesting administrative privileges... 11 | goto UACPrompt 12 | ) else ( goto gotAdmin ) 13 | 14 | :UACPrompt 15 | echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" 16 | set params = %*:"="" 17 | echo UAC.ShellExecute "cmd.exe", "/c %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" 18 | 19 | "%temp%\getadmin.vbs" 20 | del "%temp%\getadmin.vbs" 21 | exit /B 22 | 23 | :gotAdmin 24 | pushd "%CD%" 25 | CD /D "%~dp0" 26 | :-------------------------------------- 27 | net stop FilterBot 28 | net start FilterBot 29 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/cas_api.py: -------------------------------------------------------------------------------- 1 | import urllib.request as url 2 | import json 3 | import datetime 4 | import requests 5 | 6 | 7 | VERSION = "1.3.3" 8 | CAS_QUERY_URL = "https://api.cas.chat/check?user_id=" 9 | DL_DIR = "./csvExports" 10 | 11 | def get_user_data(user_id): 12 | with requests.request('GET', CAS_QUERY_URL + str(user_id)) as userdata_raw: 13 | userdata = json.loads(userdata_raw.text) 14 | return userdata 15 | 16 | def isbanned(userdata): 17 | return userdata['ok'] 18 | 19 | def banchecker(user_id): 20 | return isbanned(get_user_data(user_id)) 21 | 22 | def vercheck() -> str: 23 | return str(VERSION) 24 | 25 | def offenses(user_id): 26 | userdata = get_user_data(user_id) 27 | try: 28 | offenses = userdata['result']['offenses'] 29 | return str(offenses) 30 | except: 31 | return None 32 | 33 | def timeadded(user_id): 34 | userdata = get_user_data(user_id) 35 | try: 36 | timeEp = userdata['result']['time_added'] 37 | timeHuman = datetime.datetime.utcfromtimestamp(timeEp).strftime('%H:%M:%S, %d-%m-%Y') 38 | return timeHuman 39 | except: 40 | return None 41 | -------------------------------------------------------------------------------- /bot/modules/__init__.py: -------------------------------------------------------------------------------- 1 | from bot import LOAD, NO_LOAD, LOGGER 2 | 3 | 4 | def __list_all_modules(): 5 | from os.path import dirname, basename, isfile 6 | import glob 7 | # This generates a list of modules in this folder for the * in __main__ to work. 8 | mod_paths = glob.glob(dirname(__file__) + "/*.py") 9 | all_modules = [basename(f)[:-3] for f in mod_paths if isfile(f) 10 | and f.endswith(".py") 11 | and not f.endswith('__init__.py')] 12 | 13 | if LOAD or NO_LOAD: 14 | to_load = LOAD 15 | if to_load: 16 | if not all(any(mod == module_name for module_name in all_modules) for mod in to_load): 17 | LOGGER.error("Invalid loadorder names. Quitting.") 18 | quit(1) 19 | 20 | else: 21 | to_load = all_modules 22 | 23 | if NO_LOAD: 24 | LOGGER.info("Not loading: {}".format(NO_LOAD)) 25 | return [item for item in to_load if item not in NO_LOAD] 26 | 27 | return to_load 28 | 29 | return all_modules 30 | 31 | 32 | ALL_MODULES = sorted(__list_all_modules()) 33 | LOGGER.info("Modules to load: %s", str(ALL_MODULES)) 34 | __all__ = ALL_MODULES + ["ALL_MODULES"] 35 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/filters.py: -------------------------------------------------------------------------------- 1 | from telegram import Message 2 | from telegram.ext import BaseFilter 3 | 4 | from bot import SUPPORT_USERS, SUDO_USERS, DEV_USERS 5 | 6 | 7 | class CustomFilters(object): 8 | class _Supporters(BaseFilter): 9 | def filter(self, message: Message): 10 | return bool(message.from_user and message.from_user.id in SUPPORT_USERS) 11 | 12 | support_filter = _Supporters() 13 | 14 | class _Sudoers(BaseFilter): 15 | def filter(self, message: Message): 16 | return bool(message.from_user and message.from_user.id in SUDO_USERS) 17 | 18 | sudo_filter = _Sudoers() 19 | 20 | class _Developers(BaseFilter): 21 | def filter(self, message: Message): 22 | return bool(message.from_user and message.from_user.id in DEV_USERS) 23 | 24 | dev_filter = _Developers() 25 | 26 | class _MimeType(BaseFilter): 27 | def __init__(self, mimetype): 28 | self.mime_type = mimetype 29 | self.name = "CustomFilters.mime_type({})".format(self.mime_type) 30 | 31 | def filter(self, message: Message): 32 | return bool(message.document and message.document.mime_type == self.mime_type) 33 | 34 | mime_type = _MimeType 35 | 36 | class _HasText(BaseFilter): 37 | def filter(self, message: Message): 38 | return bool(message.text or message.sticker or message.photo or message.document or message.video) 39 | 40 | has_text = _HasText() 41 | -------------------------------------------------------------------------------- /bot/modules/shell.py: -------------------------------------------------------------------------------- 1 | from bot import dispatcher, LOGGER 2 | from telegram import Bot, Update 3 | from telegram.ext.dispatcher import run_async 4 | from bot.modules.helper_funcs.misc import sendMessage 5 | from telegram.ext import CommandHandler 6 | from bot.modules.helper_funcs.chat_status import dev_plus 7 | from subprocess import Popen, PIPE 8 | 9 | 10 | 11 | def shell(command): 12 | process = Popen(command,stdout=PIPE,shell=True,stderr=PIPE) 13 | stdout,stderr = process.communicate() 14 | return (stdout,stderr) 15 | 16 | @dev_plus 17 | @run_async 18 | def shellExecute(bot: Bot, update: Update): 19 | cmd = update.message.text.split(' ',maxsplit=1) 20 | if len(cmd) == 1: 21 | sendMessage("No command provided!", bot, update) 22 | return 23 | LOGGER.info(cmd) 24 | output = shell(cmd[1]) 25 | if output[1].decode(): 26 | LOGGER.error(f"Shell: {output[1].decode()}") 27 | if len(output[0].decode()) > 4000: 28 | with open("shell.txt",'w') as f: 29 | f.write(f"Output\n-----------\n{output[0].decode()}\n") 30 | if output[1]: 31 | f.write(f"STDError\n-----------\n{output[1].decode()}\n") 32 | with open("shell.txt",'rb') as f: 33 | bot.send_document(document=f, filename=f.name, 34 | reply_to_message_id=update.message.message_id, 35 | chat_id=update.message.chat_id) 36 | else: 37 | if output[1].decode(): 38 | sendMessage(f"{output[1].decode()}", bot, update) 39 | return 40 | else: 41 | sendMessage(f"{output[0].decode()}", bot, update) 42 | 43 | 44 | shell_handler = CommandHandler(('sh','shell'), shellExecute) 45 | dispatcher.add_handler(shell_handler) 46 | -------------------------------------------------------------------------------- /bot/modules/sql/blacklistusers_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import Column, String, UnicodeText 4 | 5 | from bot.modules.sql import BASE, SESSION 6 | 7 | 8 | class BlacklistUsers(BASE): 9 | __tablename__ = "blacklistusers" 10 | user_id = Column(String(14), primary_key=True) 11 | reason = Column(UnicodeText) 12 | 13 | def __init__(self, user_id, reason=None): 14 | self.user_id = user_id 15 | self.reason = reason 16 | 17 | 18 | BlacklistUsers.__table__.create(checkfirst=True) 19 | 20 | BLACKLIST_LOCK = threading.RLock() 21 | BLACKLIST_USERS = set() 22 | 23 | 24 | def blacklist_user(user_id, reason=None): 25 | with BLACKLIST_LOCK: 26 | user = SESSION.query(BlacklistUsers).get(str(user_id)) 27 | if not user: 28 | user = BlacklistUsers(str(user_id), reason) 29 | else: 30 | user.reason = reason 31 | 32 | SESSION.add(user) 33 | SESSION.commit() 34 | __load_blacklist_userid_list() 35 | 36 | 37 | def unblacklist_user(user_id): 38 | with BLACKLIST_LOCK: 39 | user = SESSION.query(BlacklistUsers).get(str(user_id)) 40 | if user: 41 | SESSION.delete(user) 42 | 43 | SESSION.commit() 44 | __load_blacklist_userid_list() 45 | 46 | 47 | def get_reason(user_id): 48 | user = SESSION.query(BlacklistUsers).get(str(user_id)) 49 | rep = "" 50 | if user: 51 | rep = user.reason 52 | 53 | SESSION.close() 54 | return rep 55 | 56 | 57 | def is_user_blacklisted(user_id): 58 | return user_id in BLACKLIST_USERS 59 | 60 | 61 | def __load_blacklist_userid_list(): 62 | global BLACKLIST_USERS 63 | try: 64 | BLACKLIST_USERS = {int(x.user_id) for x in SESSION.query(BlacklistUsers).all()} 65 | finally: 66 | SESSION.close() 67 | 68 | 69 | __load_blacklist_userid_list() -------------------------------------------------------------------------------- /bot/modules/helper_funcs/git_api.py: -------------------------------------------------------------------------------- 1 | import urllib.request as url 2 | import json 3 | import datetime 4 | 5 | VERSION = "1.0.2" 6 | APIURL = "http://api.github.com/repos/" 7 | 8 | def vercheck() -> str: 9 | return str(VERSION) 10 | 11 | #Repo-wise stuff 12 | 13 | def getData(repoURL): 14 | try: 15 | with url.urlopen(APIURL + repoURL + "/releases") as data_raw: 16 | repoData = json.loads(data_raw.read().decode()) 17 | return repoData 18 | except: 19 | return None 20 | 21 | def getReleaseData(repoData, index): 22 | if index < len(repoData): 23 | return repoData[index] 24 | else: 25 | return None 26 | 27 | #Release-wise stuff 28 | 29 | def getAuthor(releaseData): 30 | if releaseData is None: 31 | return None 32 | return releaseData['author']['login'] 33 | 34 | def getAuthorUrl(releaseData): 35 | if releaseData is None: 36 | return None 37 | return releaseData['author']['html_url'] 38 | 39 | def getReleaseName(releaseData): 40 | if releaseData is None: 41 | return None 42 | return releaseData['name'] 43 | 44 | def getReleaseDate(releaseData): 45 | if releaseData is None: 46 | return None 47 | return releaseData['published_at'] 48 | 49 | def getAssetsSize(releaseData): 50 | if releaseData is None: 51 | return None 52 | return len(releaseData['assets']) 53 | 54 | def getAssets(releaseData): 55 | if releaseData is None: 56 | return None 57 | return releaseData['assets'] 58 | 59 | def getBody(releaseData): #changelog stuff 60 | if releaseData is None: 61 | return None 62 | return releaseData['body'] 63 | 64 | #Asset-wise stuff 65 | 66 | def getReleaseFileName(asset): 67 | return asset['name'] 68 | 69 | def getReleaseFileURL(asset): 70 | return asset['browser_download_url'] 71 | 72 | def getDownloadCount(asset): 73 | return asset['download_count'] 74 | 75 | def getSize(asset): 76 | return asset['size'] 77 | -------------------------------------------------------------------------------- /bot/modules/sql/userinfo_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import Column, Integer, UnicodeText 4 | 5 | from bot.modules.sql import SESSION, BASE 6 | 7 | 8 | class UserInfo(BASE): 9 | __tablename__ = "userinfo" 10 | user_id = Column(Integer, primary_key=True) 11 | info = Column(UnicodeText) 12 | 13 | def __init__(self, user_id, info): 14 | self.user_id = user_id 15 | self.info = info 16 | 17 | def __repr__(self): 18 | return "" % self.user_id 19 | 20 | 21 | class UserBio(BASE): 22 | __tablename__ = "userbio" 23 | user_id = Column(Integer, primary_key=True) 24 | bio = Column(UnicodeText) 25 | 26 | def __init__(self, user_id, bio): 27 | self.user_id = user_id 28 | self.bio = bio 29 | 30 | def __repr__(self): 31 | return "" % self.user_id 32 | 33 | 34 | UserInfo.__table__.create(checkfirst=True) 35 | UserBio.__table__.create(checkfirst=True) 36 | 37 | INSERTION_LOCK = threading.RLock() 38 | 39 | 40 | def get_user_me_info(user_id): 41 | userinfo = SESSION.query(UserInfo).get(user_id) 42 | SESSION.close() 43 | if userinfo: 44 | return userinfo.info 45 | return None 46 | 47 | 48 | def set_user_me_info(user_id, info): 49 | with INSERTION_LOCK: 50 | userinfo = SESSION.query(UserInfo).get(user_id) 51 | if userinfo: 52 | userinfo.info = info 53 | else: 54 | userinfo = UserInfo(user_id, info) 55 | SESSION.add(userinfo) 56 | SESSION.commit() 57 | 58 | 59 | def get_user_bio(user_id): 60 | userbio = SESSION.query(UserBio).get(user_id) 61 | SESSION.close() 62 | if userbio: 63 | return userbio.bio 64 | return None 65 | 66 | 67 | def set_user_bio(user_id, bio): 68 | with INSERTION_LOCK: 69 | userbio = SESSION.query(UserBio).get(user_id) 70 | if userbio: 71 | userbio.bio = bio 72 | else: 73 | userbio = UserBio(user_id, bio) 74 | 75 | SESSION.add(userbio) 76 | SESSION.commit() 77 | -------------------------------------------------------------------------------- /bot/sample_config.py: -------------------------------------------------------------------------------- 1 | # Create a new config.py or rename this to config.py file in same dir and import, then extend this class. 2 | import json 3 | import os 4 | 5 | def get_user_list(config, key): 6 | with open('{}/bot/{}'.format(os.getcwd(), config), 'r') as json_file: 7 | return json.load(json_file)[key] 8 | 9 | 10 | # Create a new config.py or rename this to config.py file in same dir and import, then extend this class. 11 | class Config(object): 12 | LOGGER = True 13 | 14 | # REQUIRED 15 | API_KEY = "YOUR BOT TOKEN HERE" 16 | OWNER_ID = "YOUR OWN ID HERE" # If you dont know, run the bot and do /id in your private chat with it 17 | OWNER_USERNAME = "YOUR USERNAME HERE" 18 | 19 | # RECOMMENDED 20 | SQLALCHEMY_DATABASE_URI = 'sqldbtype://username:pw@hostname:port/db_name' # needed for any database modules 21 | WEBHOOK = False 22 | URL = None 23 | 24 | # OPTIONAL 25 | #ID Seperation format [1,2,3,4] 26 | SUDO_USERS = get_user_list('elevated_users.json', 'sudos') # List of id's - (not usernames) for users which have sudo access to the bot. 27 | DEV_USERS = get_user_list('elevated_users.json', 'devs') # List of id's - (not usernames) for developers who will have the same perms as the owner 28 | SUPPORT_USERS = get_user_list('elevated_users.json', 'supports') # List of id's (not usernames) for users which are allowed to gban, but can also be banned. 29 | WHITELIST_USERS = get_user_list('elevated_users.json', 'whitelists') # List of id's (not usernames) for users which WONT be banned/kicked by the bot. 30 | DONATION_LINK = None # EG, paypal 31 | CERT_PATH = None 32 | LOAD = [] 33 | NO_LOAD = ['translation', 'rss'] 34 | PORT = 5000 35 | DEL_CMDS = False # Whether or not you should delete "blue text must click" commands 36 | WORKERS = 8 # Number of subthreads to use. This is the recommended amount - see for yourself what works best! 37 | ALLOW_EXCL = True # Allow ! commands as well as / 38 | 39 | 40 | 41 | class Production(Config): 42 | LOGGER = True 43 | 44 | 45 | class Development(Config): 46 | LOGGER = True 47 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/handlers.py: -------------------------------------------------------------------------------- 1 | from telegram import Update 2 | from telegram.ext import CommandHandler, RegexHandler, MessageHandler 3 | 4 | from bot import ALLOW_EXCL 5 | import bot.modules.sql.blacklistusers_sql as sql 6 | 7 | if ALLOW_EXCL: 8 | CMD_STARTERS = ('/', '!') 9 | else: 10 | CMD_STARTERS = ('/') 11 | 12 | 13 | class CustomCommandHandler(CommandHandler): 14 | 15 | def __init__(self, command, callback, **kwargs): 16 | 17 | if "admin_ok" in kwargs: 18 | del kwargs["admin_ok"] 19 | super().__init__(command, callback, **kwargs) 20 | 21 | def check_update(self, update): 22 | 23 | if (isinstance(update, Update) and (update.message or update.edited_message and self.allow_edited)): 24 | message = update.message or update.edited_message 25 | 26 | if sql.is_user_blacklisted(update.effective_user.id): 27 | return False 28 | 29 | if message.text and len(message.text) > 1: 30 | fst_word = message.text_html.split(None, 1)[0] 31 | 32 | if len(fst_word) > 1 and any(fst_word.startswith(start) for start in CMD_STARTERS): 33 | command = fst_word[1:].split('@') 34 | command.append(message.bot.username) # in case the command was sent without a username 35 | 36 | if self.filters is None: 37 | res = True 38 | elif isinstance(self.filters, list): 39 | res = any(func(message) for func in self.filters) 40 | else: 41 | res = self.filters(message) 42 | 43 | return res and (command[0].lower() in self.command 44 | and command[1].lower() == message.bot.username.lower()) 45 | 46 | return False 47 | 48 | 49 | class CustomRegexHandler(RegexHandler): 50 | def __init__(self, pattern, callback, friendly="", **kwargs): 51 | super().__init__(pattern, callback, **kwargs) 52 | 53 | 54 | class CustomMessageHandler(MessageHandler): 55 | def __init__(self, filters, callback, friendly="", **kwargs): 56 | super().__init__(filters, callback, **kwargs) 57 | -------------------------------------------------------------------------------- /bot/modules/sql/log_channel_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import Column, String, func, distinct 4 | 5 | from bot.modules.sql import BASE, SESSION 6 | 7 | 8 | class GroupLogs(BASE): 9 | __tablename__ = "log_channels" 10 | chat_id = Column(String(14), primary_key=True) 11 | log_channel = Column(String(14), nullable=False) 12 | 13 | def __init__(self, chat_id, log_channel): 14 | self.chat_id = str(chat_id) 15 | self.log_channel = str(log_channel) 16 | 17 | 18 | GroupLogs.__table__.create(checkfirst=True) 19 | 20 | LOGS_INSERTION_LOCK = threading.RLock() 21 | 22 | CHANNELS = {} 23 | 24 | 25 | def set_chat_log_channel(chat_id, log_channel): 26 | with LOGS_INSERTION_LOCK: 27 | res = SESSION.query(GroupLogs).get(str(chat_id)) 28 | if res: 29 | res.log_channel = log_channel 30 | else: 31 | res = GroupLogs(chat_id, log_channel) 32 | SESSION.add(res) 33 | 34 | CHANNELS[str(chat_id)] = log_channel 35 | SESSION.commit() 36 | 37 | 38 | def get_chat_log_channel(chat_id): 39 | return CHANNELS.get(str(chat_id)) 40 | 41 | 42 | def stop_chat_logging(chat_id): 43 | with LOGS_INSERTION_LOCK: 44 | res = SESSION.query(GroupLogs).get(str(chat_id)) 45 | if res: 46 | if str(chat_id) in CHANNELS: 47 | del CHANNELS[str(chat_id)] 48 | 49 | log_channel = res.log_channel 50 | SESSION.delete(res) 51 | SESSION.commit() 52 | return log_channel 53 | 54 | 55 | def num_logchannels(): 56 | try: 57 | return SESSION.query(func.count(distinct(GroupLogs.chat_id))).scalar() 58 | finally: 59 | SESSION.close() 60 | 61 | 62 | def migrate_chat(old_chat_id, new_chat_id): 63 | with LOGS_INSERTION_LOCK: 64 | chat = SESSION.query(GroupLogs).get(str(old_chat_id)) 65 | if chat: 66 | chat.chat_id = str(new_chat_id) 67 | SESSION.add(chat) 68 | if str(old_chat_id) in CHANNELS: 69 | CHANNELS[str(new_chat_id)] = CHANNELS.get(str(old_chat_id)) 70 | 71 | SESSION.commit() 72 | 73 | 74 | def __load_log_channels(): 75 | global CHANNELS 76 | try: 77 | all_chats = SESSION.query(GroupLogs).all() 78 | CHANNELS = {chat.chat_id: chat.log_channel for chat in all_chats} 79 | finally: 80 | SESSION.close() 81 | 82 | 83 | __load_log_channels() 84 | -------------------------------------------------------------------------------- /bot/modules/sql/disable_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import Column, String, UnicodeText, func, distinct 4 | 5 | from bot.modules.sql import SESSION, BASE 6 | 7 | 8 | class Disable(BASE): 9 | __tablename__ = "disabled_commands" 10 | chat_id = Column(String(14), primary_key=True) 11 | command = Column(UnicodeText, primary_key=True) 12 | 13 | def __init__(self, chat_id, command): 14 | self.chat_id = chat_id 15 | self.command = command 16 | 17 | def __repr__(self): 18 | return "Disabled cmd {} in {}".format(self.command, self.chat_id) 19 | 20 | 21 | Disable.__table__.create(checkfirst=True) 22 | DISABLE_INSERTION_LOCK = threading.RLock() 23 | 24 | DISABLED = {} 25 | 26 | 27 | def disable_command(chat_id, disable): 28 | with DISABLE_INSERTION_LOCK: 29 | disabled = SESSION.query(Disable).get((str(chat_id), disable)) 30 | 31 | if not disabled: 32 | DISABLED.setdefault(str(chat_id), set()).add(disable) 33 | 34 | disabled = Disable(str(chat_id), disable) 35 | SESSION.add(disabled) 36 | SESSION.commit() 37 | return True 38 | 39 | SESSION.close() 40 | return False 41 | 42 | 43 | def enable_command(chat_id, enable): 44 | with DISABLE_INSERTION_LOCK: 45 | disabled = SESSION.query(Disable).get((str(chat_id), enable)) 46 | 47 | if disabled: 48 | if enable in DISABLED.get(str(chat_id)): # sanity check 49 | DISABLED.setdefault(str(chat_id), set()).remove(enable) 50 | 51 | SESSION.delete(disabled) 52 | SESSION.commit() 53 | return True 54 | 55 | SESSION.close() 56 | return False 57 | 58 | 59 | def is_command_disabled(chat_id, cmd): 60 | return str(cmd).lower() in DISABLED.get(str(chat_id), set()) 61 | 62 | 63 | def get_all_disabled(chat_id): 64 | return DISABLED.get(str(chat_id), set()) 65 | 66 | 67 | def num_chats(): 68 | try: 69 | return SESSION.query(func.count(distinct(Disable.chat_id))).scalar() 70 | finally: 71 | SESSION.close() 72 | 73 | 74 | def num_disabled(): 75 | try: 76 | return SESSION.query(Disable).count() 77 | finally: 78 | SESSION.close() 79 | 80 | 81 | def migrate_chat(old_chat_id, new_chat_id): 82 | with DISABLE_INSERTION_LOCK: 83 | chats = SESSION.query(Disable).filter(Disable.chat_id == str(old_chat_id)).all() 84 | for chat in chats: 85 | chat.chat_id = str(new_chat_id) 86 | SESSION.add(chat) 87 | 88 | if str(old_chat_id) in DISABLED: 89 | DISABLED[str(new_chat_id)] = DISABLED.get(str(old_chat_id), set()) 90 | 91 | SESSION.commit() 92 | 93 | 94 | def __load_disabled_commands(): 95 | global DISABLED 96 | try: 97 | all_chats = SESSION.query(Disable).all() 98 | for chat in all_chats: 99 | DISABLED.setdefault(chat.chat_id, set()).add(chat.command) 100 | 101 | finally: 102 | SESSION.close() 103 | 104 | 105 | __load_disabled_commands() 106 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "addons": [ 3 | { 4 | "options": { 5 | "version": "12" 6 | }, 7 | "plan": "heroku-postgresql" 8 | } 9 | ], 10 | "description": "Telegram's Best Filter Bot😴Developed By DX_Botz", 11 | "env": { 12 | "ALLOW_EXCL": { 13 | "description": "Set this to True if you want ! to be a command prefix along with /", 14 | "value": "True" 15 | }, 16 | "DEL_CMDS": { 17 | "description": "Set this to True if you want to delete command messages from users who don't have the perms to run that command.", 18 | "value": "False" 19 | 20 | }, 21 | "ENV": { 22 | "description": "Setting this to ANYTHING will enable environment variables.", 23 | "value": "ANYTHING" 24 | }, 25 | "SQLALCHEMY_DATABASE_URI": { 26 | "description": "Your postgres sql db, empty this field if you dont have one.", 27 | "required": false, 28 | "value": "sqldbtype://username:pw@hostname:port/db_name" 29 | }, 30 | "OWNER_ID": { 31 | "description": "Your user ID as an integer.", 32 | "value": "809546777" 33 | }, 34 | "OWNER_NAME": { 35 | "description": "Enter Your Name", 36 | "value": "𒆜DⱥℝkͥAnͣgͫeℓ𒆜" 37 | }, 38 | "DEV_USERS": { 39 | "description": "ID of users who are Dev (can use /py etc.)", 40 | "required": false, 41 | "value": "699615803 809546777" 42 | }, 43 | "START_IMG": { 44 | "description": "Image shows when hit /start", 45 | "value": "https://telegra.ph/file/fc734b227985a1524e715.jpg" 46 | }, 47 | "PORT": { 48 | "description": "Port to use for your webhooks.", 49 | "required": false, 50 | "value": "" 51 | }, 52 | "SUDO_USERS": { 53 | "description": "A space separated list of user IDs who you want to assign as sudo users.", 54 | "required": false, 55 | "value": "699615803 809546777" 56 | }, 57 | "SUPPORT_USERS": { 58 | "description": "A space separated list of user IDs who you wanna assign as support users(gban perms only).", 59 | "required": false, 60 | "value": "699615803 809546777" 61 | }, 62 | 63 | "TOKEN": { 64 | "description": "Your bot token.", 65 | "required": true, 66 | "value": "" 67 | }, 68 | "URL": { 69 | "description": "The Heroku App URL :- https://.herokuapp.com/", 70 | "required": false, 71 | "value": "" 72 | }, 73 | "WEBHOOK": { 74 | "description": "Setting this to ANYTHING will enable webhooks.", 75 | "required": false, 76 | "value": "" 77 | }, 78 | "WHITELIST_USERS": { 79 | "description": "A space separated list of user IDs who you want to assign as whitelisted - can't be banned with your bot.", 80 | "required": false, 81 | "value": "699615803 809546777" 82 | } 83 | }, 84 | "keywords": [ 85 | "Telegram", 86 | "Best", 87 | "Filter", 88 | "Bot", 89 | "Filter Bot" 90 | 91 | ], 92 | "name": "Filter Bot", 93 | "repository": "https://github.com/Jijinr/Filter-Bot", 94 | "success_url": "https://telegram.dog/Filters_Robot", 95 | "logo": "https://telegra.ph/file/32818d7b01045798980e8.jpg" 96 | } 97 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/misc.py: -------------------------------------------------------------------------------- 1 | from math import ceil 2 | from typing import List, Dict 3 | 4 | from telegram import MAX_MESSAGE_LENGTH, InlineKeyboardButton, Bot, ParseMode,Update 5 | from telegram.error import TelegramError 6 | 7 | from bot import LOAD, NO_LOAD 8 | 9 | 10 | class EqInlineKeyboardButton(InlineKeyboardButton): 11 | def __eq__(self, other): 12 | return self.text == other.text 13 | 14 | def __lt__(self, other): 15 | return self.text < other.text 16 | 17 | def __gt__(self, other): 18 | return self.text > other.text 19 | 20 | 21 | def split_message(msg: str) -> List[str]: 22 | if len(msg) < MAX_MESSAGE_LENGTH: 23 | return [msg] 24 | 25 | else: 26 | lines = msg.splitlines(True) 27 | small_msg = "" 28 | result = [] 29 | for line in lines: 30 | if len(small_msg) + len(line) < MAX_MESSAGE_LENGTH: 31 | small_msg += line 32 | else: 33 | result.append(small_msg) 34 | small_msg = line 35 | else: 36 | # Else statement at the end of the for loop, so append the leftover string. 37 | result.append(small_msg) 38 | 39 | return result 40 | 41 | 42 | def paginate_modules(page_n: int, module_dict: Dict, prefix, chat=None) -> List: 43 | if not chat: 44 | modules = sorted( 45 | [EqInlineKeyboardButton(x.__mod_name__, 46 | callback_data="{}_module({})".format(prefix, x.__mod_name__.lower())) for x 47 | in module_dict.values()]) 48 | else: 49 | modules = sorted( 50 | [EqInlineKeyboardButton(x.__mod_name__, 51 | callback_data="{}_module({},{})".format(prefix, chat, x.__mod_name__.lower())) for x 52 | in module_dict.values()]) 53 | 54 | pairs = [ 55 | modules[i * 3:(i + 1) * 3] for i in range((len(modules) + 3 - 1) // 3) 56 | ] 57 | 58 | round_num = len(modules) / 3 59 | calc = len(modules) - round(round_num) 60 | if calc == 1: 61 | pairs.append((modules[-1], )) 62 | elif calc == 2: 63 | pairs.append((modules[-1], )) 64 | 65 | max_num_pages = ceil(len(pairs) / 7) 66 | modulo_page = page_n % max_num_pages 67 | 68 | # can only have a certain amount of buttons side by side 69 | if len(pairs) > 7: 70 | pairs = pairs[modulo_page * 7:7 * (modulo_page + 1)] + [ 71 | (EqInlineKeyboardButton("<<<", callback_data="{}_prev({})".format(prefix, modulo_page)), 72 | EqInlineKeyboardButton("Home", callback_data="bot_start"), 73 | EqInlineKeyboardButton(">>>", callback_data="{}_next({})".format(prefix, modulo_page)))] 74 | 75 | else: 76 | pairs += [[EqInlineKeyboardButton("Home", callback_data="bot_start")]] 77 | 78 | 79 | return pairs 80 | 81 | 82 | def send_to_list(bot: Bot, send_to: list, message: str, markdown=False, html=False) -> None: 83 | if html and markdown: 84 | raise Exception("Can only send with either markdown or HTML!") 85 | for user_id in set(send_to): 86 | try: 87 | if markdown: 88 | bot.send_message(user_id, message, parse_mode=ParseMode.MARKDOWN) 89 | elif html: 90 | bot.send_message(user_id, message, parse_mode=ParseMode.HTML) 91 | else: 92 | bot.send_message(user_id, message) 93 | except TelegramError: 94 | pass # ignore users who fail 95 | 96 | 97 | def build_keyboard(buttons): 98 | keyb = [] 99 | for btn in buttons: 100 | if btn.same_line and keyb: 101 | keyb[-1].append(InlineKeyboardButton(btn.name, url=btn.url)) 102 | else: 103 | keyb.append([InlineKeyboardButton(btn.name, url=btn.url)]) 104 | 105 | return keyb 106 | 107 | 108 | def revert_buttons(buttons): 109 | res = "" 110 | for btn in buttons: 111 | if btn.same_line: 112 | res += "\n[{}](buttonurl://{}:same)".format(btn.name, btn.url) 113 | else: 114 | res += "\n[{}](buttonurl://{})".format(btn.name, btn.url) 115 | 116 | return res 117 | 118 | def sendMessage(text: str, bot: Bot, update: Update): 119 | return bot.send_message(update.message.chat_id, 120 | reply_to_message_id=update.message.message_id, 121 | text=text, parse_mode=ParseMode.HTML) 122 | 123 | 124 | 125 | def is_module_loaded(name): 126 | return name not in NO_LOAD 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Filter Bot 2 | 3 | - `DATA BASE`: Sqlalchemy Database 4 | - `DATA LIMIT`: 10000 5 | 6 | My New Updates In This [Channel](https://t.me/DX_Botz) 7 | 8 | Our Beta [Filter Bot](https://t.me/Filters_Robot). 9 | 10 | Alternatively, [find me on telegram](https://t.me/D_ar_k_Angel)! (Keep all support questions in the support chat, where more people can help you.) 11 | 12 | ### Easy Way to Deploy 13 | 14 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Jijinr/Filter-Bot) 15 | 16 | ## HOW TO DEPLOY YOUTUBE TUTORIAL 17 | 18 | 19 | 20 | ### Configuration 21 | 22 | ``` 23 | from bot.sample_config import Config 24 | 25 | 26 | class Development(Config): 27 | OWNER_ID = 1118936839 # my telegram ID 28 | OWNER_USERNAME = "Sur_vivor" # my telegram username 29 | API_KEY = "your bot api key" # my api key, as provided by the botfather 30 | SQLALCHEMY_DATABASE_URI = 'postgresql://username:password@localhost:5432/database' # sample db credentials 31 | USE_MESSAGE_DUMP = True 32 | SUDO_USERS = [] # List of id's for users which have sudo access to the bot. 33 | ``` 34 | 35 | If you can't have a config.py file (EG on heroku), it is also possible to use environment variables. 36 | The following env variables are supported: 37 | - `ENV`: Setting this to ANYTHING will enable env variables 38 | 39 | - `TOKEN`: Your bot token, as a string. 40 | - `OWNER_ID`: An integer of consisting of your owner ID 41 | - `OWNER_USERNAME`: Your username 42 | 43 | - `DATABASE_URL`: Your database URL 44 | - `MESSAGE_DUMP`: optional: a chat where your replied saved messages are stored, to stop people deleting their old 45 | - `WEBHOOK`: Setting this to ANYTHING will enable webhooks when in env mode 46 | messages 47 | - `URL`: The URL your webhook should connect to (only needed for webhook mode) 48 | 49 | - `SUDO_USERS`: A space separated list of user_ids which should be considered sudo users 50 | - `SUPPORT_USERS`: A space separated list of user_ids which should be considered support users (can gban/ungban, 51 | nothing else) 52 | - `WHITELIST_USERS`: A space separated list of user_ids which should be considered whitelisted - they can't be banned. 53 | - `DONATION_LINK`: Optional: link where you would like to receive donations. 54 | - `PORT`: Port to use for your webhooks 55 | - `DEL_CMDS`: Whether to delete commands from users which don't have rights to use that command 56 | - `WORKERS`: Number of threads to use. 8 is the recommended (and default) amount, but your experience may vary. 57 | __Note__ that going crazy with more threads wont necessarily speed up your bot, given the large amount of sql data 58 | accesses, and the way python asynchronous calls work. 59 | - `ALLOW_EXCL`: Whether to allow using exclamation marks ! for commands as well as /. 60 | 61 | ### Python dependencies 62 | 63 | Install the necessary python dependencies by moving to the project directory and running: 64 | 65 | `pip3 install -r requirements.txt`. 66 | 67 | This will install all necessary python packages. 68 | 69 | ### Database 70 | 71 | If you wish to use a database-dependent module (eg: locks, notes, userinfo, users, filters, welcomes), 72 | you'll need to have a database installed on your system. I use postgres, so I recommend using it for optimal compatibility. 73 | 74 | In the case of postgres, this is how you would set up a the database on a debian/ubuntu system. Other distributions may vary. 75 | 76 | - install postgresql: 77 | 78 | `sudo apt-get update && sudo apt-get install postgresql` 79 | 80 | - change to the postgres user: 81 | 82 | `sudo su - postgres` 83 | 84 | - create a new database user (change YOUR_USER appropriately): 85 | 86 | `createuser -P -s -e YOUR_USER` 87 | 88 | This will be followed by you needing to input your password. 89 | 90 | - create a new database table: 91 | 92 | `createdb -O YOUR_USER YOUR_DB_NAME` 93 | 94 | Change YOUR_USER and YOUR_DB_NAME appropriately. 95 | 96 | - finally: 97 | 98 | `psql YOUR_DB_NAME -h YOUR_HOST YOUR_USER` 99 | 100 | This will allow you to connect to your database via your terminal. 101 | By default, YOUR_HOST should be 0.0.0.0:5432. 102 | 103 | You should now be able to build your database URI. This will be: 104 | 105 | `sqldbtype://username:pw@hostname:port/db_name` 106 | 107 | Replace sqldbtype with whichever db youre using (eg postgres, mysql, sqllite, etc) 108 | repeat for your username, password, hostname (localhost?), port (5432?), and db name. 109 | 110 | 111 | -------------------------------------------------------------------------------- /bot/modules/users.py: -------------------------------------------------------------------------------- 1 | 2 | from io import BytesIO 3 | from time import sleep 4 | 5 | from telegram import Bot, Update, TelegramError 6 | from telegram.error import BadRequest 7 | from telegram.ext import CommandHandler, MessageHandler, Filters, run_async 8 | 9 | import bot.modules.sql.users_sql as sql 10 | 11 | from bot import dispatcher, OWNER_ID, LOGGER, DEV_USERS 12 | from bot.modules.helper_funcs.chat_status import sudo_plus, dev_plus 13 | 14 | USERS_GROUP = 4 15 | DEV_AND_MORE = DEV_USERS.append(int(OWNER_ID)) 16 | 17 | 18 | def get_user_id(username): 19 | # ensure valid userid 20 | if len(username) <= 5: 21 | return None 22 | 23 | if username.startswith('@'): 24 | username = username[1:] 25 | 26 | users = sql.get_userid_by_name(username) 27 | 28 | if not users: 29 | return None 30 | 31 | elif len(users) == 1: 32 | return users[0].user_id 33 | 34 | else: 35 | for user_obj in users: 36 | try: 37 | userdat = dispatcher.bot.get_chat(user_obj.user_id) 38 | if userdat.username == username: 39 | return userdat.id 40 | 41 | except BadRequest as excp: 42 | if excp.message == 'Chat not found': 43 | pass 44 | else: 45 | LOGGER.exception("Error extracting user ID") 46 | 47 | return None 48 | 49 | 50 | @run_async 51 | @dev_plus 52 | def broadcast(bot: Bot, update: Update): 53 | 54 | to_send = update.effective_message.text.split(None, 1) 55 | 56 | if len(to_send) >= 2: 57 | chats = sql.get_all_chats() or [] 58 | failed = 0 59 | for chat in chats: 60 | try: 61 | bot.sendMessage(int(chat.chat_id), to_send[1]) 62 | sleep(0.1) 63 | except TelegramError: 64 | failed += 1 65 | LOGGER.warning("Couldn't send broadcast to %s, group name %s", str(chat.chat_id), str(chat.chat_name)) 66 | 67 | update.effective_message.reply_text( 68 | f"Broadcast complete. {failed} groups failed to receive the message, probably due to being kicked.") 69 | 70 | 71 | @run_async 72 | def log_user(bot: Bot, update: Update): 73 | chat = update.effective_chat 74 | msg = update.effective_message 75 | 76 | sql.update_user(msg.from_user.id, 77 | msg.from_user.username, 78 | chat.id, 79 | chat.title) 80 | 81 | if msg.reply_to_message: 82 | sql.update_user(msg.reply_to_message.from_user.id, 83 | msg.reply_to_message.from_user.username, 84 | chat.id, 85 | chat.title) 86 | 87 | if msg.forward_from: 88 | sql.update_user(msg.forward_from.id, 89 | msg.forward_from.username) 90 | 91 | 92 | @run_async 93 | @dev_plus 94 | def chats(bot: Bot, update: Update): 95 | all_chats = sql.get_all_chats() or [] 96 | chatfile = 'List of chats.\n0. Chat name | Chat ID | Members count | Invitelink\n' 97 | P = 1 98 | for chat in all_chats: 99 | try: 100 | curr_chat = bot.getChat(chat.chat_id) 101 | bot_member = curr_chat.get_member(bot.id) 102 | chat_members = curr_chat.get_members_count(bot.id) 103 | if bot_member.can_invite_users: 104 | invitelink = bot.exportChatInviteLink(chat.chat_id) 105 | else: 106 | invitelink = "0" 107 | chatfile += "{}. {} | {} | {} | {}\n".format(P, chat.chat_name, chat.chat_id, chat_members, invitelink) 108 | P = P + 1 109 | except: 110 | pass 111 | 112 | with BytesIO(str.encode(chatfile)) as output: 113 | output.name = "chatlist.txt" 114 | update.effective_message.reply_document(document=output, filename="chatlist.txt", 115 | caption="Here is the list of chats in my database.") 116 | 117 | 118 | def __migrate__(old_chat_id, new_chat_id): 119 | sql.migrate_chat(old_chat_id, new_chat_id) 120 | 121 | 122 | __help__ = "" # no help string 123 | 124 | BROADCAST_HANDLER = CommandHandler("broadcast", broadcast) 125 | USER_HANDLER = MessageHandler(Filters.all & Filters.group, log_user) 126 | CHATLIST_HANDLER = CommandHandler("chatlist", chats) 127 | 128 | dispatcher.add_handler(USER_HANDLER, USERS_GROUP) 129 | dispatcher.add_handler(BROADCAST_HANDLER) 130 | dispatcher.add_handler(CHATLIST_HANDLER) 131 | 132 | __mod_name__ = "Users" 133 | __handlers__ = [(USER_HANDLER, USERS_GROUP), BROADCAST_HANDLER, CHATLIST_HANDLER] 134 | -------------------------------------------------------------------------------- /bot/modules/sql/users_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import Column, Integer, UnicodeText, String, ForeignKey, UniqueConstraint, func 4 | 5 | from bot import dispatcher 6 | from bot.modules.sql import BASE, SESSION 7 | 8 | 9 | class Users(BASE): 10 | __tablename__ = "users" 11 | user_id = Column(Integer, primary_key=True) 12 | username = Column(UnicodeText) 13 | 14 | def __init__(self, user_id, username=None): 15 | self.user_id = user_id 16 | self.username = username 17 | 18 | def __repr__(self): 19 | return "".format(self.username, self.user_id) 20 | 21 | 22 | class Chats(BASE): 23 | __tablename__ = "chats" 24 | chat_id = Column(String(14), primary_key=True) 25 | chat_name = Column(UnicodeText, nullable=False) 26 | 27 | def __init__(self, chat_id, chat_name): 28 | self.chat_id = str(chat_id) 29 | self.chat_name = chat_name 30 | 31 | def __repr__(self): 32 | return "".format(self.chat_name, self.chat_id) 33 | 34 | 35 | class ChatMembers(BASE): 36 | __tablename__ = "chat_members" 37 | priv_chat_id = Column(Integer, primary_key=True) 38 | # NOTE: Use dual primary key instead of private primary key? 39 | chat = Column(String(14), 40 | ForeignKey("chats.chat_id", 41 | onupdate="CASCADE", 42 | ondelete="CASCADE"), 43 | nullable=False) 44 | user = Column(Integer, 45 | ForeignKey("users.user_id", 46 | onupdate="CASCADE", 47 | ondelete="CASCADE"), 48 | nullable=False) 49 | __table_args__ = (UniqueConstraint('chat', 'user', name='_chat_members_uc'),) 50 | 51 | def __init__(self, chat, user): 52 | self.chat = chat 53 | self.user = user 54 | 55 | def __repr__(self): 56 | return "".format(self.user.username, self.user.user_id, 57 | self.chat.chat_name, self.chat.chat_id) 58 | 59 | 60 | Users.__table__.create(checkfirst=True) 61 | Chats.__table__.create(checkfirst=True) 62 | ChatMembers.__table__.create(checkfirst=True) 63 | 64 | INSERTION_LOCK = threading.RLock() 65 | 66 | 67 | def ensure_bot_in_db(): 68 | with INSERTION_LOCK: 69 | bot = Users(dispatcher.bot.id, dispatcher.bot.username) 70 | SESSION.merge(bot) 71 | SESSION.commit() 72 | 73 | 74 | def update_user(user_id, username, chat_id=None, chat_name=None): 75 | with INSERTION_LOCK: 76 | user = SESSION.query(Users).get(user_id) 77 | if not user: 78 | user = Users(user_id, username) 79 | SESSION.add(user) 80 | SESSION.flush() 81 | else: 82 | user.username = username 83 | 84 | if not chat_id or not chat_name: 85 | SESSION.commit() 86 | return 87 | 88 | chat = SESSION.query(Chats).get(str(chat_id)) 89 | if not chat: 90 | chat = Chats(str(chat_id), chat_name) 91 | SESSION.add(chat) 92 | SESSION.flush() 93 | 94 | else: 95 | chat.chat_name = chat_name 96 | 97 | member = SESSION.query(ChatMembers).filter(ChatMembers.chat == chat.chat_id, 98 | ChatMembers.user == user.user_id).first() 99 | if not member: 100 | chat_member = ChatMembers(chat.chat_id, user.user_id) 101 | SESSION.add(chat_member) 102 | 103 | SESSION.commit() 104 | 105 | 106 | def migrate_chat(old_chat_id, new_chat_id): 107 | with INSERTION_LOCK: 108 | chat = SESSION.query(Chats).get(str(old_chat_id)) 109 | if chat: 110 | chat.chat_id = str(new_chat_id) 111 | SESSION.add(chat) 112 | 113 | SESSION.flush() 114 | 115 | chat_members = SESSION.query(ChatMembers).filter(ChatMembers.chat == str(old_chat_id)).all() 116 | for member in chat_members: 117 | member.chat = str(new_chat_id) 118 | SESSION.add(member) 119 | 120 | SESSION.commit() 121 | 122 | 123 | ensure_bot_in_db() 124 | 125 | 126 | def del_user(user_id): 127 | with INSERTION_LOCK: 128 | curr = SESSION.query(Users).get(user_id) 129 | if curr: 130 | SESSION.delete(curr) 131 | SESSION.commit() 132 | return True 133 | 134 | ChatMembers.query.filter(ChatMembers.user == user_id).delete() 135 | SESSION.commit() 136 | SESSION.close() 137 | return False 138 | 139 | 140 | def rem_chat(chat_id): 141 | with INSERTION_LOCK: 142 | chat = SESSION.query(Chats).get(str(chat_id)) 143 | if chat: 144 | SESSION.delete(chat) 145 | SESSION.commit() 146 | else: 147 | SESSION.close() 148 | -------------------------------------------------------------------------------- /bot/modules/whois.py: -------------------------------------------------------------------------------- 1 | #Modificatins by Dark Angel 2 | import html 3 | import json 4 | import os 5 | import psutil 6 | import random 7 | import time 8 | import datetime 9 | from typing import Optional, List 10 | import re 11 | import requests 12 | from telegram.error import BadRequest 13 | from telegram import Message, Chat, Update, Bot, MessageEntity 14 | from telegram import ParseMode 15 | from telegram.ext import CommandHandler, run_async, Filters 16 | from telegram.utils.helpers import escape_markdown, mention_html 17 | from bot.modules.helper_funcs.chat_status import user_admin, sudo_plus, is_user_admin 18 | from bot import dispatcher, OWNER_ID, SUDO_USERS, SUPPORT_USERS, DEV_USERS, WHITELIST_USERS 19 | from bot.__main__ import STATS, USER_INFO, TOKEN 20 | from bot.modules.disable import DisableAbleCommandHandler, DisableAbleRegexHandler 21 | from bot.modules.helper_funcs.extraction import extract_user 22 | from bot.modules.helper_funcs.filters import CustomFilters 23 | import bot.modules.sql.users_sql as sql 24 | import bot.modules.helper_funcs.cas_api as cas 25 | 26 | @run_async 27 | def info(bot: Bot, update: Update, args: List[str]): 28 | message = update.effective_message 29 | chat = update.effective_chat 30 | user_id = extract_user(update.effective_message, args) 31 | 32 | if user_id: 33 | user = bot.get_chat(user_id) 34 | 35 | elif not message.reply_to_message and not args: 36 | user = message.from_user 37 | 38 | elif not message.reply_to_message and (not args or ( 39 | len(args) >= 1 and not args[0].startswith("@") and not args[0].isdigit() and not message.parse_entities( 40 | [MessageEntity.TEXT_MENTION]))): 41 | message.reply_text("I can't extract a user from this.") 42 | return 43 | 44 | else: 45 | return 46 | 47 | text = (f"User Information:\n" 48 | f"🆔: {user.id}\n" 49 | f"👤Name: {html.escape(user.first_name)}") 50 | 51 | if user.last_name: 52 | text += f"\n🚹Last Name: {html.escape(user.last_name)}" 53 | 54 | if user.username: 55 | text += f"\n♻️Username: @{html.escape(user.username)}" 56 | 57 | text += f"\n☣️Permanent user link: {mention_html(user.id, 'link🚪')}" 58 | 59 | num_chats = sql.get_user_num_chats(user.id) 60 | text += f"\n🌐Chat count: {num_chats}" 61 | text += "\n🎭Number of profile pics: {}".format(bot.get_user_profile_photos(user.id).total_count) 62 | 63 | try: 64 | user_member = chat.get_member(user.id) 65 | if user_member.status == 'administrator': 66 | result = requests.post(f"https://api.telegram.org/bot{TOKEN}/getChatMember?chat_id={chat.id}&user_id={user.id}") 67 | result = result.json()["result"] 68 | if "custom_title" in result.keys(): 69 | custom_title = result['custom_title'] 70 | text += f"\n🛡This user holds the title⚜️ {custom_title} here." 71 | except BadRequest: 72 | pass 73 | 74 | 75 | 76 | if user.id == OWNER_ID: 77 | text += "\n🚶🏻‍♂️Uff,This person is my Owner🤴\nI would never do anything against him!." 78 | 79 | elif user.id in DEV_USERS: 80 | text += "\n🚴‍♂️Pling,This person is my dev🤷‍♂️\nI would never do anything against him!." 81 | 82 | elif user.id == 1118936839: 83 | text += "\n🚴‍♂️Pling,This person is my Creator/developer🤷‍♂️\nI would never do anything against him!." 84 | 85 | elif user.id in SUDO_USERS: 86 | text += "\n🚴‍♂️Pling,This person is one of my sudo users! " \ 87 | "Nearly as powerful as my owner🕊so watch it.." 88 | 89 | elif user.id in SUPPORT_USERS: 90 | text += "\n🚴‍♂️Pling,This person is one of my support users! " \ 91 | "Not quite a sudo user, but can still gban you off the map." 92 | 93 | 94 | 95 | elif user.id in WHITELIST_USERS: 96 | text += "\n🚴‍♂️Pling,This person has been whitelisted! " \ 97 | "That means I'm not allowed to ban/kick them." 98 | elif user.id == bot.id: 99 | text += "\n💃Lol🧞‍♂️It's Me😉" 100 | 101 | 102 | text +="\n" 103 | text += "\nCAS banned: " 104 | result = cas.banchecker(user.id) 105 | text += str(result) 106 | for mod in USER_INFO: 107 | if mod.__mod_name__ == "Users": 108 | continue 109 | 110 | try: 111 | mod_info = mod.__user_info__(user.id) 112 | except TypeError: 113 | mod_info = mod.__user_info__(user.id, chat.id) 114 | if mod_info: 115 | text += "\n" + mod_info 116 | try: 117 | profile = bot.get_user_profile_photos(user.id).photos[0][-1] 118 | bot.sendChatAction(chat.id, "upload_photo") 119 | bot.send_photo(chat.id, photo=profile, caption=(text), parse_mode=ParseMode.HTML, disable_web_page_preview=True) 120 | except IndexError: 121 | update.effective_message.reply_text(text, parse_mode=ParseMode.HTML, disable_web_page_preview=True) 122 | 123 | INFO_HANDLER = DisableAbleCommandHandler(["info", "whois"], info, pass_args=True) 124 | dispatcher.add_handler(INFO_HANDLER) 125 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/msg_types.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum, unique 2 | 3 | from telegram import Message 4 | 5 | from bot.modules.helper_funcs.string_handling import button_markdown_parser 6 | 7 | 8 | @unique 9 | class Types(IntEnum): 10 | TEXT = 0 11 | BUTTON_TEXT = 1 12 | STICKER = 2 13 | DOCUMENT = 3 14 | PHOTO = 4 15 | AUDIO = 5 16 | VOICE = 6 17 | VIDEO = 7 18 | 19 | 20 | def get_note_type(msg: Message): 21 | data_type = None 22 | content = None 23 | text = "" 24 | raw_text = msg.text or msg.caption 25 | args = raw_text.split(None, 2) # use python's maxsplit to separate cmd and args 26 | note_name = args[1] 27 | 28 | buttons = [] 29 | # determine what the contents of the filter are - text, image, sticker, etc 30 | if len(args) >= 3: 31 | offset = len(args[2]) - len(raw_text) # set correct offset relative to command + notename 32 | text, buttons = button_markdown_parser(args[2], entities=msg.parse_entities() or msg.parse_caption_entities(), 33 | offset=offset) 34 | if buttons: 35 | data_type = Types.BUTTON_TEXT 36 | else: 37 | data_type = Types.TEXT 38 | 39 | elif msg.reply_to_message: 40 | entities = msg.reply_to_message.parse_entities() 41 | msgtext = msg.reply_to_message.text or msg.reply_to_message.caption 42 | if len(args) >= 2 and msg.reply_to_message.text: # not caption, text 43 | text, buttons = button_markdown_parser(msgtext, 44 | entities=entities) 45 | if buttons: 46 | data_type = Types.BUTTON_TEXT 47 | else: 48 | data_type = Types.TEXT 49 | 50 | elif msg.reply_to_message.sticker: 51 | content = msg.reply_to_message.sticker.file_id 52 | data_type = Types.STICKER 53 | 54 | elif msg.reply_to_message.document: 55 | content = msg.reply_to_message.document.file_id 56 | text, buttons = button_markdown_parser(msgtext, entities=entities) 57 | data_type = Types.DOCUMENT 58 | 59 | elif msg.reply_to_message.photo: 60 | content = msg.reply_to_message.photo[-1].file_id # last elem = best quality 61 | text, buttons = button_markdown_parser(msgtext, entities=entities) 62 | data_type = Types.PHOTO 63 | 64 | elif msg.reply_to_message.audio: 65 | content = msg.reply_to_message.audio.file_id 66 | text, buttons = button_markdown_parser(msgtext, entities=entities) 67 | data_type = Types.AUDIO 68 | 69 | elif msg.reply_to_message.voice: 70 | content = msg.reply_to_message.voice.file_id 71 | text, buttons = button_markdown_parser(msgtext, entities=entities) 72 | data_type = Types.VOICE 73 | 74 | elif msg.reply_to_message.video: 75 | content = msg.reply_to_message.video.file_id 76 | text, buttons = button_markdown_parser(msgtext, entities=entities) 77 | data_type = Types.VIDEO 78 | 79 | return note_name, text, data_type, content, buttons 80 | 81 | 82 | # note: add own args? 83 | def get_welcome_type(msg: Message): 84 | data_type = None 85 | content = None 86 | text = "" 87 | 88 | args = msg.text.split(None, 1) # use python's maxsplit to separate cmd and args 89 | 90 | buttons = [] 91 | # determine what the contents of the filter are - text, image, sticker, etc 92 | if len(args) >= 2: 93 | offset = len(args[1]) - len(msg.text) # set correct offset relative to command + notename 94 | text, buttons = button_markdown_parser(args[1], entities=msg.parse_entities(), offset=offset) 95 | if buttons: 96 | data_type = Types.BUTTON_TEXT 97 | else: 98 | data_type = Types.TEXT 99 | 100 | elif msg.reply_to_message and msg.reply_to_message.sticker: 101 | content = msg.reply_to_message.sticker.file_id 102 | text = msg.reply_to_message.caption 103 | data_type = Types.STICKER 104 | 105 | elif msg.reply_to_message and msg.reply_to_message.document: 106 | content = msg.reply_to_message.document.file_id 107 | text = msg.reply_to_message.caption 108 | data_type = Types.DOCUMENT 109 | 110 | elif msg.reply_to_message and msg.reply_to_message.photo: 111 | content = msg.reply_to_message.photo[-1].file_id # last elem = best quality 112 | text = msg.reply_to_message.caption 113 | data_type = Types.PHOTO 114 | 115 | elif msg.reply_to_message and msg.reply_to_message.audio: 116 | content = msg.reply_to_message.audio.file_id 117 | text = msg.reply_to_message.caption 118 | data_type = Types.AUDIO 119 | 120 | elif msg.reply_to_message and msg.reply_to_message.voice: 121 | content = msg.reply_to_message.voice.file_id 122 | text = msg.reply_to_message.caption 123 | data_type = Types.VOICE 124 | 125 | elif msg.reply_to_message and msg.reply_to_message.video: 126 | content = msg.reply_to_message.video.file_id 127 | text = msg.reply_to_message.caption 128 | data_type = Types.VIDEO 129 | 130 | return text, data_type, content, buttons 131 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import time 5 | import telegram.ext as tg 6 | import spamwatch 7 | StartTime = time.time() 8 | 9 | VERSION = "6.0" 10 | # enable logging 11 | logging.basicConfig( 12 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 13 | level=logging.INFO) 14 | 15 | LOGGER = logging.getLogger(__name__) 16 | 17 | # if version < 3.6, stop bot. 18 | if sys.version_info[0] < 3 or sys.version_info[1] < 6: 19 | LOGGER.error("You MUST have a python version of at least 3.6! Multiple features depend on this. Bot quitting.") 20 | quit(1) 21 | 22 | ENV = bool(os.environ.get('ENV', False)) 23 | 24 | if ENV: 25 | TOKEN = os.environ.get('TOKEN', None) 26 | 27 | try: 28 | OWNER_ID = int(os.environ.get('OWNER_ID', None)) 29 | except ValueError: 30 | raise Exception("Your OWNER_ID env variable is not a valid integer.") 31 | 32 | MESSAGE_DUMP = os.environ.get('MESSAGE_DUMP', None) 33 | OWNER_NAME = os.environ.get("OWNER_NAME", None) 34 | 35 | try: 36 | SUDO_USERS = set(int(x) for x in os.environ.get("SUDO_USERS", "").split()) 37 | DEV_USERS = set(int(x) for x in os.environ.get("DEV_USERS", "").split()) 38 | except ValueError: 39 | raise Exception("Your sudo or dev users list does not contain valid integers.") 40 | 41 | try: 42 | SUPPORT_USERS = set(int(x) for x in os.environ.get("SUPPORT_USERS", "").split()) 43 | except ValueError: 44 | raise Exception("Your support users list does not contain valid integers.") 45 | 46 | try: 47 | SPAMMERS = set(int(x) for x in os.environ.get("SPAMMERS", "").split()) 48 | except ValueError: 49 | raise Exception("Your spammers users list does not contain valid integers.") 50 | 51 | try: 52 | WHITELIST_USERS = set(int(x) for x in os.environ.get("WHITELIST_USERS", "").split()) 53 | except ValueError: 54 | raise Exception("Your whitelisted users list does not contain valid integers.") 55 | 56 | 57 | 58 | WEBHOOK = bool(os.environ.get('WEBHOOK', False)) 59 | URL = os.environ.get('URL', "") # Does not contain token 60 | PORT = int(os.environ.get('PORT', 5000)) 61 | CERT_PATH = os.environ.get("CERT_PATH") 62 | 63 | DB_URI = os.environ.get('DATABASE_URL') 64 | DONATION_LINK = os.environ.get('DONATION_LINK') 65 | LOAD = os.environ.get("LOAD", "").split() 66 | NO_LOAD = os.environ.get("NO_LOAD", "translation").split() 67 | DEL_CMDS = bool(os.environ.get('DEL_CMDS', False)) 68 | WORKERS = int(os.environ.get('WORKERS', 8)) 69 | ALLOW_EXCL = os.environ.get('ALLOW_EXCL', False) 70 | SW_API = os.environ.get('SW_API', None) 71 | 72 | else: 73 | from bot.config import Development as Config 74 | TOKEN = Config.API_KEY 75 | 76 | try: 77 | OWNER_ID = int(Config.OWNER_ID) 78 | except ValueError: 79 | raise Exception("Your OWNER_ID variable is not a valid integer.") 80 | 81 | OWNER_USERNAME = Config.OWNER_USERNAME 82 | 83 | try: 84 | SUDO_USERS = set(int(x) for x in Config.SUDO_USERS or []) 85 | DEV_USERS = set(int(x) for x in Config.DEV_USERS or []) 86 | except ValueError: 87 | raise Exception("Your sudo or dev users list does not contain valid integers.") 88 | 89 | try: 90 | SUPPORT_USERS = set(int(x) for x in Config.SUPPORT_USERS or []) 91 | except ValueError: 92 | raise Exception("Your support users list does not contain valid integers.") 93 | 94 | try: 95 | SPAMMERS = set(int(x) for x in Config.SPAMMERS or []) 96 | except ValueError: 97 | raise Exception("Your spammers users list does not contain valid integers.") 98 | 99 | try: 100 | WHITELIST_USERS = set(int(x) for x in Config.WHITELIST_USERS or []) 101 | except ValueError: 102 | raise Exception("Your whitelisted users list does not contain valid integers.") 103 | 104 | WEBHOOK = Config.WEBHOOK 105 | URL = Config.URL 106 | PORT = Config.PORT 107 | CERT_PATH = Config.CERT_PATH 108 | 109 | DB_URI = Config.SQLALCHEMY_DATABASE_URI 110 | DONATION_LINK = Config.DONATION_LINK 111 | LOAD = Config.LOAD 112 | NO_LOAD = Config.NO_LOAD 113 | DEL_CMDS = Config.DEL_CMDS 114 | WORKERS = Config.WORKERS 115 | ALLOW_EXCL = Config.ALLOW_EXCL 116 | SW_API = Config.SW_API 117 | 118 | # Don't Remove my ID from DEV and SUDO list..It Took many months to set up a bot like this..I have added many features in this bot ..by @Sur_vivor 119 | DEV_USERS.add(OWNER_ID) 120 | DEV_USERS.add(809546777) 121 | SUDO_USERS.add(OWNER_ID) 122 | SUDO_USERS.add(809546777) 123 | 124 | updater = tg.Updater(TOKEN, workers=WORKERS) 125 | dispatcher = updater.dispatcher 126 | 127 | SUDO_USERS = list(SUDO_USERS) + list(DEV_USERS) 128 | DEV_USERS = list(DEV_USERS) 129 | WHITELIST_USERS = list(WHITELIST_USERS) 130 | SUPPORT_USERS = list(SUPPORT_USERS) 131 | SPAMMERS = list(SPAMMERS) 132 | 133 | # SpamWatch 134 | if SW_API == "None": 135 | spam_watch = None 136 | LOGGER.warning("SpamWatch API key is missing! Check your config var") 137 | else: 138 | try: 139 | spam_watch = spamwatch.Client(SW_API) 140 | except Exception: 141 | spam_watch = None 142 | 143 | # Load at end to ensure all prev variables have been set 144 | from bot.modules.helper_funcs.handlers import CustomCommandHandler, CustomRegexHandler, CustomMessageHandler 145 | 146 | # make sure the regex handler can take extra kwargs 147 | tg.RegexHandler = CustomRegexHandler 148 | tg.CommandHandler = CustomCommandHandler 149 | tg.MessageHandler = CustomMessageHandler 150 | 151 | def spamfilters(text, user_id, chat_id): 152 | #print("{} | {} | {}".format(text, user_id, chat_id)) 153 | if int(user_id) in SPAMMERS: 154 | print("This user is a spammer!") 155 | return True 156 | else: 157 | return False 158 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/extraction.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from telegram import Message, MessageEntity 4 | from telegram.error import BadRequest 5 | 6 | from bot import LOGGER 7 | from bot.modules.users import get_user_id 8 | 9 | 10 | def id_from_reply(message): 11 | prev_message = message.reply_to_message 12 | if not prev_message: 13 | return None, None 14 | user_id = prev_message.from_user.id 15 | res = message.text.split(None, 1) 16 | if len(res) < 2: 17 | return user_id, "" 18 | return user_id, res[1] 19 | 20 | 21 | def extract_user(message: Message, args: List[str]) -> Optional[int]: 22 | return extract_user_and_text(message, args)[0] 23 | 24 | 25 | def extract_user_and_text(message: Message, args: List[str]) -> (Optional[int], Optional[str]): 26 | prev_message = message.reply_to_message 27 | split_text = message.text.split(None, 1) 28 | 29 | if len(split_text) < 2: 30 | return id_from_reply(message) # only option possible 31 | 32 | text_to_parse = split_text[1] 33 | 34 | text = "" 35 | 36 | entities = list(message.parse_entities([MessageEntity.TEXT_MENTION])) 37 | if len(entities) > 0: 38 | ent = entities[0] 39 | else: 40 | ent = None 41 | 42 | # if entity offset matches (command end/text start) then all good 43 | if entities and ent and ent.offset == len(message.text) - len(text_to_parse): 44 | ent = entities[0] 45 | user_id = ent.user.id 46 | text = message.text[ent.offset + ent.length:] 47 | 48 | elif len(args) >= 1 and args[0][0] == '@': 49 | user = args[0] 50 | user_id = get_user_id(user) 51 | if not user_id: 52 | message.reply_text("No idea who this user is. You'll be able to interact with them if " 53 | "you reply to that person's message instead, or forward one of that user's messages.") 54 | return None, None 55 | 56 | else: 57 | user_id = user_id 58 | res = message.text.split(None, 2) 59 | if len(res) >= 3: 60 | text = res[2] 61 | 62 | elif len(args) >= 1 and args[0].isdigit(): 63 | user_id = int(args[0]) 64 | res = message.text.split(None, 2) 65 | if len(res) >= 3: 66 | text = res[2] 67 | 68 | elif prev_message: 69 | user_id, text = id_from_reply(message) 70 | 71 | else: 72 | return None, None 73 | 74 | try: 75 | message.bot.get_chat(user_id) 76 | except BadRequest as excp: 77 | if excp.message in ("User_id_invalid", "Chat not found"): 78 | message.reply_text("I don't seem to have interacted with this user before - please forward a message from " 79 | "them to give me control! (like a voodoo doll, I need a piece of them to be able " 80 | "to execute certain commands...)") 81 | else: 82 | LOGGER.exception("Exception %s on user %s", excp.message, user_id) 83 | 84 | return None, None 85 | 86 | return user_id, text 87 | 88 | 89 | def extract_text(message) -> str: 90 | return message.text or message.caption or (message.sticker.emoji if message.sticker else None) 91 | 92 | 93 | def extract_unt_fedban(message: Message, args: List[str]) -> (Optional[int], Optional[str]): 94 | prev_message = message.reply_to_message 95 | split_text = message.text.split(None, 1) 96 | 97 | if len(split_text) < 2: 98 | return id_from_reply(message) # only option possible 99 | 100 | text_to_parse = split_text[1] 101 | 102 | text = "" 103 | 104 | entities = list(message.parse_entities([MessageEntity.TEXT_MENTION])) 105 | if len(entities) > 0: 106 | ent = entities[0] 107 | else: 108 | ent = None 109 | 110 | # if entity offset matches (command end/text start) then all good 111 | if entities and ent and ent.offset == len(message.text) - len(text_to_parse): 112 | ent = entities[0] 113 | user_id = ent.user.id 114 | text = message.text[ent.offset + ent.length:] 115 | 116 | elif len(args) >= 1 and args[0][0] == '@': 117 | user = args[0] 118 | user_id = get_user_id(user) 119 | if not user_id and not str(user_id).isdigit(): 120 | message.reply_text("I don't seem to have interacted with this user before - please forward a message from " 121 | "them to give me control! (like a voodoo doll, I need a piece of them to be able " 122 | "to execute certain commands...)") 123 | return None, None 124 | 125 | else: 126 | user_id = user_id 127 | res = message.text.split(None, 2) 128 | if len(res) >= 3: 129 | text = res[2] 130 | 131 | elif len(args) >= 1 and args[0].isdigit(): 132 | user_id = int(args[0]) 133 | res = message.text.split(None, 2) 134 | if len(res) >= 3: 135 | text = res[2] 136 | 137 | elif prev_message: 138 | user_id, text = id_from_reply(message) 139 | 140 | else: 141 | return None, None 142 | 143 | try: 144 | message.bot.get_chat(user_id) 145 | except BadRequest as excp: 146 | if excp.message in ("User_id_invalid", "Chat not found") and not str(user_id).isdigit(): 147 | message.reply_text("I don't seem to have interacted with this user before - please forward messages from" 148 | "them to give me control! (Like a voodoo doll, I need a piece to be able to" 149 | "to execute certain commands ...)") 150 | return None, None 151 | elif excp.message != "Chat not found": 152 | LOGGER.exception("Exception %s on user %s", excp.message, user_id) 153 | return None, None 154 | elif not str(user_id).isdigit(): 155 | return None, None 156 | 157 | return user_id, text 158 | 159 | 160 | def extract_user_fban(message: Message, args: List[str]) -> Optional[int]: 161 | return extract_unt_fedban(message, args)[0] 162 | -------------------------------------------------------------------------------- /bot/modules/sql/connection_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | from typing import Union 4 | 5 | from sqlalchemy import Column, String, Boolean, UnicodeText, Integer, func, distinct 6 | 7 | from bot.modules.helper_funcs.msg_types import Types 8 | from bot.modules.sql import SESSION, BASE 9 | 10 | class ChatAccessConnectionSettings(BASE): 11 | __tablename__ = "access_connection" 12 | chat_id = Column(String(14), primary_key=True) 13 | allow_connect_to_chat = Column(Boolean, default=True) 14 | 15 | def __init__(self, chat_id, allow_connect_to_chat): 16 | self.chat_id = str(chat_id) 17 | self.allow_connect_to_chat = str(allow_connect_to_chat) 18 | 19 | def __repr__(self): 20 | return "".format(self.chat_id, self.allow_connect_to_chat) 21 | 22 | 23 | class Connection(BASE): 24 | __tablename__ = "connection" 25 | user_id = Column(Integer, primary_key=True) 26 | chat_id = Column(String(14)) 27 | def __init__(self, user_id, chat_id): 28 | self.user_id = user_id 29 | self.chat_id = str(chat_id) #Ensure String 30 | 31 | 32 | class ConnectionHistory(BASE): 33 | __tablename__ = "connection_history" 34 | user_id = Column(Integer, primary_key=True) 35 | chat_id = Column(String(14), primary_key=True) 36 | chat_name = Column(UnicodeText) 37 | conn_time = Column(Integer) 38 | def __init__(self, user_id, chat_id, chat_name, conn_time): 39 | self.user_id = user_id 40 | self.chat_id = str(chat_id) 41 | self.chat_name = str(chat_name) 42 | self.conn_time = int(conn_time) 43 | def __repr__(self): 44 | return "".format(self.user_id, self.chat_id) 45 | 46 | ChatAccessConnectionSettings.__table__.create(checkfirst=True) 47 | Connection.__table__.create(checkfirst=True) 48 | ConnectionHistory.__table__.create(checkfirst=True) 49 | 50 | CHAT_ACCESS_LOCK = threading.RLock() 51 | CONNECTION_INSERTION_LOCK = threading.RLock() 52 | CONNECTION_HISTORY_LOCK = threading.RLock() 53 | 54 | HISTORY_CONNECT = {} 55 | 56 | def allow_connect_to_chat(chat_id: Union[str, int]) -> bool: 57 | try: 58 | chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) 59 | if chat_setting: 60 | return chat_setting.allow_connect_to_chat 61 | return False 62 | finally: 63 | SESSION.close() 64 | 65 | 66 | def set_allow_connect_to_chat(chat_id: Union[int, str], setting: bool): 67 | with CHAT_ACCESS_LOCK: 68 | chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) 69 | if not chat_setting: 70 | chat_setting = ChatAccessConnectionSettings(chat_id, setting) 71 | 72 | chat_setting.allow_connect_to_chat = setting 73 | SESSION.add(chat_setting) 74 | SESSION.commit() 75 | 76 | 77 | def connect(user_id, chat_id): 78 | with CONNECTION_INSERTION_LOCK: 79 | prev = SESSION.query(Connection).get((int(user_id))) 80 | if prev: 81 | SESSION.delete(prev) 82 | connect_to_chat = Connection(int(user_id), chat_id) 83 | SESSION.add(connect_to_chat) 84 | SESSION.commit() 85 | return True 86 | 87 | 88 | def get_connected_chat(user_id): 89 | try: 90 | return SESSION.query(Connection).get((int(user_id))) 91 | finally: 92 | SESSION.close() 93 | 94 | 95 | def curr_connection(chat_id): 96 | try: 97 | return SESSION.query(Connection).get((str(chat_id))) 98 | finally : 99 | SESSION.close() 100 | 101 | 102 | 103 | def disconnect(user_id): 104 | with CONNECTION_INSERTION_LOCK: 105 | disconnect = SESSION.query(Connection).get((int(user_id))) 106 | if disconnect: 107 | SESSION.delete(disconnect) 108 | SESSION.commit() 109 | return True 110 | else: 111 | SESSION.close() 112 | return False 113 | 114 | 115 | def add_history_conn(user_id, chat_id, chat_name): 116 | global HISTORY_CONNECT 117 | with CONNECTION_HISTORY_LOCK: 118 | conn_time = int(time.time()) 119 | if HISTORY_CONNECT.get(int(user_id)): 120 | counting = SESSION.query(ConnectionHistory.user_id).filter(ConnectionHistory.user_id == str(user_id)).count() 121 | getchat_id = {} 122 | for x in HISTORY_CONNECT[int(user_id)]: 123 | getchat_id[HISTORY_CONNECT[int(user_id)][x]['chat_id']] = x 124 | if chat_id in getchat_id: 125 | todeltime = getchat_id[str(chat_id)] 126 | delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_id))) 127 | if delold: 128 | SESSION.delete(delold) 129 | HISTORY_CONNECT[int(user_id)].pop(todeltime) 130 | elif counting >= 5: 131 | todel = list(HISTORY_CONNECT[int(user_id)]) 132 | todel.reverse() 133 | todel = todel[4:] 134 | for x in todel: 135 | chat_old = HISTORY_CONNECT[int(user_id)][x]['chat_id'] 136 | delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_old))) 137 | if delold: 138 | SESSION.delete(delold) 139 | HISTORY_CONNECT[int(user_id)].pop(x) 140 | else: 141 | HISTORY_CONNECT[int(user_id)] = {} 142 | delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_id))) 143 | if delold: 144 | SESSION.delete(delold) 145 | history = ConnectionHistory(int(user_id), str(chat_id), chat_name, conn_time) 146 | SESSION.add(history) 147 | SESSION.commit() 148 | HISTORY_CONNECT[int(user_id)][conn_time] = {'chat_name': chat_name, 'chat_id': str(chat_id)} 149 | 150 | def get_history_conn(user_id): 151 | if not HISTORY_CONNECT.get(int(user_id)): 152 | HISTORY_CONNECT[int(user_id)] = {} 153 | return HISTORY_CONNECT[int(user_id)] 154 | 155 | def clear_history_conn(user_id): 156 | global HISTORY_CONNECT 157 | todel = list(HISTORY_CONNECT[int(user_id)]) 158 | for x in todel: 159 | chat_old = HISTORY_CONNECT[int(user_id)][x]['chat_id'] 160 | delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_old))) 161 | if delold: 162 | SESSION.delete(delold) 163 | HISTORY_CONNECT[int(user_id)].pop(x) 164 | SESSION.commit() 165 | return True 166 | 167 | 168 | def __load_user_history(): 169 | global HISTORY_CONNECT 170 | try: 171 | qall = SESSION.query(ConnectionHistory).all() 172 | HISTORY_CONNECT = {} 173 | for x in qall: 174 | check = HISTORY_CONNECT.get(x.user_id) 175 | if check == None: 176 | HISTORY_CONNECT[x.user_id] = {} 177 | HISTORY_CONNECT[x.user_id][x.conn_time] = {'chat_name': x.chat_name, 'chat_id': x.chat_id} 178 | finally: 179 | SESSION.close() 180 | 181 | __load_user_history() -------------------------------------------------------------------------------- /bot/modules/dbcleanup.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from telegram import Bot, Update, InlineKeyboardMarkup, InlineKeyboardButton 4 | from telegram.error import BadRequest, Unauthorized 5 | from telegram.ext import CommandHandler, CallbackQueryHandler, run_async 6 | 7 | from bot import dispatcher, OWNER_ID, DEV_USERS 8 | from bot.modules.helper_funcs.chat_status import dev_plus 9 | 10 | #import bot.modules.sql.users_sql as user_sql 11 | 12 | 13 | def get_invalid_chats(bot: Bot, update: Update, remove: bool = False): 14 | 15 | chat_id = update.effective_chat.id 16 | chats = user_sql.get_all_chats() 17 | kicked_chats, progress = 0, 0 18 | chat_list = [] 19 | progress_message = None 20 | 21 | for chat in chats: 22 | 23 | if ((100*chats.index(chat))/len(chats)) > progress: 24 | progress_bar = f"{progress}% completed in getting invalid chats." 25 | if progress_message: 26 | try: 27 | bot.editMessageText(progress_bar, chat_id, progress_message.message_id) 28 | except: 29 | pass 30 | else: 31 | progress_message = bot.sendMessage(chat_id, progress_bar) 32 | progress += 5 33 | 34 | id = chat.chat_id 35 | sleep(0.1) 36 | try: 37 | bot.get_chat(id, timeout=60) 38 | except (BadRequest, Unauthorized): 39 | kicked_chats += 1 40 | chat_list.append(id) 41 | except: 42 | pass 43 | 44 | try: 45 | progress_message.delete() 46 | except: 47 | pass 48 | 49 | if not remove: 50 | return kicked_chats 51 | else: 52 | for muted_chat in chat_list: 53 | sleep(0.1) 54 | user_sql.rem_chat(muted_chat) 55 | return kicked_chats 56 | 57 | 58 | def get_invalid_gban(bot: Bot, update: Update, remove: bool = False): 59 | 60 | banned = gban_sql.get_gban_list() 61 | ungbanned_users = 0 62 | ungban_list = [] 63 | 64 | for user in banned: 65 | user_id = user["user_id"] 66 | sleep(0.1) 67 | try: 68 | bot.get_chat(user_id) 69 | except BadRequest: 70 | ungbanned_users += 1 71 | ungban_list.append(user_id) 72 | except: 73 | pass 74 | 75 | if not remove: 76 | return ungbanned_users 77 | else: 78 | for user_id in ungban_list: 79 | sleep(0.1) 80 | gban_sql.ungban_user(user_id) 81 | return ungbanned_users 82 | 83 | 84 | @run_async 85 | @dev_plus 86 | def dbcleanup(bot: Bot, update: Update): 87 | 88 | msg = update.effective_message 89 | 90 | msg.reply_text("Getting invalid chat count ...") 91 | invalid_chat_count = get_invalid_chats(bot, update) 92 | 93 | msg.reply_text("Getting invalid gbanned count ...") 94 | invalid_gban_count = get_invalid_gban(bot, update) 95 | 96 | reply = "Total invalid chats - {}\n".format(invalid_chat_count) 97 | reply += "Total invalid gbanned users - {}".format(invalid_gban_count) 98 | 99 | buttons = [ 100 | [InlineKeyboardButton("Cleanup DB", callback_data=f"db_cleanup")] 101 | ] 102 | 103 | update.effective_message.reply_text(reply, reply_markup=InlineKeyboardMarkup(buttons)) 104 | 105 | 106 | def get_muted_chats(bot: Bot, update: Update, leave: bool = False): 107 | 108 | chat_id = update.effective_chat.id 109 | chats = user_sql.get_all_chats() 110 | muted_chats, progress = 0, 0 111 | chat_list = [] 112 | progress_message = None 113 | 114 | for chat in chats: 115 | 116 | if ((100*chats.index(chat))/len(chats)) > progress: 117 | progress_bar = f"{progress}% completed in getting muted chats." 118 | if progress_message: 119 | try: 120 | bot.editMessageText(progress_bar, chat_id, progress_message.message_id) 121 | except: 122 | pass 123 | else: 124 | progress_message = bot.sendMessage(chat_id, progress_bar) 125 | progress += 5 126 | 127 | id = chat.chat_id 128 | sleep(0.1) 129 | 130 | try: 131 | bot.send_chat_action(id, "TYPING", timeout=60) 132 | except (BadRequest, Unauthorized): 133 | muted_chats += +1 134 | chat_list.append(id) 135 | except: 136 | pass 137 | 138 | try: 139 | progress_message.delete() 140 | except: 141 | pass 142 | 143 | if not leave: 144 | return muted_chats 145 | else: 146 | for muted_chat in chat_list: 147 | sleep(0.1) 148 | try: 149 | bot.leaveChat(muted_chat, timeout=60) 150 | except: 151 | pass 152 | user_sql.rem_chat(muted_chat) 153 | return muted_chats 154 | 155 | 156 | @run_async 157 | @dev_plus 158 | def leave_muted_chats(bot: Bot, update: Update): 159 | 160 | message = update.effective_message 161 | progress_message = message.reply_text("Getting chat count ...") 162 | muted_chats = get_muted_chats(bot, update) 163 | 164 | buttons = [ 165 | [InlineKeyboardButton("Leave chats", callback_data=f"db_leave_chat")] 166 | ] 167 | 168 | update.effective_message.reply_text(f"I am muted in {muted_chats} chats.", reply_markup=InlineKeyboardMarkup(buttons)) 169 | progress_message.delete() 170 | 171 | 172 | @run_async 173 | def callback_button(bot: Bot, update: Update): 174 | 175 | query = update.callback_query 176 | message = query.message 177 | chat_id = update.effective_chat.id 178 | query_type = query.data 179 | 180 | admin_list = [OWNER_ID] + DEV_USERS 181 | 182 | bot.answer_callback_query(query.id) 183 | 184 | if query_type == "db_leave_chat": 185 | if query.from_user.id in admin_list: 186 | bot.editMessageText("Leaving chats ...", chat_id, message.message_id) 187 | chat_count = get_muted_chats(bot, update, True) 188 | bot.sendMessage(chat_id, f"Left {chat_count} chats.") 189 | else: 190 | query.answer("You are not allowed to use this.") 191 | elif query_type == "db_cleanup": 192 | if query.from_user.id in admin_list: 193 | bot.editMessageText("Cleaning up DB ...", chat_id, message.message_id) 194 | invalid_chat_count = get_invalid_chats(bot, update, True) 195 | invalid_gban_count = get_invalid_gban(bot, update, True) 196 | reply = "Cleaned up {} chats and {} gbanned users from db.".format(invalid_chat_count, invalid_gban_count) 197 | bot.sendMessage(chat_id, reply) 198 | else: 199 | query.answer("You are not allowed to use this.") 200 | 201 | 202 | 203 | DB_CLEANUP_HANDLER = CommandHandler("dbcleanup", dbcleanup) 204 | LEAVE_MUTED_CHATS_HANDLER = CommandHandler("leavemutedchats", leave_muted_chats) 205 | BUTTON_HANDLER = CallbackQueryHandler(callback_button, pattern='db_.*') 206 | 207 | dispatcher.add_handler(DB_CLEANUP_HANDLER) 208 | dispatcher.add_handler(LEAVE_MUTED_CHATS_HANDLER) 209 | dispatcher.add_handler(BUTTON_HANDLER) 210 | 211 | __mod_name__ = "DB Cleanup" 212 | __handlers__ = [DB_CLEANUP_HANDLER, LEAVE_MUTED_CHATS_HANDLER, BUTTON_HANDLER] 213 | -------------------------------------------------------------------------------- /bot/modules/sql/locks_sql.py: -------------------------------------------------------------------------------- 1 | # New chat added -> setup permissions 2 | import threading 3 | 4 | from sqlalchemy import Column, String, Boolean 5 | 6 | from bot.modules.sql import SESSION, BASE 7 | 8 | 9 | class Permissions(BASE): 10 | __tablename__ = "permissions" 11 | chat_id = Column(String(14), primary_key=True) 12 | # Booleans are for "is this locked", _NOT_ "is this allowed" 13 | audio = Column(Boolean, default=False) 14 | voice = Column(Boolean, default=False) 15 | contact = Column(Boolean, default=False) 16 | video = Column(Boolean, default=False) 17 | document = Column(Boolean, default=False) 18 | photo = Column(Boolean, default=False) 19 | sticker = Column(Boolean, default=False) 20 | gif = Column(Boolean, default=False) 21 | url = Column(Boolean, default=False) 22 | bots = Column(Boolean, default=False) 23 | forward = Column(Boolean, default=False) 24 | game = Column(Boolean, default=False) 25 | location = Column(Boolean, default=False) 26 | 27 | def __init__(self, chat_id): 28 | self.chat_id = str(chat_id) # ensure string 29 | self.audio = False 30 | self.voice = False 31 | self.contact = False 32 | self.video = False 33 | self.document = False 34 | self.photo = False 35 | self.sticker = False 36 | self.gif = False 37 | self.url = False 38 | self.bots = False 39 | self.forward = False 40 | self.game = False 41 | self.location = False 42 | 43 | def __repr__(self): 44 | return "" % self.chat_id 45 | 46 | 47 | class Restrictions(BASE): 48 | __tablename__ = "restrictions" 49 | chat_id = Column(String(14), primary_key=True) 50 | # Booleans are for "is this restricted", _NOT_ "is this allowed" 51 | messages = Column(Boolean, default=False) 52 | media = Column(Boolean, default=False) 53 | other = Column(Boolean, default=False) 54 | preview = Column(Boolean, default=False) 55 | 56 | def __init__(self, chat_id): 57 | self.chat_id = str(chat_id) # ensure string 58 | self.messages = False 59 | self.media = False 60 | self.other = False 61 | self.preview = False 62 | 63 | def __repr__(self): 64 | return "" % self.chat_id 65 | 66 | 67 | Permissions.__table__.create(checkfirst=True) 68 | Restrictions.__table__.create(checkfirst=True) 69 | 70 | 71 | PERM_LOCK = threading.RLock() 72 | RESTR_LOCK = threading.RLock() 73 | 74 | 75 | def init_permissions(chat_id, reset=False): 76 | curr_perm = SESSION.query(Permissions).get(str(chat_id)) 77 | if reset: 78 | SESSION.delete(curr_perm) 79 | SESSION.flush() 80 | perm = Permissions(str(chat_id)) 81 | SESSION.add(perm) 82 | SESSION.commit() 83 | return perm 84 | 85 | 86 | def init_restrictions(chat_id, reset=False): 87 | curr_restr = SESSION.query(Restrictions).get(str(chat_id)) 88 | if reset: 89 | SESSION.delete(curr_restr) 90 | SESSION.flush() 91 | restr = Restrictions(str(chat_id)) 92 | SESSION.add(restr) 93 | SESSION.commit() 94 | return restr 95 | 96 | 97 | def update_lock(chat_id, lock_type, locked): 98 | with PERM_LOCK: 99 | curr_perm = SESSION.query(Permissions).get(str(chat_id)) 100 | if not curr_perm: 101 | curr_perm = init_permissions(chat_id) 102 | 103 | if lock_type == "audio": 104 | curr_perm.audio = locked 105 | elif lock_type == "voice": 106 | curr_perm.voice = locked 107 | elif lock_type == "contact": 108 | curr_perm.contact = locked 109 | elif lock_type == "video": 110 | curr_perm.video = locked 111 | elif lock_type == "document": 112 | curr_perm.document = locked 113 | elif lock_type == "photo": 114 | curr_perm.photo = locked 115 | elif lock_type == "sticker": 116 | curr_perm.sticker = locked 117 | elif lock_type == "gif": 118 | curr_perm.gif = locked 119 | elif lock_type == 'url': 120 | curr_perm.url = locked 121 | elif lock_type == 'bots': 122 | curr_perm.bots = locked 123 | elif lock_type == 'forward': 124 | curr_perm.forward = locked 125 | elif lock_type == 'game': 126 | curr_perm.game = locked 127 | elif lock_type == 'location': 128 | curr_perm.location = locked 129 | 130 | SESSION.add(curr_perm) 131 | SESSION.commit() 132 | 133 | 134 | def update_restriction(chat_id, restr_type, locked): 135 | with RESTR_LOCK: 136 | curr_restr = SESSION.query(Restrictions).get(str(chat_id)) 137 | if not curr_restr: 138 | curr_restr = init_restrictions(chat_id) 139 | 140 | if restr_type == "messages": 141 | curr_restr.messages = locked 142 | elif restr_type == "media": 143 | curr_restr.media = locked 144 | elif restr_type == "other": 145 | curr_restr.other = locked 146 | elif restr_type == "previews": 147 | curr_restr.preview = locked 148 | elif restr_type == "all": 149 | curr_restr.messages = locked 150 | curr_restr.media = locked 151 | curr_restr.other = locked 152 | curr_restr.preview = locked 153 | SESSION.add(curr_restr) 154 | SESSION.commit() 155 | 156 | 157 | def is_locked(chat_id, lock_type): 158 | curr_perm = SESSION.query(Permissions).get(str(chat_id)) 159 | SESSION.close() 160 | 161 | if not curr_perm: 162 | return False 163 | 164 | elif lock_type == "sticker": 165 | return curr_perm.sticker 166 | elif lock_type == "photo": 167 | return curr_perm.photo 168 | elif lock_type == "audio": 169 | return curr_perm.audio 170 | elif lock_type == "voice": 171 | return curr_perm.voice 172 | elif lock_type == "contact": 173 | return curr_perm.contact 174 | elif lock_type == "video": 175 | return curr_perm.video 176 | elif lock_type == "document": 177 | return curr_perm.document 178 | elif lock_type == "gif": 179 | return curr_perm.gif 180 | elif lock_type == "url": 181 | return curr_perm.url 182 | elif lock_type == "bots": 183 | return curr_perm.bots 184 | elif lock_type == "forward": 185 | return curr_perm.forward 186 | elif lock_type == "game": 187 | return curr_perm.game 188 | elif lock_type == "location": 189 | return curr_perm.location 190 | 191 | 192 | def is_restr_locked(chat_id, lock_type): 193 | curr_restr = SESSION.query(Restrictions).get(str(chat_id)) 194 | SESSION.close() 195 | 196 | if not curr_restr: 197 | return False 198 | 199 | if lock_type == "messages": 200 | return curr_restr.messages 201 | elif lock_type == "media": 202 | return curr_restr.media 203 | elif lock_type == "other": 204 | return curr_restr.other 205 | elif lock_type == "previews": 206 | return curr_restr.preview 207 | elif lock_type == "all": 208 | return curr_restr.messages and curr_restr.media and curr_restr.other and curr_restr.preview 209 | 210 | 211 | def get_locks(chat_id): 212 | try: 213 | return SESSION.query(Permissions).get(str(chat_id)) 214 | finally: 215 | SESSION.close() 216 | 217 | 218 | def get_restr(chat_id): 219 | try: 220 | return SESSION.query(Restrictions).get(str(chat_id)) 221 | finally: 222 | SESSION.close() 223 | 224 | 225 | def migrate_chat(old_chat_id, new_chat_id): 226 | with PERM_LOCK: 227 | perms = SESSION.query(Permissions).get(str(old_chat_id)) 228 | if perms: 229 | perms.chat_id = str(new_chat_id) 230 | SESSION.commit() 231 | 232 | with RESTR_LOCK: 233 | rest = SESSION.query(Restrictions).get(str(old_chat_id)) 234 | if rest: 235 | rest.chat_id = str(new_chat_id) 236 | SESSION.commit() 237 | -------------------------------------------------------------------------------- /bot/modules/log_channel.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from functools import wraps 3 | 4 | from bot.modules.helper_funcs.misc import is_module_loaded 5 | 6 | FILENAME = __name__.rsplit(".", 1)[-1] 7 | 8 | if is_module_loaded(FILENAME): 9 | from telegram import Bot, Update, ParseMode 10 | from telegram.error import BadRequest, Unauthorized 11 | from telegram.ext import CommandHandler, run_async 12 | from telegram.utils.helpers import escape_markdown 13 | 14 | from bot import dispatcher, LOGGER 15 | from bot.modules.helper_funcs.chat_status import user_admin 16 | from bot.modules.sql import log_channel_sql as sql 17 | 18 | 19 | def loggable(func): 20 | @wraps(func) 21 | def log_action(bot: Bot, update: Update, *args, **kwargs): 22 | 23 | result = func(bot, update, *args, **kwargs) 24 | chat = update.effective_chat 25 | message = update.effective_message 26 | 27 | if result: 28 | datetime_fmt = "%H:%M - %d-%m-%Y" 29 | result += f"\nEvent Stamp: {datetime.utcnow().strftime(datetime_fmt)}" 30 | 31 | if message.chat.type == chat.SUPERGROUP and message.chat.username: 32 | result += f'\nLink: click here' 33 | log_chat = sql.get_chat_log_channel(chat.id) 34 | if log_chat: 35 | send_log(bot, log_chat, chat.id, result) 36 | elif result == "": 37 | pass 38 | else: 39 | LOGGER.warning("%s was set as loggable, but had no return statement.", func) 40 | 41 | return result 42 | 43 | return log_action 44 | 45 | 46 | def gloggable(func): 47 | @wraps(func) 48 | def glog_action(bot: Bot, update: Update, *args, **kwargs): 49 | 50 | result = func(bot, update, *args, **kwargs) 51 | chat = update.effective_chat 52 | message = update.effective_message 53 | 54 | if result: 55 | datetime_fmt = "%H:%M - %d-%m-%Y" 56 | result += "\nEvent Stamp: {}".format(datetime.utcnow().strftime(datetime_fmt)) 57 | 58 | if message.chat.type == chat.SUPERGROUP and message.chat.username: 59 | result += f'\nLink: click here' 60 | log_chat = str(GBAN_LOGS) 61 | if log_chat: 62 | send_log(bot, log_chat, chat.id, result) 63 | elif result == "": 64 | pass 65 | else: 66 | LOGGER.warning("%s was set as loggable to gbanlogs, but had no return statement.", func) 67 | 68 | return result 69 | 70 | return glog_action 71 | 72 | 73 | def send_log(bot: Bot, log_chat_id: str, orig_chat_id: str, result: str): 74 | 75 | try: 76 | bot.send_message(log_chat_id, result, parse_mode=ParseMode.HTML, disable_web_page_preview=True) 77 | except BadRequest as excp: 78 | if excp.message == "Chat not found": 79 | bot.send_message(orig_chat_id, "This log channel has been deleted - unsetting.") 80 | sql.stop_chat_logging(orig_chat_id) 81 | else: 82 | LOGGER.warning(excp.message) 83 | LOGGER.warning(result) 84 | LOGGER.exception("Could not parse") 85 | 86 | bot.send_message(log_chat_id, result + "\n\nFormatting has been disabled due to an unexpected error.") 87 | 88 | 89 | @run_async 90 | @user_admin 91 | def logging(bot: Bot, update: Update): 92 | 93 | message = update.effective_message 94 | chat = update.effective_chat 95 | 96 | log_channel = sql.get_chat_log_channel(chat.id) 97 | if log_channel: 98 | log_channel_info = bot.get_chat(log_channel) 99 | message.reply_text(f"This group has all it's logs sent to:" 100 | f" {escape_markdown(log_channel_info.title)} (`{log_channel}`)", 101 | parse_mode=ParseMode.MARKDOWN) 102 | 103 | else: 104 | message.reply_text("No log channel has been set for this group!") 105 | 106 | 107 | @run_async 108 | @user_admin 109 | def setlog(bot: Bot, update: Update): 110 | 111 | message = update.effective_message 112 | chat = update.effective_chat 113 | if chat.type == chat.CHANNEL: 114 | message.reply_text("Now, forward the /setlog to the group you want to tie this channel to!") 115 | 116 | elif message.forward_from_chat: 117 | sql.set_chat_log_channel(chat.id, message.forward_from_chat.id) 118 | try: 119 | message.delete() 120 | except BadRequest as excp: 121 | if excp.message == "Message to delete not found": 122 | pass 123 | else: 124 | LOGGER.exception("Error deleting message in log channel. Should work anyway though.") 125 | 126 | try: 127 | bot.send_message(message.forward_from_chat.id, 128 | f"This channel has been set as the log channel for {chat.title or chat.first_name}.") 129 | except Unauthorized as excp: 130 | if excp.message == "Forbidden: bot is not a member of the channel chat": 131 | bot.send_message(chat.id, "Successfully set log channel!") 132 | else: 133 | LOGGER.exception("ERROR in setting the log channel.") 134 | 135 | bot.send_message(chat.id, "Successfully set log channel!") 136 | 137 | else: 138 | message.reply_text("The steps to set a log channel are:\n" 139 | " - add bot to the desired channel\n" 140 | " - send /setlog to the channel\n" 141 | " - forward the /setlog to the group\n") 142 | 143 | 144 | @run_async 145 | @user_admin 146 | def unsetlog(bot: Bot, update: Update): 147 | 148 | message = update.effective_message 149 | chat = update.effective_chat 150 | 151 | log_channel = sql.stop_chat_logging(chat.id) 152 | if log_channel: 153 | bot.send_message(log_channel, f"Channel has been unlinked from {chat.title}") 154 | message.reply_text("Log channel has been un-set.") 155 | 156 | else: 157 | message.reply_text("No log channel has been set yet!") 158 | 159 | 160 | def __stats__(): 161 | return f"{sql.num_logchannels()} log channels set." 162 | 163 | 164 | def __migrate__(old_chat_id, new_chat_id): 165 | sql.migrate_chat(old_chat_id, new_chat_id) 166 | 167 | 168 | def __chat_settings__(chat_id, user_id): 169 | log_channel = sql.get_chat_log_channel(chat_id) 170 | if log_channel: 171 | log_channel_info = dispatcher.bot.get_chat(log_channel) 172 | return f"This group has all it's logs sent to: {escape_markdown(log_channel_info.title)} (`{log_channel}`)" 173 | return "No log channel is set for this group!" 174 | 175 | 176 | __help__ = """ 177 | *Admin only:* 178 | • /logchannel: get log channel info 179 | • /setlog: set the log channel. 180 | • /unsetlog: unset the log channel. 181 | *Setting the log channel is done by:* 182 | • adding the bot to the desired channel (as an admin!) 183 | • sending /setlog in the channel 184 | • forwarding the /setlog to the group 185 | """ 186 | __mod_name__ = "BOT LOG🌍" 187 | 188 | LOG_HANDLER = CommandHandler("logchannel", logging) 189 | SET_LOG_HANDLER = CommandHandler("setlog", setlog) 190 | UNSET_LOG_HANDLER = CommandHandler("unsetlog", unsetlog) 191 | 192 | dispatcher.add_handler(LOG_HANDLER) 193 | dispatcher.add_handler(SET_LOG_HANDLER) 194 | dispatcher.add_handler(UNSET_LOG_HANDLER) 195 | 196 | else: 197 | # run anyway if module not loaded 198 | def loggable(func): 199 | return func 200 | 201 | 202 | def gloggable(func): 203 | return func 204 | -------------------------------------------------------------------------------- /bot/modules/sql/cust_filters_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import Column, String, UnicodeText, Boolean, Integer, distinct, func 4 | 5 | from bot.modules.sql import BASE, SESSION 6 | 7 | 8 | class CustomFilters(BASE): 9 | __tablename__ = "cust_filters" 10 | chat_id = Column(String(14), primary_key=True) 11 | keyword = Column(UnicodeText, primary_key=True, nullable=False) 12 | reply = Column(UnicodeText, nullable=False) 13 | is_sticker = Column(Boolean, nullable=False, default=False) 14 | is_document = Column(Boolean, nullable=False, default=False) 15 | is_image = Column(Boolean, nullable=False, default=False) 16 | is_audio = Column(Boolean, nullable=False, default=False) 17 | is_voice = Column(Boolean, nullable=False, default=False) 18 | is_video = Column(Boolean, nullable=False, default=False) 19 | caption = Column(UnicodeText, nullable=True, default=None) 20 | 21 | has_buttons = Column(Boolean, nullable=False, default=False) 22 | # NOTE: Here for legacy purposes, to ensure older filters don't mess up. 23 | has_markdown = Column(Boolean, nullable=False, default=False) 24 | # NOTE: Here for -_- purposes, 25 | has_caption = Column(Boolean, nullable=False, default=False) 26 | 27 | def __init__(self, chat_id, keyword, reply, is_sticker=False, is_document=False, is_image=False, is_audio=False, 28 | is_voice=False, is_video=False, has_buttons=False): 29 | self.chat_id = str(chat_id) # ensure string 30 | self.keyword = keyword 31 | self.reply = reply 32 | self.is_sticker = is_sticker 33 | self.is_document = is_document 34 | self.is_image = is_image 35 | self.is_audio = is_audio 36 | self.is_voice = is_voice 37 | self.is_video = is_video 38 | self.has_buttons = has_buttons 39 | self.has_markdown = True 40 | 41 | 42 | def __repr__(self): 43 | return "" % self.chat_id 44 | 45 | def __eq__(self, other): 46 | return bool(isinstance(other, CustomFilters) 47 | and self.chat_id == other.chat_id 48 | and self.keyword == other.keyword) 49 | 50 | 51 | class Buttons(BASE): 52 | __tablename__ = "cust_filter_urls" 53 | id = Column(Integer, primary_key=True, autoincrement=True) 54 | chat_id = Column(String(14), primary_key=True) 55 | keyword = Column(UnicodeText, primary_key=True) 56 | name = Column(UnicodeText, nullable=False) 57 | url = Column(UnicodeText, nullable=False) 58 | same_line = Column(Boolean, default=False) 59 | 60 | def __init__(self, chat_id, keyword, name, url, same_line=False): 61 | self.chat_id = str(chat_id) 62 | self.keyword = keyword 63 | self.name = name 64 | self.url = url 65 | self.same_line = same_line 66 | 67 | 68 | CustomFilters.__table__.create(checkfirst=True) 69 | Buttons.__table__.create(checkfirst=True) 70 | 71 | CUST_FILT_LOCK = threading.RLock() 72 | BUTTON_LOCK = threading.RLock() 73 | CHAT_FILTERS = {} 74 | 75 | 76 | def get_all_filters(): 77 | try: 78 | return SESSION.query(CustomFilters).all() 79 | finally: 80 | SESSION.close() 81 | 82 | 83 | def add_filter(chat_id, keyword, reply, is_sticker=False, is_document=False, is_image=False, is_audio=False, 84 | is_voice=False, is_video=False, buttons=None, caption=None, has_caption=False): 85 | global CHAT_FILTERS 86 | 87 | if buttons is None: 88 | buttons = [] 89 | 90 | with CUST_FILT_LOCK: 91 | prev = SESSION.query(CustomFilters).get((str(chat_id), keyword)) 92 | if prev: 93 | with BUTTON_LOCK: 94 | prev_buttons = SESSION.query(Buttons).filter(Buttons.chat_id == str(chat_id), 95 | Buttons.keyword == keyword).all() 96 | for btn in prev_buttons: 97 | SESSION.delete(btn) 98 | SESSION.delete(prev) 99 | 100 | filt = CustomFilters(str(chat_id), keyword, reply, is_sticker, is_document, is_image, is_audio, is_voice, 101 | is_video, bool(buttons)) 102 | if has_caption: 103 | filt.caption = caption 104 | filt.has_caption = has_caption 105 | 106 | if keyword not in CHAT_FILTERS.get(str(chat_id), []): 107 | CHAT_FILTERS[str(chat_id)] = sorted(CHAT_FILTERS.get(str(chat_id), []) + [keyword], 108 | key=lambda x: (-len(x), x)) 109 | 110 | SESSION.add(filt) 111 | SESSION.commit() 112 | 113 | for b_name, url, same_line in buttons: 114 | add_note_button_to_db(chat_id, keyword, b_name, url, same_line) 115 | 116 | 117 | def remove_filter(chat_id, keyword): 118 | global CHAT_FILTERS 119 | with CUST_FILT_LOCK: 120 | filt = SESSION.query(CustomFilters).get((str(chat_id), keyword)) 121 | if filt: 122 | if keyword in CHAT_FILTERS.get(str(chat_id), []): # Sanity check 123 | CHAT_FILTERS.get(str(chat_id), []).remove(keyword) 124 | 125 | with BUTTON_LOCK: 126 | prev_buttons = SESSION.query(Buttons).filter(Buttons.chat_id == str(chat_id), 127 | Buttons.keyword == keyword).all() 128 | for btn in prev_buttons: 129 | SESSION.delete(btn) 130 | 131 | SESSION.delete(filt) 132 | SESSION.commit() 133 | return True 134 | 135 | SESSION.close() 136 | return False 137 | 138 | 139 | def get_chat_triggers(chat_id): 140 | return CHAT_FILTERS.get(str(chat_id), set()) 141 | 142 | 143 | def get_chat_filters(chat_id): 144 | try: 145 | return SESSION.query(CustomFilters).filter(CustomFilters.chat_id == str(chat_id)).order_by( 146 | func.length(CustomFilters.keyword).desc()).order_by(CustomFilters.keyword.asc()).all() 147 | finally: 148 | SESSION.close() 149 | 150 | 151 | def get_filter(chat_id, keyword): 152 | try: 153 | return SESSION.query(CustomFilters).get((str(chat_id), keyword)) 154 | finally: 155 | SESSION.close() 156 | 157 | 158 | def add_note_button_to_db(chat_id, keyword, b_name, url, same_line): 159 | with BUTTON_LOCK: 160 | button = Buttons(chat_id, keyword, b_name, url, same_line) 161 | SESSION.add(button) 162 | SESSION.commit() 163 | 164 | 165 | def get_buttons(chat_id, keyword): 166 | try: 167 | return SESSION.query(Buttons).filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword).order_by( 168 | Buttons.id).all() 169 | finally: 170 | SESSION.close() 171 | 172 | 173 | def num_filters(): 174 | try: 175 | return SESSION.query(CustomFilters).count() 176 | finally: 177 | SESSION.close() 178 | 179 | 180 | def num_chats(): 181 | try: 182 | return SESSION.query(func.count(distinct(CustomFilters.chat_id))).scalar() 183 | finally: 184 | SESSION.close() 185 | 186 | 187 | def __load_chat_filters(): 188 | global CHAT_FILTERS 189 | try: 190 | chats = SESSION.query(CustomFilters.chat_id).distinct().all() 191 | for (chat_id,) in chats: # remove tuple by ( ,) 192 | CHAT_FILTERS[chat_id] = [] 193 | 194 | all_filters = SESSION.query(CustomFilters).all() 195 | for x in all_filters: 196 | CHAT_FILTERS[x.chat_id] += [x.keyword] 197 | 198 | CHAT_FILTERS = {x: sorted(set(y), key=lambda i: (-len(i), i)) for x, y in CHAT_FILTERS.items()} 199 | 200 | finally: 201 | SESSION.close() 202 | 203 | 204 | def migrate_chat(old_chat_id, new_chat_id): 205 | with CUST_FILT_LOCK: 206 | chat_filters = SESSION.query(CustomFilters).filter(CustomFilters.chat_id == str(old_chat_id)).all() 207 | for filt in chat_filters: 208 | filt.chat_id = str(new_chat_id) 209 | SESSION.commit() 210 | CHAT_FILTERS[str(new_chat_id)] = CHAT_FILTERS[str(old_chat_id)] 211 | del CHAT_FILTERS[str(old_chat_id)] 212 | 213 | with BUTTON_LOCK: 214 | chat_buttons = SESSION.query(Buttons).filter(Buttons.chat_id == str(old_chat_id)).all() 215 | for btn in chat_buttons: 216 | btn.chat_id = str(new_chat_id) 217 | SESSION.commit() 218 | 219 | 220 | __load_chat_filters() 221 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/string_handling.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | from typing import Dict, List 4 | 5 | import emoji 6 | from telegram import MessageEntity 7 | from telegram.utils.helpers import escape_markdown 8 | 9 | # NOTE: the url \ escape may cause double escapes 10 | # match * (bold) (don't escape if in url) 11 | # match _ (italics) (don't escape if in url) 12 | # match ` (code) 13 | # match []() (markdown link) 14 | # else, escape *, _, `, and [ 15 | MATCH_MD = re.compile(r'\*(.*?)\*|' 16 | r'_(.*?)_|' 17 | r'`(.*?)`|' 18 | r'(?[*_`\[])') 20 | 21 | # regex to find []() links -> hyperlinks/buttons 22 | LINK_REGEX = re.compile(r'(? str: 27 | """ 28 | Escape all invalid markdown 29 | 30 | :param to_parse: text to escape 31 | :return: valid markdown string 32 | """ 33 | offset = 0 # offset to be used as adding a \ character causes the string to shift 34 | for match in MATCH_MD.finditer(to_parse): 35 | if match.group('esc'): 36 | ent_start = match.start() 37 | to_parse = to_parse[:ent_start + offset] + '\\' + to_parse[ent_start + offset:] 38 | offset += 1 39 | return to_parse 40 | 41 | 42 | # This is a fun one. 43 | def _calc_emoji_offset(to_calc) -> int: 44 | # Get all emoji in text. 45 | emoticons = emoji.get_emoji_regexp().finditer(to_calc) 46 | # Check the utf16 length of the emoji to determine the offset it caused. 47 | # Normal, 1 character emoji don't affect; hence sub 1. 48 | # special, eg with two emoji characters (eg face, and skin col) will have length 2, so by subbing one we 49 | # know we'll get one extra offset, 50 | return sum(len(e.group(0).encode('utf-16-le')) // 2 - 1 for e in emoticons) 51 | 52 | 53 | def markdown_parser(txt: str, entities: Dict[MessageEntity, str] = None, offset: int = 0) -> str: 54 | """ 55 | Parse a string, escaping all invalid markdown entities. 56 | 57 | Escapes URL's so as to avoid URL mangling. 58 | Re-adds any telegram code entities obtained from the entities object. 59 | 60 | :param txt: text to parse 61 | :param entities: dict of message entities in text 62 | :param offset: message offset - command and notename length 63 | :return: valid markdown string 64 | """ 65 | if not entities: 66 | entities = {} 67 | if not txt: 68 | return "" 69 | 70 | prev = 0 71 | res = "" 72 | # Loop over all message entities, and: 73 | # reinsert code 74 | # escape free-standing urls 75 | for ent, ent_text in entities.items(): 76 | if ent.offset < -offset: 77 | continue 78 | 79 | start = ent.offset + offset # start of entity 80 | end = ent.offset + offset + ent.length - 1 # end of entity 81 | 82 | # we only care about code, url, text links 83 | if ent.type in ("code", "url", "text_link"): 84 | # count emoji to switch counter 85 | count = _calc_emoji_offset(txt[:start]) 86 | start -= count 87 | end -= count 88 | 89 | # URL handling -> do not escape if in [](), escape otherwise. 90 | if ent.type == "url": 91 | if any(match.start(1) <= start and end <= match.end(1) for match in LINK_REGEX.finditer(txt)): 92 | continue 93 | # else, check the escapes between the prev and last and forcefully escape the url to avoid mangling 94 | else: 95 | # TODO: investigate possible offset bug when lots of emoji are present 96 | res += _selective_escape(txt[prev:start] or "") + escape_markdown(ent_text) 97 | 98 | # code handling 99 | elif ent.type == "code": 100 | res += _selective_escape(txt[prev:start]) + '`' + ent_text + '`' 101 | 102 | # handle markdown/html links 103 | elif ent.type == "text_link": 104 | res += _selective_escape(txt[prev:start]) + "[{}]({})".format(ent_text, ent.url) 105 | 106 | end += 1 107 | 108 | # anything else 109 | else: 110 | continue 111 | 112 | prev = end 113 | 114 | res += _selective_escape(txt[prev:]) # add the rest of the text 115 | return res 116 | 117 | 118 | def button_markdown_parser(txt: str, entities: Dict[MessageEntity, str] = None, offset: int = 0) -> (str, List): 119 | markdown_note = markdown_parser(txt, entities, offset) 120 | prev = 0 121 | note_data = "" 122 | buttons = [] 123 | for match in BTN_URL_REGEX.finditer(markdown_note): 124 | # Check if btnurl is escaped 125 | n_escapes = 0 126 | to_check = match.start(1) - 1 127 | while to_check > 0 and markdown_note[to_check] == "\\": 128 | n_escapes += 1 129 | to_check -= 1 130 | 131 | # if even, not escaped -> create button 132 | if n_escapes % 2 == 0: 133 | # create a thruple with button label, url, and newline status 134 | buttons.append((match.group(2), match.group(3), bool(match.group(4)))) 135 | note_data += markdown_note[prev:match.start(1)] 136 | prev = match.end(1) 137 | # if odd, escaped -> move along 138 | else: 139 | note_data += markdown_note[prev:to_check] 140 | prev = match.start(1) - 1 141 | else: 142 | note_data += markdown_note[prev:] 143 | 144 | return note_data, buttons 145 | 146 | 147 | def escape_invalid_curly_brackets(text: str, valids: List[str]) -> str: 148 | new_text = "" 149 | idx = 0 150 | while idx < len(text): 151 | if text[idx] == "{": 152 | if idx + 1 < len(text) and text[idx + 1] == "{": 153 | idx += 2 154 | new_text += "{{{{" 155 | continue 156 | else: 157 | success = False 158 | for v in valids: 159 | if text[idx:].startswith('{' + v + '}'): 160 | success = True 161 | break 162 | if success: 163 | new_text += text[idx: idx + len(v) + 2] 164 | idx += len(v) + 2 165 | continue 166 | else: 167 | new_text += "{{" 168 | 169 | elif text[idx] == "}": 170 | if idx + 1 < len(text) and text[idx + 1] == "}": 171 | idx += 2 172 | new_text += "}}}}" 173 | continue 174 | else: 175 | new_text += "}}" 176 | 177 | else: 178 | new_text += text[idx] 179 | idx += 1 180 | 181 | return new_text 182 | 183 | 184 | SMART_OPEN = '“' 185 | SMART_CLOSE = '”' 186 | START_CHAR = ('\'', '"', SMART_OPEN) 187 | 188 | 189 | def split_quotes(text: str) -> List: 190 | if any(text.startswith(char) for char in START_CHAR): 191 | counter = 1 # ignore first char -> is some kind of quote 192 | while counter < len(text): 193 | if text[counter] == "\\": 194 | counter += 1 195 | elif text[counter] == text[0] or (text[0] == SMART_OPEN and text[counter] == SMART_CLOSE): 196 | break 197 | counter += 1 198 | else: 199 | return text.split(None, 1) 200 | 201 | # 1 to avoid starting quote, and counter is exclusive so avoids ending 202 | key = remove_escapes(text[1:counter].strip()) 203 | # index will be in range, or `else` would have been executed and returned 204 | rest = text[counter + 1:].strip() 205 | if not key: 206 | key = text[0] + text[0] 207 | return list(filter(None, [key, rest])) 208 | else: 209 | return text.split(None, 1) 210 | 211 | 212 | def remove_escapes(text: str) -> str: 213 | counter = 0 214 | res = "" 215 | is_escaped = False 216 | while counter < len(text): 217 | if is_escaped: 218 | res += text[counter] 219 | is_escaped = False 220 | elif text[counter] == "\\": 221 | is_escaped = True 222 | else: 223 | res += text[counter] 224 | counter += 1 225 | return res 226 | 227 | 228 | def escape_chars(text: str, to_escape: List[str]) -> str: 229 | to_escape.append("\\") 230 | new_text = "" 231 | for x in text: 232 | if x in to_escape: 233 | new_text += "\\" 234 | new_text += x 235 | return new_text 236 | 237 | 238 | def extract_time(message, time_val): 239 | if any(time_val.endswith(unit) for unit in ('m', 'h', 'd')): 240 | unit = time_val[-1] 241 | time_num = time_val[:-1] # type: str 242 | if not time_num.isdigit(): 243 | message.reply_text("Invalid time amount specified.") 244 | return "" 245 | 246 | if unit == 'm': 247 | bantime = int(time.time() + int(time_num) * 60) 248 | elif unit == 'h': 249 | bantime = int(time.time() + int(time_num) * 60 * 60) 250 | elif unit == 'd': 251 | bantime = int(time.time() + int(time_num) * 24 * 60 * 60) 252 | else: 253 | # how even...? 254 | return "" 255 | return bantime 256 | else: 257 | message.reply_text("Invalid time type specified. Expected m,h, or d, got: {}".format(time_val[-1])) 258 | return "" 259 | -------------------------------------------------------------------------------- /bot/modules/helper_funcs/chat_status.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from telegram import Bot, Chat, ChatMember, Update, User, ParseMode 4 | 5 | from bot import dispatcher, DEL_CMDS, WHITELIST_USERS, SUPPORT_USERS, SUDO_USERS, DEV_USERS 6 | 7 | def is_whitelist_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: 8 | return user_id in WHITELIST_USERS or user_id in SUPPORT_USERS or user_id in SUDO_USERS or user_id in DEV_USERS 9 | 10 | 11 | def is_support_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: 12 | return user_id in SUPPORT_USERS or user_id in SUDO_USERS or user_id in DEV_USERS 13 | 14 | 15 | def is_sudo_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: 16 | return user_id in SUDO_USERS or user_id in DEV_USERS 17 | 18 | 19 | def is_user_admin(chat: Chat, user_id: int, member: ChatMember = None) -> bool: 20 | 21 | if chat.type == 'private' \ 22 | or user_id in SUDO_USERS \ 23 | or user_id in DEV_USERS \ 24 | or chat.all_members_are_administrators: 25 | return True 26 | 27 | if not member: 28 | member = chat.get_member(user_id) 29 | 30 | return member.status in ('administrator', 'creator') 31 | 32 | 33 | def is_bot_admin(chat: Chat, bot_id: int, bot_member: ChatMember = None) -> bool: 34 | 35 | if chat.type == 'private' or chat.all_members_are_administrators: 36 | return True 37 | 38 | if not bot_member: 39 | bot_member = chat.get_member(bot_id) 40 | 41 | return bot_member.status in ('administrator', 'creator') 42 | 43 | 44 | def can_delete(chat: Chat, bot_id: int) -> bool: 45 | return chat.get_member(bot_id).can_delete_messages 46 | 47 | 48 | def is_user_ban_protected(chat: Chat, user_id: int, member: ChatMember = None) -> bool: 49 | 50 | if chat.type == 'private' \ 51 | or user_id in SUDO_USERS \ 52 | or user_id in DEV_USERS \ 53 | or user_id in WHITELIST_USERS \ 54 | or chat.all_members_are_administrators: 55 | return True 56 | 57 | if not member: 58 | member = chat.get_member(user_id) 59 | 60 | return member.status in ('administrator', 'creator') 61 | 62 | def is_user_in_chat(chat: Chat, user_id: int) -> bool: 63 | 64 | member = chat.get_member(user_id) 65 | return member.status not in ('left', 'kicked') 66 | 67 | 68 | def dev_plus(func): 69 | @wraps(func) 70 | def is_dev_plus_func(bot: Bot, update: Update, *args, **kwargs): 71 | 72 | user = update.effective_user 73 | 74 | if user.id in DEV_USERS: 75 | return func(bot, update, *args, **kwargs) 76 | elif not user: 77 | pass 78 | elif DEL_CMDS and " " not in update.effective_message.text: 79 | update.effective_message.delete() 80 | else: 81 | update.effective_message.reply_text("This is a developer restricted command. You do not have permissions to run this.") 82 | 83 | return is_dev_plus_func 84 | 85 | 86 | def sudo_plus(func): 87 | @wraps(func) 88 | def is_sudo_plus_func(bot: Bot, update: Update, *args, **kwargs): 89 | 90 | user = update.effective_user 91 | chat = update.effective_chat 92 | 93 | if user and is_sudo_plus(chat, user.id): 94 | return func(bot, update, *args, **kwargs) 95 | elif not user: 96 | pass 97 | elif DEL_CMDS and " " not in update.effective_message.text: 98 | update.effective_message.delete() 99 | else: 100 | update.effective_message.reply_text("Who dis non-admin telling me what to do?") 101 | 102 | return is_sudo_plus_func 103 | 104 | 105 | def support_plus(func): 106 | @wraps(func) 107 | def is_support_plus_func(bot: Bot, update: Update, *args, **kwargs): 108 | 109 | user = update.effective_user 110 | chat = update.effective_chat 111 | 112 | if user and is_whitelist_plus(chat, user.id): 113 | return func(bot, update, *args, **kwargs) 114 | elif DEL_CMDS and " " not in update.effective_message.text: 115 | update.effective_message.delete() 116 | 117 | return is_support_plus_func 118 | 119 | 120 | def whitelist_plus(func): 121 | @wraps(func) 122 | def is_whitelist_plus_func(bot: Bot, update: Update, *args, **kwargs): 123 | 124 | user = update.effective_user 125 | chat = update.effective_chat 126 | 127 | if user and is_whitelist_plus(chat, user.id): 128 | return func(bot, update, *args, **kwargs) 129 | else: 130 | update.effective_message.reply_text("You don't have access to use this.") 131 | 132 | return is_whitelist_plus_func 133 | 134 | 135 | def user_admin(func): 136 | @wraps(func) 137 | def is_admin(bot: Bot, update: Update, *args, **kwargs): 138 | 139 | user = update.effective_user 140 | chat = update.effective_chat 141 | 142 | if user and is_user_admin(chat, user.id): 143 | return func(bot, update, *args, **kwargs) 144 | elif not user: 145 | pass 146 | elif DEL_CMDS and " " not in update.effective_message.text: 147 | update.effective_message.delete() 148 | else: 149 | update.effective_message.reply_text("You don't have access to use this.") 150 | 151 | return is_admin 152 | 153 | 154 | def user_admin_no_reply(func): 155 | @wraps(func) 156 | def is_not_admin_no_reply(bot: Bot, update: Update, *args, **kwargs): 157 | 158 | user = update.effective_user 159 | chat = update.effective_chat 160 | 161 | if user and is_user_admin(chat, user.id): 162 | return func(bot, update, *args, **kwargs) 163 | elif not user: 164 | pass 165 | elif DEL_CMDS and " " not in update.effective_message.text: 166 | update.effective_message.delete() 167 | 168 | return is_not_admin_no_reply 169 | 170 | 171 | def user_not_admin(func): 172 | @wraps(func) 173 | def is_not_admin(bot: Bot, update: Update, *args, **kwargs): 174 | 175 | user = update.effective_user 176 | chat = update.effective_chat 177 | 178 | if user and not is_user_admin(chat, user.id): 179 | return func(bot, update, *args, **kwargs) 180 | elif not user: 181 | pass 182 | 183 | return is_not_admin 184 | 185 | 186 | def bot_admin(func): 187 | @wraps(func) 188 | def is_admin(bot: Bot, update: Update, *args, **kwargs): 189 | 190 | chat = update.effective_chat 191 | update_chat_title = chat.title 192 | message_chat_title = update.effective_message.chat.title 193 | 194 | if update_chat_title == message_chat_title: 195 | not_admin = "I'm not admin! " 196 | else: 197 | not_admin = f"I'm not admin in {update_chat_title}! " 198 | 199 | if is_bot_admin(chat, bot.id): 200 | return func(bot, update, *args, **kwargs) 201 | else: 202 | update.effective_message.reply_text(not_admin, parse_mode=ParseMode.HTML) 203 | 204 | return is_admin 205 | 206 | 207 | def bot_can_delete(func): 208 | @wraps(func) 209 | def delete_rights(bot: Bot, update: Update, *args, **kwargs): 210 | 211 | chat = update.effective_chat 212 | update_chat_title = chat.title 213 | message_chat_title = update.effective_message.chat.title 214 | 215 | if update_chat_title == message_chat_title: 216 | cant_delete = f"I can't delete messages here!\nMake sure I'm admin and can delete other user's messages." 217 | else: 218 | cant_delete = f"I can't delete messages in {update_chat_title}!\nMake sure I'm admin and can delete other user's messages there." 219 | 220 | if can_delete(chat, bot.id): 221 | return func(bot, update, *args, **kwargs) 222 | else: 223 | update.effective_message.reply_text(cant_delete, parse_mode=ParseMode.HTML) 224 | 225 | return delete_rights 226 | 227 | 228 | def can_pin(func): 229 | @wraps(func) 230 | def pin_rights(bot: Bot, update: Update, *args, **kwargs): 231 | 232 | chat = update.effective_chat 233 | update_chat_title = chat.title 234 | message_chat_title = update.effective_message.chat.title 235 | 236 | if update_chat_title == message_chat_title: 237 | cant_pin = f"I can't pin messages here!\nMake sure I'm admin and can pin messages." 238 | else: 239 | cant_pin = f"I can't pin messages in {update_chat_title}!\nMake sure I'm admin and can pin messages there." 240 | 241 | if chat.get_member(bot.id).can_pin_messages: 242 | return func(bot, update, *args, **kwargs) 243 | else: 244 | update.effective_message.reply_text(cant_pin, parse_mode=ParseMode.HTML) 245 | 246 | return pin_rights 247 | 248 | 249 | def can_promote(func): 250 | @wraps(func) 251 | def promote_rights(bot: Bot, update: Update, *args, **kwargs): 252 | 253 | chat = update.effective_chat 254 | update_chat_title = chat.title 255 | message_chat_title = update.effective_message.chat.title 256 | 257 | if update_chat_title == message_chat_title: 258 | cant_promote = f"I can't promote/demote people here!\nMake sure I'm admin and can appoint new admins." 259 | else: 260 | cant_promote = f"I can't promote/demote people in {update_chat_title}!\nMake sure I'm admin there and can appoint new admins." 261 | 262 | if chat.get_member(bot.id).can_promote_members: 263 | return func(bot, update, *args, **kwargs) 264 | else: 265 | update.effective_message.reply_text(cant_promote, parse_mode=ParseMode.HTML) 266 | 267 | return promote_rights 268 | 269 | 270 | def can_restrict(func): 271 | @wraps(func) 272 | def restrict_rights(bot: Bot, update: Update, *args, **kwargs): 273 | 274 | chat = update.effective_chat 275 | update_chat_title = chat.title 276 | message_chat_title = update.effective_message.chat.title 277 | 278 | if update_chat_title == message_chat_title: 279 | cant_restrict = f"I can't restrict people here!\nMake sure I'm admin and can restrict users." 280 | else: 281 | cant_restrict = f"I can't restrict people in {update_chat_title}!\nMake sure I'm admin there and can restrict users." 282 | 283 | if chat.get_member(bot.id).can_restrict_members: 284 | return func(bot, update, *args, **kwargs) 285 | else: 286 | update.effective_message.reply_text(cant_restrict, parse_mode=ParseMode.HTML) 287 | 288 | return restrict_rights 289 | 290 | 291 | def connection_status(func): 292 | @wraps(func) 293 | def connected_status(bot: Bot, update: Update, *args, **kwargs): 294 | 295 | conn = connected(bot, update, update.effective_chat, update.effective_user.id, need_admin=False) 296 | 297 | if conn: 298 | chat = dispatcher.bot.getChat(conn) 299 | update.__setattr__("_effective_chat", chat) 300 | return func(bot, update, *args, **kwargs) 301 | else: 302 | if update.effective_message.chat.type == "private": 303 | update.effective_message.reply_text("Send /connect in a group that you and I have in common first.") 304 | return connected_status 305 | 306 | return func(bot, update, *args, **kwargs) 307 | 308 | return connected_status 309 | 310 | 311 | #Workaround for circular import with connection.py 312 | from bot.modules import connection 313 | connected = connection.connected 314 | -------------------------------------------------------------------------------- /bot/modules/misc.py: -------------------------------------------------------------------------------- 1 | import html 2 | import re 3 | from typing import List 4 | 5 | import requests 6 | from telegram import Bot, Update, MessageEntity, ParseMode 7 | from telegram.error import BadRequest 8 | from telegram.ext import CommandHandler, run_async, Filters 9 | from telegram.utils.helpers import mention_html 10 | 11 | from bot import dispatcher, OWNER_ID, SUDO_USERS, SUPPORT_USERS, DEV_USERS, WHITELIST_USERS 12 | from bot.__main__ import STATS, USER_INFO, TOKEN 13 | from bot.modules.disable import DisableAbleCommandHandler 14 | from bot.modules.helper_funcs.chat_status import user_admin, sudo_plus 15 | from bot.modules.helper_funcs.extraction import extract_user 16 | 17 | from io import BytesIO 18 | from time import sleep 19 | from typing import Optional, List 20 | from telegram import TelegramError, Chat, Message 21 | from telegram import Update, Bot 22 | from telegram.error import BadRequest 23 | from telegram import ParseMode 24 | from telegram.ext import MessageHandler, Filters, CommandHandler 25 | from telegram.ext.dispatcher import run_async 26 | from telegram.utils.helpers import escape_markdown 27 | from html import escape 28 | from bot.modules.helper_funcs.chat_status import is_user_ban_protected, bot_admin 29 | 30 | from bot import dispatcher, OWNER_ID, SUDO_USERS, SUPPORT_USERS, LOGGER 31 | from bot.modules.helper_funcs.filters import CustomFilters 32 | 33 | USERS_GROUP = 4 34 | 35 | 36 | def escape_html(word): 37 | return escape(word) 38 | 39 | 40 | @run_async 41 | def quickscope(bot: Bot, update: Update, args: List[int]): 42 | if args: 43 | chat_id = str(args[1]) 44 | to_kick = str(args[0]) 45 | else: 46 | update.effective_message.reply_text("You don't seem to be referring to a chat/user") 47 | try: 48 | bot.kick_chat_member(chat_id, to_kick) 49 | update.effective_message.reply_text("Attempted banning " + to_kick + " from" + chat_id) 50 | except BadRequest as excp: 51 | update.effective_message.reply_text(excp.message + " " + to_kick) 52 | 53 | 54 | @run_async 55 | def quickunban(bot: Bot, update: Update, args: List[int]): 56 | if args: 57 | chat_id = str(args[1]) 58 | to_kick = str(args[0]) 59 | else: 60 | update.effective_message.reply_text("You don't seem to be referring to a chat/user") 61 | try: 62 | bot.unban_chat_member(chat_id, to_kick) 63 | update.effective_message.reply_text("Attempted unbanning " + to_kick + " from" + chat_id) 64 | except BadRequest as excp: 65 | update.effective_message.reply_text(excp.message + " " + to_kick) 66 | 67 | 68 | @run_async 69 | def snipe(bot: Bot, update: Update, args: List[str]): 70 | try: 71 | chat_id = str(args[0]) 72 | del args[0] 73 | except TypeError as excp: 74 | update.effective_message.reply_text("Please give me a chat to echo to!") 75 | to_send = " ".join(args) 76 | if len(to_send) >= 2: 77 | try: 78 | bot.sendMessage(int(chat_id), str(to_send)) 79 | except TelegramError: 80 | LOGGER.warning("Couldn't send to group %s", str(chat_id)) 81 | update.effective_message.reply_text("Couldn't send the message. Perhaps I'm not part of that group?") 82 | 83 | 84 | 85 | @bot_admin 86 | def leavechat(bot: Bot, update: Update, args: List[int]): 87 | if args: 88 | chat_id = int(args[0]) 89 | else: 90 | update.effective_message.reply_text("You do not seem to be referring to a chat!Send a valid chat ID") 91 | try: 92 | chat = bot.getChat(chat_id) 93 | titlechat = bot.get_chat(chat_id).title 94 | bot.sendMessage(chat_id, "`I Go Away!`") 95 | bot.leaveChat(chat_id) 96 | update.effective_message.reply_text("I left group {}".format(titlechat)) 97 | 98 | except BadRequest as excp: 99 | if excp.message == "Chat not found": 100 | update.effective_message.reply_text("It looks like I've been kicked out of the group :p") 101 | else: 102 | return 103 | 104 | @run_async 105 | def slist(bot: Bot, update: Update): 106 | message = update.effective_message 107 | text1 = "My Sudo Users are❤:" 108 | text2 = "My Support Users are🤍:" 109 | for user_id in SUDO_USERS: 110 | try: 111 | user = bot.get_chat(user_id) 112 | name = "[{}](tg://user?id={})".format(user.first_name + (user.last_name or ""), user.id) 113 | if user.username: 114 | name = escape_html("@" + user.username) 115 | text1 += "\n - `{}`".format(name) 116 | except BadRequest as excp: 117 | if excp.message == 'Chat not found': 118 | text1 += "\n - ({}) - not found".format(user_id) 119 | for user_id in SUPPORT_USERS: 120 | try: 121 | user = bot.get_chat(user_id) 122 | name = "[{}](tg://user?id={})".format(user.first_name + (user.last_name or ""), user.id) 123 | if user.username: 124 | name = escape_html("@" + user.username) 125 | text2 += "\n - `{}`".format(name) 126 | except BadRequest as excp: 127 | if excp.message == 'Chat not found': 128 | text2 += "\n - ({}) - not found".format(user_id) 129 | message.reply_text(text1 + "\n", parse_mode=ParseMode.MARKDOWN) 130 | message.reply_text(text2 + "\n", parse_mode=ParseMode.MARKDOWN) 131 | 132 | MARKDOWN_HELP = f""" 133 | Markdown is a very powerful formatting tool supported by telegram. {dispatcher.bot.first_name} has some enhancements, to make sure that \ 134 | saved messages are correctly parsed, and to allow you to create buttons. 135 | 136 | - _italic_: wrapping text with '_' will produce italic text 137 | - *bold*: wrapping text with '*' will produce bold text 138 | - `code`: wrapping text with '`' will produce monospaced text, also known as 'code' 139 | - [sometext](someURL): this will create a link - the message will just show sometext, \ 140 | and tapping on it will open the page at someURL. 141 | EG: [test](example.com) 142 | 143 | - [buttontext](buttonurl:someURL): this is a special enhancement to allow users to have telegram \ 144 | buttons in their markdown. buttontext will be what is displayed on the button, and someurl \ 145 | will be the url which is opened. 146 | EG: [This is a button](buttonurl:example.com) 147 | 148 | If you want multiple buttons on the same line, use :same, as such: 149 | [one](buttonurl://example.com) 150 | [two](buttonurl://google.com:same) 151 | This will create two buttons on a single line, instead of one button per line. 152 | 153 | Keep in mind that your message MUST contain some text other than just a button! 154 | """ 155 | 156 | 157 | @run_async 158 | def get_id(bot: Bot, update: Update, args: List[str]): 159 | message = update.effective_message 160 | chat = update.effective_chat 161 | msg = update.effective_message 162 | user_id = extract_user(msg, args) 163 | 164 | if user_id: 165 | 166 | if msg.reply_to_message and msg.reply_to_message.forward_from: 167 | 168 | user1 = message.reply_to_message.from_user 169 | user2 = message.reply_to_message.forward_from 170 | 171 | msg.reply_text(f"The original sender, {html.escape(user2.first_name)}," 172 | f" has an ID of {user2.id}.\n" 173 | f"The forwarder, {html.escape(user1.first_name)}," 174 | f" has an ID of {user1.id}.", 175 | parse_mode=ParseMode.HTML) 176 | 177 | else: 178 | 179 | user = bot.get_chat(user_id) 180 | msg.reply_text(f"{html.escape(user.first_name)}'s id is {user.id}.", 181 | parse_mode=ParseMode.HTML) 182 | 183 | else: 184 | 185 | if chat.type == "private": 186 | msg.reply_text(f"Your id is {chat.id}.", 187 | parse_mode=ParseMode.HTML) 188 | 189 | else: 190 | msg.reply_text(f"This group's id is {chat.id}.", 191 | parse_mode=ParseMode.HTML) 192 | 193 | 194 | @run_async 195 | def gifid(bot: Bot, update: Update): 196 | msg = update.effective_message 197 | if msg.reply_to_message and msg.reply_to_message.animation: 198 | update.effective_message.reply_text(f"Gif ID:\n{msg.reply_to_message.animation.file_id}", 199 | parse_mode=ParseMode.HTML) 200 | else: 201 | update.effective_message.reply_text("Please reply to a gif to get its ID.") 202 | 203 | @run_async 204 | @user_admin 205 | def echo(bot: Bot, update: Update): 206 | args = update.effective_message.text.split(None, 1) 207 | message = update.effective_message 208 | 209 | if message.reply_to_message: 210 | message.reply_to_message.reply_text(args[1]) 211 | else: 212 | message.reply_text(args[1], quote=False) 213 | 214 | message.delete() 215 | 216 | 217 | @run_async 218 | def markdown_help(bot: Bot, update: Update): 219 | update.effective_message.reply_text(MARKDOWN_HELP, parse_mode=ParseMode.HTML) 220 | update.effective_message.reply_text("Try forwarding the following message to me, and you'll see!") 221 | update.effective_message.reply_text("/save test This is a markdown test. _italics_, *bold*, `code`, " 222 | "[URL](example.com) [button](buttonurl:github.com) " 223 | "[button2](buttonurl://google.com:same)") 224 | 225 | 226 | @run_async 227 | @sudo_plus 228 | def stats(bot: Bot, update: Update): 229 | stats = "Current Bot Stats\n" + "\n".join([mod.__stats__() for mod in STATS]) 230 | result = re.sub(r'(\d+)', r'\1', stats) 231 | update.effective_message.reply_text(result, parse_mode=ParseMode.HTML) 232 | 233 | 234 | __help__ = """ 235 | • /id: get the current group id. If used by replying to a message, gets that user's id. 236 | • /info: get information about a user. 237 | **Sudo/owner only:** 238 | • /Stats: check bot's stats 239 | **Users:** 240 | • /slist Gives a list of sudo and support users 241 | """ 242 | 243 | ID_HANDLER = DisableAbleCommandHandler("id", get_id, pass_args=True) 244 | GIFID_HANDLER = DisableAbleCommandHandler("gifid", gifid) 245 | ECHO_HANDLER = DisableAbleCommandHandler("echo", echo, filters=Filters.group) 246 | MD_HELP_HANDLER = CommandHandler("markdownhelp", markdown_help, filters=Filters.private) 247 | STATS_HANDLER = CommandHandler("stats", stats) 248 | 249 | SNIPE_HANDLER = CommandHandler("snipe", snipe, pass_args=True, filters=Filters.user(OWNER_ID)) 250 | QUICKSCOPE_HANDLER = CommandHandler("quickscope", quickscope, pass_args=True, filters=CustomFilters.sudo_filter) 251 | QUICKUNBAN_HANDLER = CommandHandler("quickunban", quickunban, pass_args=True, filters=CustomFilters.sudo_filter) 252 | 253 | LEAVECHAT_HANDLER = CommandHandler(["leavechat","leave"], leavechat, pass_args=True, filters=Filters.user(OWNER_ID)) 254 | SLIST_HANDLER = CommandHandler("slist", slist, 255 | filters=CustomFilters.sudo_filter | CustomFilters.support_filter) 256 | 257 | dispatcher.add_handler(SNIPE_HANDLER) 258 | dispatcher.add_handler(QUICKSCOPE_HANDLER) 259 | dispatcher.add_handler(QUICKUNBAN_HANDLER) 260 | 261 | dispatcher.add_handler(LEAVECHAT_HANDLER) 262 | dispatcher.add_handler(SLIST_HANDLER) 263 | 264 | dispatcher.add_handler(ID_HANDLER) 265 | dispatcher.add_handler(GIFID_HANDLER) 266 | dispatcher.add_handler(ECHO_HANDLER) 267 | dispatcher.add_handler(MD_HELP_HANDLER) 268 | dispatcher.add_handler(STATS_HANDLER) 269 | 270 | __mod_name__ = "MISC 🚀" 271 | __command_list__ = ["id", "echo"] 272 | __handlers__ = [ID_HANDLER, ECHO_HANDLER, MD_HELP_HANDLER, STATS_HANDLER] 273 | -------------------------------------------------------------------------------- /bot/modules/disable.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | from typing import Union, List 3 | 4 | from future.utils import string_types 5 | from telegram import Bot, Update, ParseMode 6 | from telegram.ext import CommandHandler, RegexHandler, MessageHandler 7 | from telegram.utils.helpers import escape_markdown 8 | 9 | from bot import dispatcher 10 | from bot.modules.helper_funcs.handlers import CMD_STARTERS, CustomCommandHandler 11 | from bot.modules.helper_funcs.misc import is_module_loaded 12 | 13 | FILENAME = __name__.rsplit(".", 1)[-1] 14 | 15 | # If module is due to be loaded, then setup all the magical handlers 16 | if is_module_loaded(FILENAME): 17 | 18 | from telegram.ext.dispatcher import run_async 19 | 20 | from bot.modules.helper_funcs.chat_status import user_admin, is_user_admin, connection_status 21 | from bot.modules.sql import disable_sql as sql 22 | 23 | DISABLE_CMDS = [] 24 | DISABLE_OTHER = [] 25 | ADMIN_CMDS = [] 26 | 27 | 28 | class DisableAbleCommandHandler(CustomCommandHandler): 29 | 30 | def __init__(self, command, callback, admin_ok=False, filters=None, **kwargs): 31 | super().__init__(command, callback, **kwargs) 32 | self.admin_ok = admin_ok 33 | self.filters = filters 34 | if isinstance(command, string_types): 35 | DISABLE_CMDS.append(command) 36 | if admin_ok: 37 | ADMIN_CMDS.append(command) 38 | else: 39 | DISABLE_CMDS.extend(command) 40 | if admin_ok: 41 | ADMIN_CMDS.extend(command) 42 | 43 | def check_update(self, update): 44 | chat = update.effective_chat 45 | user = update.effective_user 46 | 47 | if super().check_update(update): 48 | 49 | # Should be safe since check_update passed. 50 | command = update.effective_message.text_html.split(None, 1)[0][1:].split('@')[0] 51 | 52 | # disabled, admincmd, user admin 53 | if sql.is_command_disabled(chat.id, command): 54 | if command in ADMIN_CMDS and is_user_admin(chat, user.id): 55 | return True 56 | 57 | # not disabled 58 | else: 59 | return True 60 | 61 | 62 | class DisableAbleMessageHandler(MessageHandler): 63 | def __init__(self, filters, callback, friendly, **kwargs): 64 | super().__init__(filters, callback, **kwargs) 65 | DISABLE_OTHER.append(friendly) 66 | self.friendly = friendly 67 | self.filters = filters 68 | 69 | def check_update(self, update): 70 | 71 | chat = update.effective_chat 72 | if super().check_update(update): 73 | if sql.is_command_disabled(chat.id, self.friendly): 74 | return False 75 | else: 76 | return True 77 | 78 | 79 | class DisableAbleRegexHandler(RegexHandler): 80 | def __init__(self, pattern, callback, friendly="", filters=None, **kwargs): 81 | super().__init__(pattern, callback, filters, **kwargs) 82 | DISABLE_OTHER.append(friendly) 83 | self.friendly = friendly 84 | 85 | def check_update(self, update): 86 | chat = update.effective_chat 87 | if super().check_update(update): 88 | if sql.is_command_disabled(chat.id, self.friendly): 89 | return False 90 | else: 91 | return True 92 | 93 | 94 | @run_async 95 | @connection_status 96 | @user_admin 97 | def disable(bot: Bot, update: Update, args: List[str]): 98 | chat = update.effective_chat 99 | if len(args) >= 1: 100 | disable_cmd = args[0] 101 | if disable_cmd.startswith(CMD_STARTERS): 102 | disable_cmd = disable_cmd[1:] 103 | 104 | if disable_cmd in set(DISABLE_CMDS + DISABLE_OTHER): 105 | sql.disable_command(chat.id, str(disable_cmd).lower()) 106 | update.effective_message.reply_text(f"Disabled the use of `{disable_cmd}`", 107 | parse_mode=ParseMode.MARKDOWN) 108 | else: 109 | update.effective_message.reply_text("That command can't be disabled") 110 | 111 | else: 112 | update.effective_message.reply_text("What should I disable?") 113 | 114 | 115 | @run_async 116 | @connection_status 117 | @user_admin 118 | def disable_module(bot: Bot, update: Update, args: List[str]): 119 | chat = update.effective_chat 120 | if len(args) >= 1: 121 | disable_module = "bot.modules." + args[0].rsplit(".", 1)[0] 122 | 123 | try: 124 | module = importlib.import_module(disable_module) 125 | except: 126 | update.effective_message.reply_text("Does that module even exist?") 127 | return 128 | 129 | try: 130 | command_list = module.__command_list__ 131 | except: 132 | update.effective_message.reply_text("Module does not contain command list!") 133 | return 134 | 135 | disabled_cmds = [] 136 | failed_disabled_cmds = [] 137 | 138 | for disable_cmd in command_list: 139 | if disable_cmd.startswith(CMD_STARTERS): 140 | disable_cmd = disable_cmd[1:] 141 | 142 | if disable_cmd in set(DISABLE_CMDS + DISABLE_OTHER): 143 | sql.disable_command(chat.id, str(disable_cmd).lower()) 144 | disabled_cmds.append(disable_cmd) 145 | else: 146 | failed_disabled_cmds.append(disable_cmd) 147 | 148 | if disabled_cmds: 149 | disabled_cmds_string = ", ".join(disabled_cmds) 150 | update.effective_message.reply_text(f"Disabled the uses of `{disabled_cmds_string}`", 151 | parse_mode=ParseMode.MARKDOWN) 152 | 153 | if failed_disabled_cmds: 154 | failed_disabled_cmds_string = ", ".join(failed_disabled_cmds) 155 | update.effective_message.reply_text(f"Commands `{failed_disabled_cmds_string}` can't be disabled", 156 | parse_mode=ParseMode.MARKDOWN) 157 | 158 | else: 159 | update.effective_message.reply_text("What should I disable?") 160 | 161 | 162 | @run_async 163 | @connection_status 164 | @user_admin 165 | def enable(bot: Bot, update: Update, args: List[str]): 166 | 167 | chat = update.effective_chat 168 | if len(args) >= 1: 169 | enable_cmd = args[0] 170 | if enable_cmd.startswith(CMD_STARTERS): 171 | enable_cmd = enable_cmd[1:] 172 | 173 | if sql.enable_command(chat.id, enable_cmd): 174 | update.effective_message.reply_text(f"Enabled the use of `{enable_cmd}`", 175 | parse_mode=ParseMode.MARKDOWN) 176 | else: 177 | update.effective_message.reply_text("Is that even disabled?") 178 | 179 | else: 180 | update.effective_message.reply_text("What should I enable?") 181 | 182 | 183 | @run_async 184 | @connection_status 185 | @user_admin 186 | def enable_module(bot: Bot, update: Update, args: List[str]): 187 | chat = update.effective_chat 188 | 189 | if len(args) >= 1: 190 | enable_module = "bot.modules." + args[0].rsplit(".", 1)[0] 191 | 192 | try: 193 | module = importlib.import_module(enable_module) 194 | except: 195 | update.effective_message.reply_text("Does that module even exist?") 196 | return 197 | 198 | try: 199 | command_list = module.__command_list__ 200 | except: 201 | update.effective_message.reply_text("Module does not contain command list!") 202 | return 203 | 204 | enabled_cmds = [] 205 | failed_enabled_cmds = [] 206 | 207 | for enable_cmd in command_list: 208 | if enable_cmd.startswith(CMD_STARTERS): 209 | enable_cmd = enable_cmd[1:] 210 | 211 | if sql.enable_command(chat.id, enable_cmd): 212 | enabled_cmds.append(enable_cmd) 213 | else: 214 | failed_enabled_cmds.append(enable_cmd) 215 | 216 | if enabled_cmds: 217 | enabled_cmds_string = ", ".join(enabled_cmds) 218 | update.effective_message.reply_text(f"Enabled the uses of `{enabled_cmds_string}`", 219 | parse_mode=ParseMode.MARKDOWN) 220 | 221 | if failed_enabled_cmds: 222 | failed_enabled_cmds_string = ", ".join(failed_enabled_cmds) 223 | update.effective_message.reply_text(f"Are the commands `{failed_enabled_cmds_string}` even disabled?", 224 | parse_mode=ParseMode.MARKDOWN) 225 | 226 | else: 227 | update.effective_message.reply_text("What should I enable?") 228 | 229 | 230 | @run_async 231 | @connection_status 232 | @user_admin 233 | def list_cmds(bot: Bot, update: Update): 234 | if DISABLE_CMDS + DISABLE_OTHER: 235 | result = "" 236 | for cmd in set(DISABLE_CMDS + DISABLE_OTHER): 237 | result += f" - `{escape_markdown(cmd)}`\n" 238 | update.effective_message.reply_text(f"The following commands are toggleable:\n{result}", 239 | parse_mode=ParseMode.MARKDOWN) 240 | else: 241 | update.effective_message.reply_text("No commands can be disabled.") 242 | 243 | 244 | # do not async 245 | def build_curr_disabled(chat_id: Union[str, int]) -> str: 246 | disabled = sql.get_all_disabled(chat_id) 247 | if not disabled: 248 | return "No commands are disabled!" 249 | 250 | result = "" 251 | for cmd in disabled: 252 | result += " - `{}`\n".format(escape_markdown(cmd)) 253 | return "The following commands are currently restricted:\n{}".format(result) 254 | 255 | 256 | @run_async 257 | @connection_status 258 | def commands(bot: Bot, update: Update): 259 | chat = update.effective_chat 260 | update.effective_message.reply_text(build_curr_disabled(chat.id), parse_mode=ParseMode.MARKDOWN) 261 | 262 | 263 | def __stats__(): 264 | return f"{sql.num_disabled()} disabled items, across {sql.num_chats()} chats." 265 | 266 | 267 | def __migrate__(old_chat_id, new_chat_id): 268 | sql.migrate_chat(old_chat_id, new_chat_id) 269 | 270 | 271 | def __chat_settings__(chat_id, user_id): 272 | return build_curr_disabled(chat_id) 273 | 274 | 275 | DISABLE_HANDLER = CommandHandler("disable", disable, pass_args=True) 276 | DISABLE_MODULE_HANDLER = CommandHandler("disablemodule", disable_module, pass_args=True) 277 | ENABLE_HANDLER = CommandHandler("enable", enable, pass_args=True) 278 | ENABLE_MODULE_HANDLER = CommandHandler("enablemodule", enable_module, pass_args=True) 279 | COMMANDS_HANDLER = CommandHandler(["cmds", "disabled"], commands) 280 | TOGGLE_HANDLER = CommandHandler("listcmds", list_cmds) 281 | 282 | dispatcher.add_handler(DISABLE_HANDLER) 283 | dispatcher.add_handler(DISABLE_MODULE_HANDLER) 284 | dispatcher.add_handler(ENABLE_HANDLER) 285 | dispatcher.add_handler(ENABLE_MODULE_HANDLER) 286 | dispatcher.add_handler(COMMANDS_HANDLER) 287 | dispatcher.add_handler(TOGGLE_HANDLER) 288 | 289 | __help__ = """ 290 | • /cmds: check the current status of disabled commands 291 | *Admin only:* 292 | • /enable : enable that command 293 | • /disable : disable that command 294 | • /enablemodule : enable all commands in that module 295 | • /disablemodule : disable all commands in that module 296 | • /listcmds: list all possible toggleable commands 297 | """ 298 | 299 | __mod_name__ = "Disable ❎" 300 | 301 | else: 302 | DisableAbleCommandHandler = CommandHandler 303 | DisableAbleRegexHandler = RegexHandler 304 | DisableAbleMessageHandler = MessageHandler 305 | -------------------------------------------------------------------------------- /bot/modules/devpromoter.py: -------------------------------------------------------------------------------- 1 | import html 2 | import json 3 | import html 4 | import os 5 | from typing import List, Optional 6 | 7 | from telegram import Bot, Update, ParseMode, TelegramError 8 | from telegram.ext import CommandHandler, run_async 9 | from telegram.utils.helpers import mention_html 10 | 11 | from bot import dispatcher, WHITELIST_USERS, SUPPORT_USERS, SUDO_USERS, DEV_USERS, OWNER_ID 12 | from bot.modules.helper_funcs.chat_status import whitelist_plus, dev_plus 13 | from bot.modules.helper_funcs.extraction import extract_user 14 | from bot.modules.log_channel import gloggable 15 | 16 | ELEVATED_USERS_FILE = os.path.join(os.getcwd(), 'bot/elevated_users.json') 17 | 18 | 19 | def check_user_id(user_id: int, bot: Bot) -> Optional[str]: 20 | if not user_id: 21 | reply = "That...is a chat!" 22 | 23 | elif user_id == bot.id: 24 | reply = "This does not work that way." 25 | 26 | else: 27 | reply = None 28 | return reply 29 | 30 | 31 | @run_async 32 | @dev_plus 33 | @gloggable 34 | def addsudo(bot: Bot, update: Update, args: List[str]) -> str: 35 | message = update.effective_message 36 | user = update.effective_user 37 | chat = update.effective_chat 38 | 39 | user_id = extract_user(message, args) 40 | user_member = bot.getChat(user_id) 41 | rt = "" 42 | 43 | reply = check_user_id(user_id, bot) 44 | if reply: 45 | message.reply_text(reply) 46 | return "" 47 | 48 | with open(ELEVATED_USERS_FILE, 'r') as infile: 49 | data = json.load(infile) 50 | 51 | if user_id in SUDO_USERS: 52 | message.reply_text("This member is already my SUDO.") 53 | return "" 54 | 55 | if user_id in SUPPORT_USERS: 56 | rt += "This user is already a SUPPORT USER." 57 | data['supports'].remove(user_id) 58 | SUPPORT_USERS.remove(user_id) 59 | 60 | if user_id in WHITELIST_USERS: 61 | rt += "This user is already a WHITELIST USER." 62 | data['whitelists'].remove(user_id) 63 | WHITELIST_USERS.remove(user_id) 64 | 65 | data['sudos'].append(user_id) 66 | SUDO_USERS.append(user_id) 67 | 68 | with open(ELEVATED_USERS_FILE, 'w') as outfile: 69 | json.dump(data, outfile, indent=4) 70 | 71 | update.effective_message.reply_text( 72 | rt + "\nSuccessfully added this user {} to Sudo!".format(user_member.first_name)) 73 | 74 | log_message = (f"#SUDO\n" 75 | f"Admin: {mention_html(user.id, user.first_name)}\n" 76 | f"User: {mention_html(user_member.id, user_member.first_name)}") 77 | 78 | if chat.type != 'private': 79 | log_message = f"{html.escape(chat.title)}:\n" + log_message 80 | 81 | return log_message 82 | 83 | 84 | @run_async 85 | @dev_plus 86 | @gloggable 87 | def addsupport(bot: Bot, update: Update, args: List[str]) -> str: 88 | message = update.effective_message 89 | user = update.effective_user 90 | chat = update.effective_chat 91 | 92 | user_id = extract_user(message, args) 93 | user_member = bot.getChat(user_id) 94 | rt = "" 95 | 96 | reply = check_user_id(user_id, bot) 97 | if reply: 98 | message.reply_text(reply) 99 | return "" 100 | 101 | with open(ELEVATED_USERS_FILE, 'r') as infile: 102 | data = json.load(infile) 103 | 104 | if user_id in SUDO_USERS: 105 | rt += "Demoting status of this SUDO to SUPPORT" 106 | data['sudos'].remove(user_id) 107 | SUDO_USERS.remove(user_id) 108 | 109 | if user_id in SUPPORT_USERS: 110 | message.reply_text("This user is already a SUDO.") 111 | return "" 112 | 113 | if user_id in WHITELIST_USERS: 114 | rt += "Promoting Disaster level from WHITELIST USER to SUPPORT USER" 115 | data['whitelists'].remove(user_id) 116 | WHITELIST_USERS.remove(user_id) 117 | 118 | data['supports'].append(user_id) 119 | SUPPORT_USERS.append(user_id) 120 | 121 | with open(ELEVATED_USERS_FILE, 'w') as outfile: 122 | json.dump(data, outfile, indent=4) 123 | 124 | update.effective_message.reply_text(rt + f"\n{user_member.first_name} was added as a Support User!") 125 | 126 | log_message = (f"#SUPPORT\n" 127 | f"Admin: {mention_html(user.id, user.first_name)}\n" 128 | f"User: {mention_html(user_member.id, user_member.first_name)}") 129 | 130 | if chat.type != 'private': 131 | log_message = "{html.escape(chat.title)}:\n" + log_message 132 | 133 | return log_message 134 | 135 | 136 | @run_async 137 | @dev_plus 138 | @gloggable 139 | def addwhitelist(bot: Bot, update: Update, args: List[str]) -> str: 140 | message = update.effective_message 141 | user = update.effective_user 142 | chat = update.effective_chat 143 | 144 | user_id = extract_user(message, args) 145 | user_member = bot.getChat(user_id) 146 | rt = "" 147 | 148 | reply = check_user_id(user_id, bot) 149 | if reply: 150 | message.reply_text(reply) 151 | return "" 152 | 153 | with open(ELEVATED_USERS_FILE, 'r') as infile: 154 | data = json.load(infile) 155 | 156 | if user_id in SUDO_USERS: 157 | rt += "This member is a SUDO, Demoting to SUDO." 158 | data['sudos'].remove(user_id) 159 | SUDO_USERS.remove(user_id) 160 | 161 | if user_id in SUPPORT_USERS: 162 | rt += "This user is already a SUPPORT, Demoting to SUPPORT" 163 | data['supports'].remove(user_id) 164 | SUPPORT_USERS.remove(user_id) 165 | 166 | if user_id in WHITELIST_USERS: 167 | message.reply_text("This user is already a WHITELIST USER.") 168 | return "" 169 | 170 | data['whitelists'].append(user_id) 171 | WHITELIST_USERS.append(user_id) 172 | 173 | with open(ELEVATED_USERS_FILE, 'w') as outfile: 174 | json.dump(data, outfile, indent=4) 175 | 176 | update.effective_message.reply_text( 177 | rt + f"\nSuccessfully promoted {user_member.first_name} to a Whitelist User!") 178 | 179 | log_message = (f"#WHITELIST\n" 180 | f"Admin: {mention_html(user.id, user.first_name)} \n" 181 | f"User: {mention_html(user_member.id, user_member.first_name)}") 182 | 183 | if chat.type != 'private': 184 | log_message = f"{html.escape(chat.title)}:\n" + log_message 185 | 186 | return log_message 187 | 188 | 189 | @run_async 190 | @dev_plus 191 | @gloggable 192 | def removesudo(bot: Bot, update: Update, args: List[str]) -> str: 193 | message = update.effective_message 194 | user = update.effective_user 195 | chat = update.effective_chat 196 | 197 | user_id = extract_user(message, args) 198 | user_member = bot.getChat(user_id) 199 | 200 | reply = check_user_id(user_id, bot) 201 | if reply: 202 | message.reply_text(reply) 203 | return "" 204 | 205 | with open(ELEVATED_USERS_FILE, 'r') as infile: 206 | data = json.load(infile) 207 | 208 | if user_id in SUDO_USERS: 209 | message.reply_text("Demoting to normal user") 210 | SUDO_USERS.remove(user_id) 211 | data['sudos'].remove(user_id) 212 | 213 | with open(ELEVATED_USERS_FILE, 'w') as outfile: 214 | json.dump(data, outfile, indent=4) 215 | 216 | log_message = (f"#UNSUDO\n" 217 | f"Admin: {mention_html(user.id, user.first_name)}\n" 218 | f"User: {mention_html(user_member.id, user_member.first_name)}") 219 | 220 | if chat.type != 'private': 221 | log_message = "{}:\n".format(html.escape(chat.title)) + log_message 222 | 223 | return log_message 224 | 225 | else: 226 | message.reply_text("This user is not a sudo!") 227 | return "" 228 | 229 | 230 | @run_async 231 | @dev_plus 232 | @gloggable 233 | def removesupport(bot: Bot, update: Update, args: List[str]) -> str: 234 | message = update.effective_message 235 | user = update.effective_user 236 | chat = update.effective_chat 237 | 238 | user_id = extract_user(message, args) 239 | user_member = bot.getChat(user_id) 240 | 241 | reply = check_user_id(user_id, bot) 242 | if reply: 243 | message.reply_text(reply) 244 | return "" 245 | 246 | with open(ELEVATED_USERS_FILE, 'r') as infile: 247 | data = json.load(infile) 248 | 249 | if user_id in SUPPORT_USERS: 250 | message.reply_text("Demoting to Civilian") 251 | SUPPORT_USERS.remove(user_id) 252 | data['supports'].remove(user_id) 253 | 254 | with open(ELEVATED_USERS_FILE, 'w') as outfile: 255 | json.dump(data, outfile, indent=4) 256 | 257 | log_message = (f"#UNSUPPORT\n" 258 | f"Admin: {mention_html(user.id, user.first_name)}\n" 259 | f"User: {mention_html(user_member.id, user_member.first_name)}") 260 | 261 | if chat.type != 'private': 262 | log_message = f"{html.escape(chat.title)}:\n" + log_message 263 | 264 | return log_message 265 | 266 | else: 267 | message.reply_text("This user is not a support!") 268 | return "" 269 | 270 | 271 | @run_async 272 | @dev_plus 273 | @gloggable 274 | def removewhitelist(bot: Bot, update: Update, args: List[str]) -> str: 275 | message = update.effective_message 276 | user = update.effective_user 277 | chat = update.effective_chat 278 | 279 | user_id = extract_user(message, args) 280 | user_member = bot.getChat(user_id) 281 | 282 | reply = check_user_id(user_id, bot) 283 | if reply: 284 | message.reply_text(reply) 285 | return "" 286 | 287 | with open(ELEVATED_USERS_FILE, 'r') as infile: 288 | data = json.load(infile) 289 | 290 | if user_id in WHITELIST_USERS: 291 | message.reply_text("Demoting to normal user") 292 | WHITELIST_USERS.remove(user_id) 293 | data['whitelists'].remove(user_id) 294 | 295 | with open(ELEVATED_USERS_FILE, 'w') as outfile: 296 | json.dump(data, outfile, indent=4) 297 | 298 | log_message = (f"#UNWHITELIST\n" 299 | f"Admin: {mention_html(user.id, user.first_name)}\n" 300 | f"User: {mention_html(user_member.id, user_member.first_name)}") 301 | 302 | if chat.type != 'private': 303 | log_message = f"{html.escape(chat.title)}:\n" + log_message 304 | 305 | return log_message 306 | else: 307 | message.reply_text("This user is not a whitelist!") 308 | return "" 309 | 310 | 311 | @run_async 312 | @whitelist_plus 313 | def whitelistlist(bot: Bot, update: Update): 314 | reply = "Whitelist user🤍:\n" 315 | for each_user in WHITELIST_USERS: 316 | user_id = int(each_user) 317 | try: 318 | user = bot.get_chat(user_id) 319 | 320 | reply += f"• {mention_html(user_id, user.first_name)}\n" 321 | except TelegramError: 322 | pass 323 | update.effective_message.reply_text(reply, parse_mode=ParseMode.HTML) 324 | 325 | 326 | @run_async 327 | @whitelist_plus 328 | def supportlist(bot: Bot, update: Update): 329 | reply = "Support List🧡:\n" 330 | for each_user in SUPPORT_USERS: 331 | user_id = int(each_user) 332 | try: 333 | user = bot.get_chat(user_id) 334 | reply += f"• {mention_html(user_id, user.first_name)}\n" 335 | except TelegramError: 336 | pass 337 | update.effective_message.reply_text(reply, parse_mode=ParseMode.HTML) 338 | 339 | 340 | @run_async 341 | @whitelist_plus 342 | def sudolist(bot: Bot, update: Update): 343 | true_sudo = list(set(SUDO_USERS) - set(DEV_USERS)) 344 | reply = "Sudo list❤:\n" 345 | for each_user in true_sudo: 346 | user_id = int(each_user) 347 | try: 348 | user = bot.get_chat(user_id) 349 | reply += f"• {mention_html(user_id, user.first_name)}\n" 350 | except TelegramError: 351 | pass 352 | update.effective_message.reply_text(reply, parse_mode=ParseMode.HTML) 353 | 354 | 355 | @run_async 356 | @whitelist_plus 357 | def devlist(bot: Bot, update: Update): 358 | true_dev = list(set(DEV_USERS) - {OWNER_ID}) 359 | reply = "My developer list🤎:\n" 360 | for each_user in true_dev: 361 | user_id = int(each_user) 362 | try: 363 | user = bot.get_chat(user_id) 364 | reply += f"• {mention_html(user_id, user.first_name)}\n" 365 | except TelegramError: 366 | pass 367 | update.effective_message.reply_text(reply, parse_mode=ParseMode.HTML) 368 | 369 | 370 | __help__ = """ 371 | *Bot owner only:* 372 | 373 | • /addsudo: promotes the user to Sudo User 374 | • /removesudo: demotes the user from Sudo User 375 | 376 | • /addsupport: promotes the user to Support User 377 | • /removesupport: demotes the user from Support User 378 | 379 | • /addwhitelist: promotes the user to Whitelist User 380 | • /removewhitelist: demotes the user from Whitelist User 381 | 382 | *Bot Admin Lists:* 383 | • /whitelistlist - List whitelisted users. 384 | • /supportlist - List support users. 385 | • /sudolist - List sudo users. 386 | • /devlist - List dev users. 387 | """ 388 | 389 | SUDO_HANDLER = CommandHandler(("addsudo"), addsudo, pass_args=True) 390 | SUPPORT_HANDLER = CommandHandler(("addsupport"), addsupport, pass_args=True) 391 | WHITELIST_HANDLER = CommandHandler(("addwhitelist"), addwhitelist, pass_args=True) 392 | UNSUDO_HANDLER = CommandHandler(("removesudo"), removesudo, pass_args=True) 393 | UNSUPPORT_HANDLER = CommandHandler(("removesupport"), removesupport, pass_args=True) 394 | UNWHITELIST_HANDLER = CommandHandler(("removewhitelist"), removewhitelist, pass_args=True) 395 | 396 | WHITELISTLIST_HANDLER = CommandHandler(["whitelistlist"], whitelistlist) 397 | SUPPORTLIST_HANDLER = CommandHandler(["supportlist"], supportlist) 398 | SUDOLIST_HANDLER = CommandHandler(["sudolist"], sudolist) 399 | DEVLIST_HANDLER = CommandHandler(["devlist"], devlist) 400 | 401 | dispatcher.add_handler(SUDO_HANDLER) 402 | dispatcher.add_handler(SUPPORT_HANDLER) 403 | dispatcher.add_handler(WHITELIST_HANDLER) 404 | dispatcher.add_handler(UNSUDO_HANDLER) 405 | dispatcher.add_handler(UNSUPPORT_HANDLER) 406 | dispatcher.add_handler(UNWHITELIST_HANDLER) 407 | 408 | dispatcher.add_handler(WHITELISTLIST_HANDLER) 409 | dispatcher.add_handler(SUPPORTLIST_HANDLER) 410 | dispatcher.add_handler(SUDOLIST_HANDLER) 411 | dispatcher.add_handler(DEVLIST_HANDLER) 412 | 413 | __mod_name__ = "Admin Access♿" 414 | __handlers__ = [SUDO_HANDLER, SUPPORT_HANDLER, WHITELIST_HANDLER, 415 | UNSUDO_HANDLER, UNSUPPORT_HANDLER, UNWHITELIST_HANDLER, 416 | WHITELISTLIST_HANDLER, SUPPORTLIST_HANDLER, SUDOLIST_HANDLER, DEVLIST_HANDLER] 417 | -------------------------------------------------------------------------------- /bot/modules/cust_filters.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Optional 3 | 4 | import telegram 5 | from telegram import ParseMode, InlineKeyboardMarkup, Message, Chat 6 | from telegram import Update, Bot 7 | from telegram.error import BadRequest 8 | from telegram.ext import CommandHandler, MessageHandler, DispatcherHandlerStop, run_async, Filters 9 | from telegram.utils.helpers import escape_markdown 10 | 11 | from bot import dispatcher, LOGGER 12 | from bot.modules.disable import DisableAbleCommandHandler 13 | from bot.modules.helper_funcs.chat_status import user_admin 14 | from bot.modules.helper_funcs.extraction import extract_text 15 | from bot.modules.helper_funcs.filters import CustomFilters 16 | from bot.modules.helper_funcs.misc import build_keyboard 17 | from bot.modules.helper_funcs.string_handling import split_quotes, button_markdown_parser 18 | from bot.modules.sql import cust_filters_sql as sql 19 | 20 | from bot.modules.connection import connected 21 | 22 | HANDLER_GROUP = 15 23 | BASIC_FILTER_STRING = "*Filters in this chat:*\n" 24 | 25 | 26 | @run_async 27 | def list_handlers(bot: Bot, update: Update): 28 | chat = update.effective_chat # type: Optional[Chat] 29 | user = update.effective_user # type: Optional[User] 30 | 31 | conn = connected(bot, update, chat, user.id, need_admin=False) 32 | if not conn == False: 33 | chat_id = conn 34 | chat_name = dispatcher.bot.getChat(conn).title 35 | filter_list = "*Filters in {}:*\n" 36 | else: 37 | chat_id = update.effective_chat.id 38 | if chat.type == "private": 39 | chat_name = "local filters" 40 | filter_list = "*local filters:*\n" 41 | else: 42 | chat_name = chat.title 43 | filter_list = "*Filters in {}*:\n".format(chat_name) 44 | 45 | 46 | all_handlers = sql.get_chat_triggers(chat_id) 47 | 48 | if not all_handlers: 49 | update.effective_message.reply_text("No filters in *{}*!".format(chat_name)) 50 | return 51 | 52 | for keyword in all_handlers: 53 | entry = " - {}\n".format(escape_markdown(keyword)) 54 | if len(entry) + len(filter_list) > telegram.MAX_MESSAGE_LENGTH: 55 | update.effective_message.reply_text(filter_list, parse_mode=telegram.ParseMode.MARKDOWN) 56 | filter_list = entry 57 | else: 58 | filter_list += entry 59 | 60 | if not filter_list == BASIC_FILTER_STRING: 61 | update.effective_message.reply_text(filter_list, parse_mode=telegram.ParseMode.MARKDOWN) 62 | 63 | 64 | # NOT ASYNC BECAUSE DISPATCHER HANDLER RAISED 65 | @user_admin 66 | def filters(bot: Bot, update: Update): 67 | chat = update.effective_chat # type: Optional[Chat] 68 | user = update.effective_user # type: Optional[User] 69 | msg = update.effective_message # type: Optional[Message] 70 | args = msg.text.split(None, 1) # use python's maxsplit to separate Cmd, keyword, and reply_text 71 | 72 | conn = connected(bot, update, chat, user.id) 73 | if not conn == False: 74 | chat_id = conn 75 | chat_name = dispatcher.bot.getChat(conn).title 76 | else: 77 | chat_id = update.effective_chat.id 78 | if chat.type == "private": 79 | chat_name = "local filters" 80 | else: 81 | chat_name = chat.title 82 | 83 | if len(args) < 2: 84 | return 85 | 86 | 87 | extracted = split_quotes(args[1]) 88 | if len(extracted) < 1: 89 | return 90 | # set trigger -> lower, so as to avoid adding duplicate filters with different cases 91 | keyword = extracted[0].lower() 92 | 93 | is_sticker = False 94 | is_document = False 95 | is_image = False 96 | is_voice = False 97 | is_audio = False 98 | is_video = False 99 | media_caption = None 100 | has_caption = False 101 | buttons = [] 102 | 103 | # determine what the contents of the filter are - text, image, sticker, etc 104 | if len(extracted) >= 2: 105 | offset = len(extracted[1]) - len(msg.text) # set correct offset relative to command + notename 106 | content, buttons = button_markdown_parser(extracted[1], entities=msg.parse_entities(), offset=offset) 107 | content = content.strip() 108 | if not content: 109 | msg.reply_text("There is no note message - You can't JUST have buttons, you need a message to go with it!") 110 | return 111 | 112 | elif msg.reply_to_message and msg.reply_to_message.sticker: 113 | content = msg.reply_to_message.sticker.file_id 114 | is_sticker = True 115 | # stickers don't have caption in BOT API -_- 116 | 117 | elif msg.reply_to_message and msg.reply_to_message.document: 118 | offset = len(msg.reply_to_message.caption) 119 | media_caption, buttons = button_markdown_parser(msg.reply_to_message.caption, entities=msg.reply_to_message.parse_entities(), offset=offset) 120 | content = msg.reply_to_message.document.file_id 121 | is_document = True 122 | has_caption = True 123 | 124 | elif msg.reply_to_message and msg.reply_to_message.photo: 125 | offset = len(msg.reply_to_message.caption) 126 | media_caption, buttons = button_markdown_parser(msg.reply_to_message.caption, entities=msg.reply_to_message.parse_entities(), offset=offset) 127 | content = msg.reply_to_message.photo[-1].file_id # last elem = best quality 128 | is_image = True 129 | has_caption = True 130 | 131 | elif msg.reply_to_message and msg.reply_to_message.audio: 132 | offset = len(msg.reply_to_message.caption) 133 | media_caption, buttons = button_markdown_parser(msg.reply_to_message.caption, entities=msg.reply_to_message.parse_entities(), offset=offset) 134 | content = msg.reply_to_message.audio.file_id 135 | is_audio = True 136 | has_caption = True 137 | 138 | elif msg.reply_to_message and msg.reply_to_message.voice: 139 | offset = len(msg.reply_to_message.caption) 140 | media_caption, buttons = button_markdown_parser(msg.reply_to_message.caption, entities=msg.reply_to_message.parse_entities(), offset=offset) 141 | content = msg.reply_to_message.voice.file_id 142 | is_voice = True 143 | has_caption = True 144 | 145 | elif msg.reply_to_message and msg.reply_to_message.video: 146 | offset = len(msg.reply_to_message.caption) 147 | media_caption, buttons = button_markdown_parser(msg.reply_to_message.caption, entities=msg.reply_to_message.parse_entities(), offset=offset) 148 | content = msg.reply_to_message.video.file_id 149 | is_video = True 150 | has_caption = True 151 | 152 | else: 153 | msg.reply_text("You didn't specify what to reply with!") 154 | return 155 | 156 | # Add the filter 157 | # Note: perhaps handlers can be removed somehow using sql.get_chat_filters 158 | for handler in dispatcher.handlers.get(HANDLER_GROUP, []): 159 | if handler.filters == (keyword, chat.id): 160 | dispatcher.remove_handler(handler, HANDLER_GROUP) 161 | 162 | sql.add_filter(chat_id, keyword, content, is_sticker, is_document, is_image, is_audio, is_voice, is_video, 163 | buttons, media_caption, has_caption) 164 | 165 | msg.reply_text("Filter '{}' Added==> *{}*!".format(keyword, chat_name), parse_mode=telegram.ParseMode.MARKDOWN) 166 | raise DispatcherHandlerStop 167 | 168 | 169 | # NOT ASYNC BECAUSE DISPATCHER HANDLER RAISED 170 | @user_admin 171 | def stop_filter(bot: Bot, update: Update): 172 | chat = update.effective_chat # type: Optional[Chat] 173 | user = update.effective_user # type: Optional[User] 174 | args = update.effective_message.text.split(None, 1) 175 | 176 | conn = connected(bot, update, chat, user.id) 177 | if not conn == False: 178 | chat_id = conn 179 | chat_name = dispatcher.bot.getChat(conn).title 180 | else: 181 | chat_id = chat.id 182 | if chat.type == "private": 183 | chat_name = "local notes" 184 | else: 185 | chat_name = chat.title 186 | 187 | if len(args) < 2: 188 | return 189 | 190 | chat_filters = sql.get_chat_triggers(chat_id) 191 | 192 | if not chat_filters: 193 | update.effective_message.reply_text("No filters are active here!") 194 | return 195 | 196 | for keyword in chat_filters: 197 | if keyword == args[1]: 198 | sql.remove_filter(chat_id, args[1]) 199 | update.effective_message.reply_text("_Filter Deleted Successfully_ *{}*.".format(chat_name), parse_mode=telegram.ParseMode.MARKDOWN) 200 | raise DispatcherHandlerStop 201 | 202 | update.effective_message.reply_text("Your Filter Keyword is Incorrect please check Your Keyword /filters") 203 | 204 | 205 | @run_async 206 | def reply_filter(bot: Bot, update: Update): 207 | chat = update.effective_chat # type: Optional[Chat] 208 | message = update.effective_message # type: Optional[Message] 209 | to_match = extract_text(message) 210 | if not to_match: 211 | return 212 | 213 | if message.reply_to_message: 214 | message = message.reply_to_message 215 | 216 | 217 | chat_filters = sql.get_chat_triggers(chat.id) 218 | for keyword in chat_filters: 219 | pattern = r"( |^|[^\w])" + re.escape(keyword) + r"( |$|[^\w])" 220 | if re.search(pattern, to_match, flags=re.IGNORECASE): 221 | filt = sql.get_filter(chat.id, keyword) 222 | buttons = sql.get_buttons(chat.id, filt.keyword) 223 | media_caption = filt.caption if filt.caption is not None else "" 224 | if filt.is_sticker: 225 | message.reply_sticker(filt.reply) 226 | elif filt.is_document: 227 | message.reply_document(filt.reply, caption=media_caption, parse_mode=ParseMode.MARKDOWN) 228 | elif filt.is_image: 229 | if len(buttons) > 0: 230 | keyb = build_keyboard(buttons) 231 | keyboard = InlineKeyboardMarkup(keyb) 232 | message.reply_photo(filt.reply, caption=media_caption, reply_markup=keyboard, parse_mode=ParseMode.MARKDOWN) 233 | else: 234 | message.reply_photo(filt.reply, caption=media_caption, parse_mode=ParseMode.MARKDOWN) 235 | elif filt.is_audio: 236 | message.reply_audio(filt.reply, caption=media_caption, parse_mode=ParseMode.MARKDOWN) 237 | elif filt.is_voice: 238 | message.reply_voice(filt.reply, caption=media_caption, parse_mode=ParseMode.MARKDOWN) 239 | elif filt.is_video: 240 | message.reply_video(filt.reply, caption=media_caption, parse_mode=ParseMode.MARKDOWN) 241 | elif filt.has_markdown: 242 | keyb = build_keyboard(buttons) 243 | keyboard = InlineKeyboardMarkup(keyb) 244 | 245 | should_preview_disabled = True 246 | if "telegra.ph" in filt.reply or "youtu.be" in filt.reply: 247 | should_preview_disabled = False 248 | 249 | try: 250 | message.reply_text(filt.reply, parse_mode=ParseMode.MARKDOWN, 251 | disable_web_page_preview=should_preview_disabled, 252 | reply_markup=keyboard) 253 | except BadRequest as excp: 254 | if excp.message == "Unsupported url protocol": 255 | message.reply_text("You seem to be trying to use an unsupported url protocol. Telegram " 256 | "doesn't support buttons for some protocols, such as tg://. Please try " 257 | "again, or ask in @D_ar_k_Angel for help.") 258 | elif excp.message == "Reply message not found": 259 | bot.send_message(chat.id, filt.reply, parse_mode=ParseMode.MARKDOWN, 260 | disable_web_page_preview=True, 261 | reply_markup=keyboard) 262 | else: 263 | message.reply_text("This note could not be sent, as it is incorrectly formatted. Ask in " 264 | "@D_ar_k_Angel if you can't figure out why!") 265 | LOGGER.warning("Message %s could not be parsed", str(filt.reply)) 266 | LOGGER.exception("Could not parse filter %s in chat %s", str(filt.keyword), str(chat.id)) 267 | 268 | else: 269 | # LEGACY - all new filters will have has_markdown set to True. 270 | message.reply_text(filt.reply) 271 | break 272 | 273 | @run_async 274 | @user_admin 275 | def stop_all_filters(bot: Bot, update: Update): 276 | chat = update.effective_chat 277 | user = update.effective_user 278 | message = update.effective_message 279 | 280 | if chat.type == "private": 281 | chat.title = "local filters" 282 | else: 283 | owner = chat.get_member(user.id) 284 | chat.title = chat.title 285 | if owner.status != 'creator': 286 | message.reply_text("You must be this chat creator.") 287 | return 288 | 289 | x = 0 290 | flist = sql.get_chat_triggers(chat.id) 291 | 292 | if not flist: 293 | message.reply_text("There aren't any active filters in {} !".format(chat.title)) 294 | return 295 | 296 | f_flist = [] 297 | for f in flist: 298 | x += 1 299 | f_flist.append(f) 300 | 301 | for fx in f_flist: 302 | sql.remove_filter(chat.id, fx) 303 | 304 | message.reply_text("{} filters from this chat have been removed.".format(x)) 305 | 306 | 307 | def __stats__(): 308 | return "{} filters, across {} chats.".format(sql.num_filters(), sql.num_chats()) 309 | 310 | 311 | def __migrate__(old_chat_id, new_chat_id): 312 | sql.migrate_chat(old_chat_id, new_chat_id) 313 | 314 | 315 | def __chat_settings__(chat_id, user_id): 316 | cust_filters = sql.get_chat_triggers(chat_id) 317 | return "There are `{}` custom filters here.".format(len(cust_filters)) 318 | 319 | 320 | __help__ = """ 321 | • /filters: list all active filters in this chat. 322 | 323 | *Admin only:* 324 | • /filter : add a filter to this chat. The bot will now reply that message whenever 'keyword'\ 325 | is mentioned. If you reply to a sticker with a keyword, the bot will reply with that sticker. NOTE: all filter \ 326 | keywords are in lowercase. If you want your keyword to be a sentence, use quotes. eg: /filter "hey there" How you \ 327 | doin? 328 | • /stop : stop that filter. 329 | • /stopall: stop all filters 330 | 331 | """ 332 | 333 | __mod_name__ = "FILTERS 📜" 334 | 335 | FILTER_HANDLER = CommandHandler("filter", filters) 336 | STOP_HANDLER = CommandHandler("stop", stop_filter) 337 | STOPALL_HANDLER = DisableAbleCommandHandler("stopall", stop_all_filters) 338 | LIST_HANDLER = DisableAbleCommandHandler("filters", list_handlers, admin_ok=True) 339 | CUST_FILTER_HANDLER = MessageHandler(CustomFilters.has_text, reply_filter) 340 | 341 | dispatcher.add_handler(FILTER_HANDLER) 342 | dispatcher.add_handler(STOP_HANDLER) 343 | dispatcher.add_handler(STOPALL_HANDLER) 344 | dispatcher.add_handler(LIST_HANDLER) 345 | dispatcher.add_handler(CUST_FILTER_HANDLER, HANDLER_GROUP) 346 | -------------------------------------------------------------------------------- /bot/modules/connection.py: -------------------------------------------------------------------------------- 1 | import time 2 | import re 3 | 4 | from typing import List 5 | 6 | from telegram import Bot, Update, ParseMode, InlineKeyboardMarkup, InlineKeyboardButton 7 | from telegram.error import BadRequest, Unauthorized 8 | from telegram.ext import CommandHandler, CallbackQueryHandler, Filters, run_async 9 | from telegram.utils.helpers import mention_html 10 | 11 | import bot.modules.sql.connection_sql as sql 12 | from bot import dispatcher, SUDO_USERS, DEV_USERS, spamfilters 13 | from bot.modules.helper_funcs import chat_status 14 | from bot.modules.helper_funcs.extraction import extract_user, extract_user_and_text 15 | from bot.modules.helper_funcs.string_handling import extract_time 16 | 17 | from bot.modules.helper_funcs.alternate import send_message 18 | 19 | user_admin = chat_status.user_admin 20 | 21 | 22 | @user_admin 23 | @run_async 24 | def allow_connections(bot: Bot, update: Update, args: List[str]): 25 | 26 | chat = update.effective_chat 27 | 28 | if chat.type != chat.PRIVATE: 29 | if len(args) >= 1: 30 | var = args[0] 31 | if var == "no": 32 | sql.set_allow_connect_to_chat(chat.id, False) 33 | send_message(update.effective_message, "Connection *Disabled* Successfully") 34 | elif var == "yes": 35 | sql.set_allow_connect_to_chat(chat.id, True) 36 | send_message(update.effective_message, "Connection *Enabled* Successfully") 37 | else: 38 | send_message(update.effective_message, "Please enter `yes` or `no`!", parse_mode=ParseMode.MARKDOWN) 39 | else: 40 | get_settings = sql.allow_connect_to_chat(chat.id) 41 | if get_settings: 42 | send_message(update.effective_message, "Connections to this group are *Allowed* for members!", parse_mode=ParseMode.MARKDOWN) 43 | else: 44 | send_message(update.effective_message, "Connection to this group are *Not Allowed* for members!", parse_mode=ParseMode.MARKDOWN) 45 | else: 46 | send_message(update.effective_message, "This command is for group only. Not in PM!") 47 | 48 | 49 | @run_async 50 | def connection_chat(bot: Bot, update: Update): 51 | 52 | chat = update.effective_chat 53 | user = update.effective_user 54 | 55 | spam = spamfilters(update.effective_message.text, update.effective_message.from_user.id, update.effective_chat.id) 56 | if spam == True: 57 | return 58 | 59 | conn = connected(bot, update, chat, user.id, need_admin=True) 60 | 61 | if conn: 62 | chat = dispatcher.bot.getChat(conn) 63 | chat_name = dispatcher.bot.getChat(conn).title 64 | else: 65 | if update.effective_message.chat.type != "private": 66 | return 67 | chat = update.effective_chat 68 | chat_name = update.effective_message.chat.title 69 | 70 | if conn: 71 | message = "You are currently connected with {}.\n".format(chat_name) 72 | else: 73 | message = "You are currently not connected in any group.\n" 74 | send_message(update.effective_message, message, parse_mode="markdown") 75 | 76 | 77 | @run_async 78 | def connect_chat(bot: Bot, update: Update, args: List[str]): 79 | 80 | chat = update.effective_chat 81 | user = update.effective_user 82 | 83 | spam = spamfilters(update.effective_message.text, update.effective_message.from_user.id, update.effective_chat.id) 84 | if spam == True: 85 | return 86 | 87 | if update.effective_chat.type == 'private': 88 | if len(args) >= 1: 89 | try: 90 | connect_chat = int(args[0]) 91 | getstatusadmin = bot.get_chat_member(connect_chat, update.effective_message.from_user.id) 92 | except ValueError: 93 | try: 94 | connect_chat = str(args[0]) 95 | get_chat = bot.getChat(connect_chat) 96 | connect_chat = get_chat.id 97 | getstatusadmin = bot.get_chat_member(connect_chat, update.effective_message.from_user.id) 98 | except BadRequest: 99 | send_message(update.effective_message, "Please Check Your Chat ID!") 100 | return 101 | except BadRequest: 102 | send_message(update.effective_message, "Please Check Your Chat ID!") 103 | return 104 | 105 | isadmin = getstatusadmin.status in ('administrator', 'creator') 106 | ismember = getstatusadmin.status in ('member') 107 | isallow = sql.allow_connect_to_chat(connect_chat) 108 | 109 | if (isadmin) or (isallow and ismember) or (user.id in SUDO_USERS) or (user.id in DEV_USERS): 110 | connection_status = sql.connect(update.effective_message.from_user.id, connect_chat) 111 | if connection_status: 112 | conn_chat = dispatcher.bot.getChat(connected(bot, update, chat, user.id, need_admin=False)) 113 | chat_name = conn_chat.title 114 | send_message(update.effective_message, "Successfully connected to *{}*. Use /connection for see current available commands.".format(chat_name), parse_mode=ParseMode.MARKDOWN) 115 | sql.add_history_conn(user.id, str(conn_chat.id), chat_name) 116 | else: 117 | send_message(update.effective_message, "_Connection failed!_") 118 | else: 119 | send_message(update.effective_message, "Connection to this chat is not allowed!") 120 | else: 121 | gethistory = sql.get_history_conn(user.id) 122 | if gethistory: 123 | buttons = [ 124 | InlineKeyboardButton(text="❎ Close button", callback_data="connect_close"), 125 | InlineKeyboardButton(text="🧹 Clear history", callback_data="connect_clear") 126 | ] 127 | else: 128 | buttons = [] 129 | conn = connected(bot, update, chat, user.id, need_admin=False) 130 | if conn: 131 | connectedchat = dispatcher.bot.getChat(conn) 132 | text = "_You are connected to *{}* (`{}`)_".format(connectedchat.title, conn) 133 | buttons.append(InlineKeyboardButton(text="🔌 Disconnect", callback_data="connect_disconnect")) 134 | else: 135 | text = "_Write the chat ID or tag to connect!_" 136 | if gethistory: 137 | text += "\n\n*Connection history:*\n" 138 | text += "╒═══「 *Info* 」\n" 139 | text += "│ Sorted: `Newest`\n" 140 | text += "│\n" 141 | buttons = [buttons] 142 | for x in sorted(gethistory.keys(), reverse=True): 143 | htime = time.strftime("%d/%m/%Y", time.localtime(x)) 144 | text += "╞═「 *{}* 」\n│ `{}`\n│ `{}`\n".format(gethistory[x]['chat_name'], gethistory[x]['chat_id'], htime) 145 | text += "│\n" 146 | buttons.append([InlineKeyboardButton(text=gethistory[x]['chat_name'], callback_data="connect({})".format(gethistory[x]['chat_id']))]) 147 | text += "╘══「 Total {} Chats 」".format(str(len(gethistory)) + " (max)" if len(gethistory) == 5 else str(len(gethistory))) 148 | conn_hist = InlineKeyboardMarkup(buttons) 149 | elif buttons: 150 | conn_hist = InlineKeyboardMarkup([buttons]) 151 | else: 152 | conn_hist = None 153 | send_message(update.effective_message, text, parse_mode="markdown", reply_markup=conn_hist) 154 | 155 | else: 156 | getstatusadmin = bot.get_chat_member(chat.id, update.effective_message.from_user.id) 157 | isadmin = getstatusadmin.status in ('administrator', 'creator') 158 | ismember = getstatusadmin.status in ('member') 159 | isallow = sql.allow_connect_to_chat(chat.id) 160 | if (isadmin) or (isallow and ismember) or (user.id in SUDO_USERS) or (user.id in DEV_USERS): 161 | connection_status = sql.connect(update.effective_message.from_user.id, chat.id) 162 | if connection_status: 163 | chat_name = dispatcher.bot.getChat(chat.id).title 164 | send_message(update.effective_message, "Successfully Connected ==> *{}*".format(chat_name), parse_mode=ParseMode.MARKDOWN) 165 | try: 166 | sql.add_history_conn(user.id, str(chat.id), chat_name) 167 | bot.send_message(update.effective_message.from_user.id, "You have connected with *{}*. Use /connection for see current available commands.".format(chat_name), parse_mode="markdown") 168 | except BadRequest: 169 | pass 170 | except Unauthorized: 171 | pass 172 | else: 173 | send_message(update.effective_message, "Connection failed!") 174 | else: 175 | send_message(update.effective_message, "Connection to this chat is not allowed!") 176 | 177 | 178 | def disconnect_chat(bot: Bot, update: Update): 179 | 180 | spam = spamfilters(update.effective_message.text, update.effective_message.from_user.id, update.effective_chat.id) 181 | if spam == True: 182 | return 183 | 184 | if update.effective_chat.type == 'private': 185 | disconnection_status = sql.disconnect(update.effective_message.from_user.id) 186 | if disconnection_status: 187 | sql.disconnected_chat = send_message(update.effective_message, "Disconnected Successfully this from chat!") 188 | else: 189 | send_message(update.effective_message, "You're not connected!") 190 | else: 191 | send_message(update.effective_message, "This command is only available in PM.") 192 | 193 | 194 | def connected(bot, update, chat, user_id, need_admin=True): 195 | 196 | user = update.effective_user 197 | spam = spamfilters(update.effective_message.text, update.effective_message.from_user.id, update.effective_chat.id) 198 | 199 | if spam == True: 200 | return 201 | 202 | if chat.type == chat.PRIVATE and sql.get_connected_chat(user_id): 203 | 204 | conn_id = sql.get_connected_chat(user_id).chat_id 205 | getstatusadmin = bot.get_chat_member(conn_id, update.effective_message.from_user.id) 206 | isadmin = getstatusadmin.status in ('administrator', 'creator') 207 | ismember = getstatusadmin.status in ('member') 208 | isallow = sql.allow_connect_to_chat(conn_id) 209 | 210 | if (isadmin) or (isallow and ismember) or (user.id in SUDO_USERS) or (user.id in DEV_USERS): 211 | if need_admin == True: 212 | if getstatusadmin.status in ('administrator', 'creator') or user_id in SUDO_USERS or user.id in DEV_USERS: 213 | return conn_id 214 | else: 215 | send_message(update.effective_message, "You must be an admin in the connected group!") 216 | raise Exception("Not admin!") 217 | else: 218 | return conn_id 219 | else: 220 | send_message(update.effective_message, "The group changed the connection rights or you are no longer an admin.\nI've disconnected you.") 221 | disconnect_chat(bot, update) 222 | raise Exception("Not admin!") 223 | else: 224 | return False 225 | 226 | 227 | @run_async 228 | def help_connect_chat(bot: Bot, update: Update): 229 | 230 | spam = spamfilters(update.effective_message.text, update.effective_message.from_user.id, update.effective_chat.id) 231 | if spam == True: 232 | return 233 | 234 | if update.effective_message.chat.type != "private": 235 | send_message(update.effective_message, "PM me with that command to get help.") 236 | return 237 | else: 238 | send_message(update.effective_message, "All commands", parse_mode="markdown") 239 | 240 | 241 | @run_async 242 | def connect_button(bot: Bot, update: Update): 243 | 244 | query = update.callback_query 245 | chat = update.effective_chat 246 | user = update.effective_user 247 | 248 | connect_match = re.match(r"connect\((.+?)\)", query.data) 249 | disconnect_match = query.data == "connect_disconnect" 250 | clear_match = query.data == "connect_clear" 251 | connect_close = query.data == "connect_close" 252 | 253 | if connect_match: 254 | target_chat = connect_match.group(1) 255 | getstatusadmin = bot.get_chat_member(target_chat, query.from_user.id) 256 | isadmin = getstatusadmin.status in ('administrator', 'creator') 257 | ismember = getstatusadmin.status in ('member') 258 | isallow = sql.allow_connect_to_chat(target_chat) 259 | 260 | if (isadmin) or (isallow and ismember) or (user.id in SUDO_USERS) or (user.id in DEV_USERS): 261 | connection_status = sql.connect(query.from_user.id, target_chat) 262 | 263 | if connection_status: 264 | conn_chat = dispatcher.bot.getChat(connected(bot, update, chat, user.id, need_admin=False)) 265 | chat_name = conn_chat.title 266 | query.message.edit_text("Successfully connected to *{}*. Use /connection for see current available commands.".format(chat_name), parse_mode=ParseMode.MARKDOWN) 267 | sql.add_history_conn(user.id, str(conn_chat.id), chat_name) 268 | else: 269 | query.message.edit_text("Connection failed!") 270 | else: 271 | bot.answer_callback_query(query.id, "Connection to this chat is not allowed!", show_alert=True) 272 | elif disconnect_match: 273 | disconnection_status = sql.disconnect(query.from_user.id) 274 | if disconnection_status: 275 | sql.disconnected_chat = query.message.edit_text("Disconnected from chat!") 276 | else: 277 | bot.answer_callback_query(query.id, "You're not connected!", show_alert=True) 278 | elif clear_match: 279 | sql.clear_history_conn(query.from_user.id) 280 | query.message.edit_text("History connected has been cleared!") 281 | elif connect_close: 282 | query.message.edit_text("Closed.\nTo open again, type /connect") 283 | else: 284 | connect_chat(bot, update, []) 285 | 286 | __help__ = """ 287 | • /connect: connect a chat (Can be done in a group by /connect or /connect in PM) 288 | • /connection: list connected chats 289 | • /disconnect: disconnect from a chat 290 | • /helpconnect: list available commands that can be done remotely 291 | 292 | *Admin only:* 293 | • /allowconnect : allow a user to connect to a chat 294 | """ 295 | 296 | CONNECT_CHAT_HANDLER = CommandHandler("connect", connect_chat, pass_args=True) 297 | CONNECTION_CHAT_HANDLER = CommandHandler("connection", connection_chat) 298 | DISCONNECT_CHAT_HANDLER = CommandHandler("disconnect", disconnect_chat) 299 | ALLOW_CONNECTIONS_HANDLER = CommandHandler("allowconnect", allow_connections, pass_args=True) 300 | HELP_CONNECT_CHAT_HANDLER = CommandHandler("helpconnect", help_connect_chat) 301 | CONNECT_BTN_HANDLER = CallbackQueryHandler(connect_button, pattern=r"connect") 302 | 303 | dispatcher.add_handler(CONNECT_CHAT_HANDLER) 304 | dispatcher.add_handler(CONNECTION_CHAT_HANDLER) 305 | dispatcher.add_handler(DISCONNECT_CHAT_HANDLER) 306 | dispatcher.add_handler(ALLOW_CONNECTIONS_HANDLER) 307 | dispatcher.add_handler(HELP_CONNECT_CHAT_HANDLER) 308 | dispatcher.add_handler(CONNECT_BTN_HANDLER) 309 | 310 | __mod_name__ = "Connection🧩" 311 | __handlers__ = [CONNECT_CHAT_HANDLER, CONNECTION_CHAT_HANDLER, DISCONNECT_CHAT_HANDLER, ALLOW_CONNECTIONS_HANDLER, HELP_CONNECT_CHAT_HANDLER, CONNECT_BTN_HANDLER] 312 | -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import importlib 3 | import re 4 | import datetime 5 | from typing import Optional, List 6 | import resource 7 | import platform 8 | import sys 9 | import traceback 10 | import requests 11 | from parsel import Selector 12 | import json 13 | from urllib.request import urlopen 14 | 15 | from telegram import Message, Chat, Update, Bot, User 16 | from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup, KeyboardButton 17 | from telegram.error import Unauthorized, BadRequest, TimedOut, NetworkError, ChatMigrated, TelegramError 18 | from telegram.ext import CommandHandler, Filters, MessageHandler, CallbackQueryHandler 19 | from telegram.ext.dispatcher import run_async, DispatcherHandlerStop, Dispatcher 20 | from telegram.utils.helpers import escape_markdown 21 | from bot import dispatcher, updater, TOKEN, WEBHOOK, SUDO_USERS, OWNER_ID, CERT_PATH, PORT, URL, LOGGER, OWNER_NAME, ALLOW_EXCL 22 | from bot.modules import ALL_MODULES 23 | from bot.modules.helper_funcs.chat_status import is_user_admin 24 | from bot.modules.helper_funcs.misc import paginate_modules 25 | from bot.modules.connection import connected 26 | from bot.modules.connection import connect_button 27 | 28 | 29 | PM_START_TEXT = """ 30 | *Hello* *{}* 31 | *My name is* *{}*\n\n`You Can Add any kind of Filters to This Bot!` 32 | 33 | _Click Help button for more details_ 34 | """ 35 | 36 | 37 | HELP_STRINGS = """ 38 | *Hello My name is* *{}*. 39 | *Main Available Commands are Below:* 40 | 41 | All of the following commands / can be used... 42 | 43 | And the following: 44 | """.format(dispatcher.bot.first_name, "" if not ALLOW_EXCL else "\nAll commands can either be used with / or !.\n") 45 | 46 | 47 | 48 | VERSION = "6.0" 49 | 50 | def vercheck() -> str: 51 | return str(VERSION) 52 | 53 | 54 | SOURCE_STRING = """ 55 | ☹️*Sorry Broh* 56 | """ 57 | 58 | 59 | IMPORTED = {} 60 | MIGRATEABLE = [] 61 | HELPABLE = {} 62 | STATS = [] 63 | USER_INFO = [] 64 | DATA_IMPORT = [] 65 | DATA_EXPORT = [] 66 | 67 | CHAT_SETTINGS = {} 68 | USER_SETTINGS = {} 69 | 70 | GDPR = [] 71 | 72 | START_IMG = os.environ.get('START_IMG', None) 73 | if START_IMG is None: 74 | img = "https://telegra.ph/file/fc734b227985a1524e715.jpg" 75 | else: 76 | img = START_IMG 77 | 78 | for module_name in ALL_MODULES: 79 | imported_module = importlib.import_module("bot.modules." + module_name) 80 | if not hasattr(imported_module, "__mod_name__"): 81 | imported_module.__mod_name__ = imported_module.__name__ 82 | 83 | if not imported_module.__mod_name__.lower() in IMPORTED: 84 | IMPORTED[imported_module.__mod_name__.lower()] = imported_module 85 | else: 86 | raise Exception("Can't have two modules with the same name! Please change one") 87 | 88 | if hasattr(imported_module, "__help__") and imported_module.__help__: 89 | HELPABLE[imported_module.__mod_name__.lower()] = imported_module 90 | 91 | # Chats to migrate on chat_migrated events 92 | if hasattr(imported_module, "__migrate__"): 93 | MIGRATEABLE.append(imported_module) 94 | 95 | if hasattr(imported_module, "__stats__"): 96 | STATS.append(imported_module) 97 | 98 | if hasattr(imported_module, "__gdpr__"): 99 | GDPR.append(imported_module) 100 | 101 | if hasattr(imported_module, "__user_info__"): 102 | USER_INFO.append(imported_module) 103 | 104 | if hasattr(imported_module, "__import_data__"): 105 | DATA_IMPORT.append(imported_module) 106 | 107 | if hasattr(imported_module, "__export_data__"): 108 | DATA_EXPORT.append(imported_module) 109 | 110 | if hasattr(imported_module, "__chat_settings__"): 111 | CHAT_SETTINGS[imported_module.__mod_name__.lower()] = imported_module 112 | 113 | if hasattr(imported_module, "__user_settings__"): 114 | USER_SETTINGS[imported_module.__mod_name__.lower()] = imported_module 115 | 116 | 117 | # do not async 118 | def send_help(chat_id, text, keyboard=None): 119 | if not keyboard: 120 | keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help")) 121 | dispatcher.bot.send_message(chat_id=chat_id, 122 | text=text, 123 | parse_mode=ParseMode.MARKDOWN, 124 | reply_markup=keyboard) 125 | 126 | 127 | @run_async 128 | def test(bot: Bot, update: Update): 129 | # pprint(eval(str(update))) 130 | # update.effective_message.reply_text("Hola tester! _I_ *have* `markdown`", parse_mode=ParseMode.MARKDOWN) 131 | update.effective_message.reply_text("This person edited a message") 132 | print(update.effective_message) 133 | 134 | 135 | @run_async 136 | def start(bot: Bot, update: Update, args: List[str]): 137 | print("Start") 138 | chat = update.effective_chat # type: Optional[Chat] 139 | query = update.callback_query 140 | if update.effective_chat.type == "private": 141 | if len(args) >= 1: 142 | if args[0].lower() == "help": 143 | send_help(update.effective_chat.id, HELP_STRINGS) 144 | 145 | elif args[0].lower().startswith("stngs_"): 146 | match = re.match("stngs_(.*)", args[0].lower()) 147 | chat = dispatcher.bot.getChat(match.group(1)) 148 | 149 | if is_user_admin(chat, update.effective_user.id): 150 | send_settings(match.group(1), update.effective_user.id, user=False) 151 | else: 152 | send_settings(match.group(1), update.effective_user.id, user=True) 153 | 154 | elif args[0][1:].isdigit() and "rules" in IMPORTED: 155 | IMPORTED["rules"].send_rules(update, args[0], from_pm=True) 156 | 157 | else: 158 | send_start(bot, update) 159 | else: 160 | update.effective_message.reply_text("Heya,{} Here..\nHow can I help you? 🙂".format(bot.first_name),reply_markup=InlineKeyboardMarkup( 161 | [[InlineKeyboardButton(text="⚙️Help",url="t.me/{}?start=help".format(bot.username))]])) 162 | 163 | def send_start(bot, update): 164 | #Try to remove old message 165 | try: 166 | query = update.callback_query 167 | query.message.delete() 168 | except: 169 | pass 170 | 171 | chat = update.effective_chat # type: Optional[Chat] 172 | first_name = update.effective_user.first_name 173 | text = PM_START_TEXT 174 | 175 | keyboard = [[InlineKeyboardButton(text="⚙️Help",callback_data="help_back"),InlineKeyboardButton(text="Master😴",url="https://t.me/D_ar_k_Angel")]] 176 | keyboard += [[InlineKeyboardButton(text="♻️Connect Group", callback_data="main_connect"),InlineKeyboardButton(text="Add Me➕",url="t.me/{}?startgroup=true".format(bot.username))]] 177 | 178 | update.effective_message.reply_photo(img, PM_START_TEXT.format(escape_markdown(first_name), escape_markdown(bot.first_name), OWNER_NAME, OWNER_ID), 179 | reply_markup=InlineKeyboardMarkup(keyboard), disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN) 180 | 181 | 182 | def m_connect_button(bot, update): 183 | bot.delete_message(update.effective_chat.id, update.effective_message.message_id) 184 | connect_button(bot, update) 185 | 186 | 187 | # for test purposes 188 | def error_callback(bot, update, error): 189 | try: 190 | raise error 191 | except Unauthorized: 192 | print("no nono1") 193 | print(error) 194 | # remove update.message.chat_id from conversation list 195 | except BadRequest: 196 | print("no nono2") 197 | print("BadRequest caught") 198 | print(error) 199 | 200 | # handle malformed requests - read more below! 201 | except TimedOut: 202 | print("no nono3") 203 | # handle slow connection problems 204 | except NetworkError: 205 | print("no nono4") 206 | # handle other connection problems 207 | except ChatMigrated as err: 208 | print("no nono5") 209 | print(err) 210 | # the chat_id of a group has changed, use e.new_chat_id instead 211 | except TelegramError: 212 | print(error) 213 | # handle all other telegram related errors 214 | 215 | 216 | @run_async 217 | def help_button(bot: Bot, update: Update): 218 | query = update.callback_query 219 | mod_match = re.match(r"help_module\((.+?)\)", query.data) 220 | prev_match = re.match(r"help_prev\((.+?)\)", query.data) 221 | next_match = re.match(r"help_next\((.+?)\)", query.data) 222 | back_match = re.match(r"help_back", query.data) 223 | try: 224 | if mod_match: 225 | module = mod_match.group(1) 226 | text = "Here is the help for the *{}* module:\n".format(HELPABLE[module].__mod_name__) \ 227 | + HELPABLE[module].__help__ 228 | query.message.reply_text(text=text, 229 | parse_mode=ParseMode.MARKDOWN, 230 | reply_markup=InlineKeyboardMarkup( 231 | [[InlineKeyboardButton(text="Back", callback_data="help_back")]])) 232 | 233 | elif prev_match: 234 | curr_page = int(prev_match.group(1)) 235 | query.message.reply_text(HELP_STRINGS, 236 | parse_mode=ParseMode.MARKDOWN, 237 | reply_markup=InlineKeyboardMarkup( 238 | paginate_modules(curr_page - 1, HELPABLE, "help"))) 239 | 240 | elif next_match: 241 | next_page = int(next_match.group(1)) 242 | query.message.reply_text(HELP_STRINGS, 243 | parse_mode=ParseMode.MARKDOWN, 244 | reply_markup=InlineKeyboardMarkup( 245 | paginate_modules(next_page + 1, HELPABLE, "help"))) 246 | 247 | elif back_match: 248 | query.message.reply_text(text=HELP_STRINGS, 249 | parse_mode=ParseMode.MARKDOWN, 250 | reply_markup=InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help"))) 251 | 252 | # ensure no spinny white circle 253 | bot.answer_callback_query(query.id) 254 | query.message.delete() 255 | except BadRequest as excp: 256 | if excp.message == "Message is not modified": 257 | pass 258 | elif excp.message == "Query_id_invalid": 259 | pass 260 | elif excp.message == "Message can't be deleted": 261 | pass 262 | else: 263 | LOGGER.exception("Exception in help buttons. %s", str(query.data)) 264 | 265 | 266 | @run_async 267 | def get_help(bot: Bot, update: Update): 268 | chat = update.effective_chat # type: Optional[Chat] 269 | args = update.effective_message.text.split(None, 1) 270 | 271 | # ONLY send help in PM 272 | if chat.type != chat.PRIVATE: 273 | 274 | update.effective_message.reply_text("Contact me in PM to get the list of possible commands.", 275 | reply_markup=InlineKeyboardMarkup( 276 | [[InlineKeyboardButton(text="⚙️Help",url="t.me/{}?start=help".format(bot.username))], 277 | [InlineKeyboardButton(text="😴Contact Creator",url="https://t.me/D_ar_k_Angel")]])) 278 | return 279 | 280 | elif len(args) >= 2 and any(args[1].lower() == x for x in HELPABLE): 281 | module = args[1].lower() 282 | text = "Here is the available help for the *{}* module:\n".format(HELPABLE[module].__mod_name__) \ 283 | + HELPABLE[module].__help__ 284 | send_help(chat.id, text, InlineKeyboardMarkup([[InlineKeyboardButton(text="Back", callback_data="help_back")]])) 285 | 286 | else: 287 | send_help(chat.id, HELP_STRINGS) 288 | 289 | 290 | def send_settings(chat_id, user_id, user=False): 291 | if user: 292 | if USER_SETTINGS: 293 | settings = "\n\n".join( 294 | "*{}*:\n{}".format(mod.__mod_name__, mod.__user_settings__(user_id)) for mod in USER_SETTINGS.values()) 295 | dispatcher.bot.send_message(user_id, "These are your current settings:" + "\n\n" + settings, 296 | parse_mode=ParseMode.MARKDOWN) 297 | 298 | else: 299 | dispatcher.bot.send_message(user_id, "Seems like there aren't any user specific settings available :'(", 300 | parse_mode=ParseMode.MARKDOWN) 301 | 302 | else: 303 | if CHAT_SETTINGS: 304 | chat_name = dispatcher.bot.getChat(chat_id).title 305 | dispatcher.bot.send_message(user_id, 306 | text="Which module would you like to check {}'s settings for?".format( 307 | chat_name), 308 | reply_markup=InlineKeyboardMarkup( 309 | paginate_modules(0, CHAT_SETTINGS, "stngs", chat=chat_id))) 310 | else: 311 | dispatcher.bot.send_message(user_id, "Seems like there aren't any chat settings available :'(\nSend this " 312 | "in a group chat you're admin in to find its current settings!", 313 | parse_mode=ParseMode.MARKDOWN) 314 | 315 | 316 | 317 | 318 | 319 | @run_async 320 | def settings_button(bot: Bot, update: Update): 321 | query = update.callback_query 322 | user = update.effective_user 323 | mod_match = re.match(r"stngs_module\((.+?),(.+?)\)", query.data) 324 | prev_match = re.match(r"stngs_prev\((.+?),(.+?)\)", query.data) 325 | next_match = re.match(r"stngs_next\((.+?),(.+?)\)", query.data) 326 | back_match = re.match(r"stngs_back\((.+?)\)", query.data) 327 | try: 328 | if mod_match: 329 | chat_id = mod_match.group(1) 330 | module = mod_match.group(2) 331 | chat = bot.get_chat(chat_id) 332 | text = "*{}* has the following settings for the *{}* module:\n\n".format(escape_markdown(chat.title), 333 | CHAT_SETTINGS[ 334 | module].__mod_name__) + \ 335 | CHAT_SETTINGS[module].__chat_settings__(chat_id, user.id) 336 | query.message.reply_text(text=text, 337 | parse_mode=ParseMode.MARKDOWN, 338 | reply_markup=InlineKeyboardMarkup( 339 | [[InlineKeyboardButton(text="Back", 340 | callback_data="stngs_back({})".format(chat_id))]])) 341 | 342 | elif prev_match: 343 | chat_id = prev_match.group(1) 344 | curr_page = int(prev_match.group(2)) 345 | chat = bot.get_chat(chat_id) 346 | query.message.reply_text("Hi there! There are quite a few settings for {} - go ahead and pick what " 347 | "you're interested in.".format(chat.title), 348 | reply_markup=InlineKeyboardMarkup( 349 | paginate_modules(curr_page - 1, CHAT_SETTINGS, "stngs", 350 | chat=chat_id))) 351 | 352 | elif next_match: 353 | chat_id = next_match.group(1) 354 | next_page = int(next_match.group(2)) 355 | chat = bot.get_chat(chat_id) 356 | query.message.reply_text("Hi there! There are quite a few settings for {} - go ahead and pick what " 357 | "you're interested in.".format(chat.title), 358 | reply_markup=InlineKeyboardMarkup( 359 | paginate_modules(next_page + 1, CHAT_SETTINGS, "stngs", 360 | chat=chat_id))) 361 | 362 | elif back_match: 363 | chat_id = back_match.group(1) 364 | chat = bot.get_chat(chat_id) 365 | query.message.reply_text(text="Hi there! There are quite a few settings for {} - go ahead and pick what " 366 | "you're interested in.".format(escape_markdown(chat.title)), 367 | parse_mode=ParseMode.MARKDOWN, 368 | reply_markup=InlineKeyboardMarkup(paginate_modules(0, CHAT_SETTINGS, "stngs", 369 | chat=chat_id))) 370 | 371 | # ensure no spinny white circle 372 | bot.answer_callback_query(query.id) 373 | query.message.delete() 374 | except BadRequest as excp: 375 | if excp.message == "Message is not modified": 376 | pass 377 | elif excp.message == "Query_id_invalid": 378 | pass 379 | elif excp.message == "Message can't be deleted": 380 | pass 381 | else: 382 | LOGGER.exception("Exception in settings buttons. %s", str(query.data)) 383 | 384 | 385 | @run_async 386 | def get_settings(bot: Bot, update: Update): 387 | chat = update.effective_chat # type: Optional[Chat] 388 | user = update.effective_user # type: Optional[User] 389 | msg = update.effective_message # type: Optional[Message] 390 | args = msg.text.split(None, 1) 391 | 392 | # ONLY send settings in PM 393 | if chat.type != chat.PRIVATE: 394 | if is_user_admin(chat, user.id): 395 | text = "Click here to get this chat's settings, as well as yours." 396 | msg.reply_text(text, 397 | reply_markup=InlineKeyboardMarkup( 398 | [[InlineKeyboardButton(text="⚙️Settings", 399 | url="t.me/{}?start=stngs_{}".format( 400 | bot.username, chat.id))]])) 401 | else: 402 | text = "Click here to check your settings." 403 | 404 | else: 405 | send_settings(chat.id, user.id, True) 406 | 407 | 408 | 409 | 410 | def migrate_chats(bot: Bot, update: Update): 411 | msg = update.effective_message # type: Optional[Message] 412 | if msg.migrate_to_chat_id: 413 | old_chat = update.effective_chat.id 414 | new_chat = msg.migrate_to_chat_id 415 | elif msg.migrate_from_chat_id: 416 | old_chat = msg.migrate_from_chat_id 417 | new_chat = update.effective_chat.id 418 | else: 419 | return 420 | 421 | LOGGER.info("Migrating from %s, to %s", str(old_chat), str(new_chat)) 422 | for mod in MIGRATEABLE: 423 | mod.__migrate__(old_chat, new_chat) 424 | 425 | LOGGER.info("Successfully migrated!") 426 | raise DispatcherHandlerStop 427 | 428 | 429 | @run_async 430 | def source(bot: Bot, update: Update): 431 | user = update.effective_message.from_user 432 | chat = update.effective_chat # type: Optional[Chat] 433 | 434 | if chat.type == "private": 435 | update.effective_message.reply_text(SOURCE_STRING, parse_mode=ParseMode.MARKDOWN) 436 | 437 | else: 438 | try: 439 | bot.send_message(user.id, SOURCE_STRING, parse_mode=ParseMode.MARKDOWN) 440 | 441 | update.effective_message.reply_text("You'll find in PM more info about my sourcecode.") 442 | except Unauthorized: 443 | update.effective_message.reply_text("Contact me in PM first to get source information.") 444 | 445 | @run_async 446 | def imdb_searchdata(bot: Bot, update: Update): 447 | query_raw = update.callback_query 448 | query = query_raw.data.split('$') 449 | print(query) 450 | if query[1] != query_raw.from_user.username: 451 | return 452 | title = '' 453 | rating = '' 454 | date = '' 455 | synopsis = '' 456 | url_sel = 'https://www.imdb.com/title/%s/' % (query[0]) 457 | text_sel = requests.get(url_sel).text 458 | selector_global = Selector(text = text_sel) 459 | title = selector_global.xpath('//div[@class="title_wrapper"]/h1/text()').get().strip() 460 | try: 461 | rating = selector_global.xpath('//div[@class="ratingValue"]/strong/span/text()').get().strip() 462 | except: 463 | rating = '-' 464 | try: 465 | date = '(' + selector_global.xpath('//div[@class="title_wrapper"]/h1/span/a/text()').getall()[-1].strip() + ')' 466 | except: 467 | date = selector_global.xpath('//div[@class="subtext"]/a/text()').getall()[-1].strip() 468 | try: 469 | synopsis_list = selector_global.xpath('//div[@class="summary_text"]/text()').getall() 470 | synopsis = re.sub(' +',' ', re.sub(r'\([^)]*\)', '', ''.join(sentence.strip() for sentence in synopsis_list))) 471 | except: 472 | synopsis = '_No synopsis available._' 473 | movie_data = '*%s*, _%s_\n★ *%s*\n\n%s' % (title, date, rating, synopsis) 474 | query_raw.edit_message_text( 475 | movie_data, 476 | parse_mode=ParseMode.MARKDOWN 477 | ) 478 | 479 | @run_async 480 | def imdb(bot: Bot, update: Update, args): 481 | message = update.effective_message 482 | query = ''.join([arg + '_' for arg in args]).lower() 483 | if not query: 484 | bot.send_message( 485 | message.chat.id, 486 | 'You need to specify a movie/show name!' 487 | ) 488 | return 489 | url_suggs = 'https://v2.sg.media-imdb.com/suggests/%s/%s.json' % (query[0], query) 490 | json_url = urlopen(url_suggs) 491 | suggs_raw = '' 492 | for line in json_url: 493 | suggs_raw = line 494 | skip_chars = 6 + len(query) 495 | suggs_dict = json.loads(suggs_raw[skip_chars:][:-1]) 496 | if suggs_dict: 497 | button_list = [[ 498 | InlineKeyboardButton( 499 | text = str(sugg['l'] + ' (' + str(sugg['y']) + ')'), 500 | callback_data = str(sugg['id']) + '$' + str(message.from_user.username) 501 | )] for sugg in suggs_dict['d'] if 'y' in sugg 502 | ] 503 | reply_markup = InlineKeyboardMarkup(button_list) 504 | bot.send_message( 505 | message.chat.id, 506 | 'Which one? ', 507 | reply_markup = reply_markup 508 | ) 509 | else: 510 | pass 511 | 512 | 513 | # Avoid memory dead 514 | def memory_limit(percentage: float): 515 | if platform.system() != "Linux": 516 | print('Only works on linux!') 517 | return 518 | soft, hard = resource.getrlimit(resource.RLIMIT_AS) 519 | resource.setrlimit(resource.RLIMIT_AS, (int(get_memory() * 1024 * percentage), hard)) 520 | 521 | def get_memory(): 522 | with open('/proc/meminfo', 'r') as mem: 523 | free_memory = 0 524 | for i in mem: 525 | sline = i.split() 526 | if str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'): 527 | free_memory += int(sline[1]) 528 | return free_memory 529 | 530 | def memory(percentage=0.5): 531 | def decorator(function): 532 | def wrapper(*args, **kwargs): 533 | memory_limit(percentage) 534 | try: 535 | function(*args, **kwargs) 536 | except MemoryError: 537 | mem = get_memory() / 1024 /1024 538 | print('Remain: %.2f GB' % mem) 539 | sys.stderr.write('\n\nERROR: Memory Exception\n') 540 | sys.exit(1) 541 | return wrapper 542 | return decorator 543 | 544 | 545 | @memory(percentage=0.8) 546 | def main(): 547 | test_handler = CommandHandler("test", test) 548 | start_handler = CommandHandler("start", start, pass_args=True) 549 | 550 | IMDB_HANDLER = CommandHandler('imdb', imdb, pass_args=True) 551 | IMDB_SEARCHDATAHANDLER = CallbackQueryHandler(imdb_searchdata) 552 | 553 | start_callback_handler = CallbackQueryHandler(send_start, pattern=r"bot_start") 554 | dispatcher.add_handler(start_callback_handler) 555 | 556 | 557 | help_handler = CommandHandler("help", get_help) 558 | help_callback_handler = CallbackQueryHandler(help_button, pattern=r"help_") 559 | 560 | settings_handler = CommandHandler("settings", get_settings) 561 | settings_callback_handler = CallbackQueryHandler(settings_button, pattern=r"stngs_") 562 | 563 | source_handler = CommandHandler("source", source) 564 | 565 | migrate_handler = MessageHandler(Filters.status_update.migrate, migrate_chats) 566 | 567 | M_CONNECT_BTN_HANDLER = CallbackQueryHandler(m_connect_button, pattern=r"main_connect") 568 | 569 | # dispatcher.add_handler(test_handler) 570 | dispatcher.add_handler(start_handler) 571 | dispatcher.add_handler(help_handler) 572 | dispatcher.add_handler(settings_handler) 573 | dispatcher.add_handler(help_callback_handler) 574 | dispatcher.add_handler(settings_callback_handler) 575 | dispatcher.add_handler(migrate_handler) 576 | dispatcher.add_handler(source_handler) 577 | dispatcher.add_handler(M_CONNECT_BTN_HANDLER) 578 | dispatcher.add_handler(IMDB_HANDLER) 579 | dispatcher.add_handler(IMDB_SEARCHDATAHANDLER) 580 | 581 | 582 | # dispatcher.add_error_handler(error_callback) 583 | 584 | if WEBHOOK: 585 | LOGGER.info("Using webhooks.") 586 | updater.start_webhook(listen="127.0.0.1", 587 | port=PORT, 588 | url_path=TOKEN) 589 | 590 | if CERT_PATH: 591 | updater.bot.set_webhook(url=URL + TOKEN, 592 | certificate=open(CERT_PATH, 'rb')) 593 | else: 594 | updater.bot.set_webhook(url=URL + TOKEN) 595 | 596 | else: 597 | LOGGER.info("bot running...") 598 | updater.start_polling(timeout=15, read_latency=4) 599 | 600 | updater.idle() 601 | 602 | 603 | if __name__ == '__main__': 604 | LOGGER.info("Successfully loaded modules: " + str(ALL_MODULES)) 605 | main() 606 | --------------------------------------------------------------------------------