├── 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 | [](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 |
--------------------------------------------------------------------------------