├── runtime.txt ├── _config.yml ├── Procfile ├── heroku.yml ├── GabiBraunRobot ├── utils │ ├── exceptions.py │ ├── __init__.py │ ├── progress.py │ └── tools.py ├── elevated_users.json ├── modules │ ├── helper_funcs │ │ ├── telethn │ │ │ ├── __init__.py │ │ │ └── chatstatus.py │ │ ├── regex_helper.py │ │ ├── readable_time.py │ │ ├── alternate.py │ │ ├── filters.py │ │ ├── misc.py │ │ ├── handlers.py │ │ └── extraction.py │ ├── sql │ │ ├── __init__.py │ │ ├── afk_redis.py │ │ ├── rules_sql.py │ │ ├── approve_sql.py │ │ ├── blacklistusers_sql.py │ │ ├── chatbot_sql.py │ │ ├── userinfo_sql.py │ │ ├── rss_sql.py │ │ ├── log_channel_sql.py │ │ ├── reporting_sql.py │ │ ├── disable_sql.py │ │ ├── antiflood_sql.py │ │ ├── global_bans_sql.py │ │ ├── notes_sql.py │ │ ├── blsticker_sql.py │ │ ├── blacklist_sql.py │ │ └── users_sql.py │ ├── truth_and_dare.py │ ├── ud.py │ ├── paste.py │ ├── animequotes.py │ ├── __init__.py │ ├── shell.py │ ├── get_common_chats.py │ ├── currency_converter.py │ ├── wiki.py │ ├── tts.py │ ├── dev.py │ ├── wallpaper.py │ ├── speed_test.py │ ├── debug.py │ ├── animequotes_strings.py │ ├── purge.py │ ├── truth_and_dare_string.py │ ├── reactions.py │ ├── ping.py │ ├── Zombies.py │ ├── gettime.py │ ├── eval.py │ ├── gtranslator.py │ ├── misc.py │ ├── rules.py │ ├── dbcleanup.py │ ├── sed.py │ ├── blacklistusers.py │ ├── chatbot.py │ ├── afk.py │ ├── math.py │ └── users.py ├── mwt.py ├── igris.py ├── events.py └── sample_config.py ├── exp.sh ├── start.bat ├── restart.bat ├── Git_Pull.bat ├── .github ├── ISSUE_TEMPLATE │ ├── query.md │ ├── bug_report.md │ └── feature_request.md ├── CODEOWNERS ├── disabled │ ├── main.yml │ └── docker_disabled.yml └── workflows │ └── yapf.yml ├── SetupVirtualEnv_run_once.bat ├── Git_Push.bat ├── animequote.py ├── requirements.txt ├── README.md ├── start_service.bat ├── CONTRIBUTING.md └── Dockerfile /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.6 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 -m GabiBraunRobot 2 | web: python3 -m GabiBraunRobot 3 | ps:scale worker=1 4 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | run: 5 | worker: python3 -m GabiBraunRobot 6 | -------------------------------------------------------------------------------- /GabiBraunRobot/utils/exceptions.py: -------------------------------------------------------------------------------- 1 | class CancelProcess(Exception): 2 | """ 3 | Cancel Process 4 | """ 5 | -------------------------------------------------------------------------------- /GabiBraunRobot/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .progress import progress 2 | from .tools import human_to_bytes, humanbytes, md5, time_formatter 3 | -------------------------------------------------------------------------------- /exp.sh: -------------------------------------------------------------------------------- 1 | sudo bash -c 'echo "{ \"cgroup-parent\": \"/actions_job\",\"experimental\":true}" > /etc/docker/daemon.json' 2 | sudo systemctl restart docker.service -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | TITLE Gabi Braun Robot 3 | :: Enables virtual env mode and then starts saitama 4 | env\scripts\activate.bat && py -m SaitamaRobot 5 | -------------------------------------------------------------------------------- /restart.bat: -------------------------------------------------------------------------------- 1 | :: starts a cmd to silently start the bat file, just another dirty way of getting things done for my env on windows 2 | start cmd.exe /c start_service.bat -------------------------------------------------------------------------------- /Git_Pull.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | TITLE Github Quick-Pull 3 | 4 | :: Print the branch cause ..oooooo fancy! 5 | echo Pulling from branch: 6 | git branch 7 | echo. 8 | git pull 9 | -------------------------------------------------------------------------------- /GabiBraunRobot/elevated_users.json: -------------------------------------------------------------------------------- 1 | { 2 | "devs": [], 3 | "supports": [], 4 | "whitelists": [], 5 | "sudos": [], 6 | "tigers": [], 7 | "spammers": [] 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/query.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Query 3 | about: Ask us a question 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **I wanted to ask that...** 11 | Just type your question here [...] 12 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/telethn/__init__.py: -------------------------------------------------------------------------------- 1 | from GabiBraunRobot import (DEV_USERS, DRAGONS, DEMONS, TIGERS, WOLVES, telethn) 2 | 3 | IMMUNE_USERS = DRAGONS + WOLVES + DEMONS + TIGERS + DEV_USERS 4 | 5 | IMMUNE_USERS = list(DRAGONS) + list(WOLVES) + list(DEMONS) + list( 6 | TIGERS) + list(DEV_USERS) 7 | -------------------------------------------------------------------------------- /SetupVirtualEnv_run_once.bat: -------------------------------------------------------------------------------- 1 | TITLE Setting up virtual env 2 | :: Running it once is fine, this just sets up virtual env >> install all modules there 3 | py -m venv env && env\scripts\activate.bat && pip install -r requirements.txt 4 | 5 | :: Note to rerun the requirements.txt in case you ever add a mdoule. 6 | :: Running this multiple time will not make a mess of your setup, dont worry about that bit. 7 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ for valid syntax for entries. 2 | # Default owners for everything in the repo, unless a later match takes precedence 3 | 4 | # Phoenix owns the files under blacklist thingies 5 | /GabiBraunRobot/modules/blacklistusers.py @rsktg 6 | /GabiBraunRobot/modules/sql/blacklistusers_sql.py @rsktg 7 | #pep8-ing the code doesn't change maker 8 | /GabiBraunRobot/modules/paste.py @DragSama 9 | /tg_bit/modules/speed_test.py @DragSama 10 | -------------------------------------------------------------------------------- /.github/disabled/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - shiken 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: akhileshns/heroku-deploy@v3.5.6 # This is the action 14 | with: 15 | heroku_api_key: ${{secrets.HEROKU_API_KEY}} 16 | heroku_app_name: ${{secrets.HEROKU_APP_NAME}} #Must be unique in Heroku 17 | heroku_email: ${{secrets.HEROKU_EMAIL}} 18 | -------------------------------------------------------------------------------- /Git_Push.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | TITLE Github Quick-pushing 3 | 4 | :: Print the branch cause people like me push to wrong branches and cry about it later. 5 | echo Pushing to branch: 6 | git branch 7 | echo. 8 | :: Take input for comment and thats about it 9 | set /p commit_title="Enter Commit title (pushes with you as author): " 10 | 11 | :: If you are reading comments to understand this part then you can go back stab yourself. 12 | echo. 13 | git add * 14 | git commit -m "%commit_title%" 15 | git pull 16 | git push 17 | 18 | 19 | :: Hail Hydra -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/__init__.py: -------------------------------------------------------------------------------- 1 | from GabiBraunRobot import DB_URI 2 | from sqlalchemy import create_engine 3 | from sqlalchemy.ext.declarative import declarative_base 4 | from sqlalchemy.orm import scoped_session, sessionmaker 5 | 6 | 7 | def start() -> scoped_session: 8 | engine = create_engine(DB_URI, client_encoding="utf8") 9 | BASE.metadata.bind = engine 10 | BASE.metadata.create_all(engine) 11 | return scoped_session(sessionmaker(bind=engine, autoflush=False)) 12 | 13 | 14 | BASE = declarative_base() 15 | SESSION = start() 16 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/afk_redis.py: -------------------------------------------------------------------------------- 1 | from GabiBraunRobot import REDIS 2 | 3 | # AFK 4 | def is_user_afk(userid): 5 | rget = REDIS.get(f'is_afk_{userid}') 6 | if rget: 7 | return True 8 | else: 9 | return False 10 | 11 | 12 | def start_afk(userid, reason): 13 | REDIS.set(f'is_afk_{userid}', reason) 14 | 15 | def afk_reason(userid): 16 | return strb(REDIS.get(f'is_afk_{userid}')) 17 | 18 | def end_afk(userid): 19 | REDIS.delete(f'is_afk_{userid}') 20 | return True 21 | 22 | # Helpers 23 | def strb(redis_string): 24 | return str(redis_string) 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'Bug report ' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots(if any)** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /animequote.py: -------------------------------------------------------------------------------- 1 | import html 2 | import random 3 | import GabiBraunRobot.modules.animequote_string as animequote_string 4 | from GabiBraunRobot import dispatcher 5 | from telegram import ParseMode, Update, Bot 6 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 7 | from telegram.ext import CallbackContext, run_async 8 | 9 | @run_async 10 | def aq(update: Update, context: CallbackContext): 11 | args = context.args 12 | update.effective_message.reply_text(random.choice(animequote_string.ANIMEQUOTE)) 13 | 14 | __help__ = """ 15 | • `/aq`*:* for random animequote 16 | """ 17 | 18 | AQ_HANDLER = DisableAbleCommandHandler("aq", aq) 19 | 20 | dispatcher.add_handler(AQ_HANDLER) 21 | 22 | __mod_name__ = "Animequote" 23 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/regex_helper.py: -------------------------------------------------------------------------------- 1 | import regex 2 | 3 | 4 | def regex_searcher(regex_string, string): 5 | try: 6 | search = regex.search(regex_string, string, timeout=6) 7 | except TimeoutError: 8 | return False 9 | except Exception: 10 | return False 11 | return search 12 | 13 | 14 | def infinite_loop_check(regex_string): 15 | loop_matches = [ 16 | r'\((.{1,}[\+\*]){1,}\)[\+\*].', 17 | r'[\(\[].{1,}\{\d(,)?\}[\)\]]\{\d(,)?\}', 18 | r'\(.{1,}\)\{.{1,}(,)?\}\(.*\)(\+|\* |\{.*\})' 19 | ] 20 | for match in loop_matches: 21 | match_1 = regex.search(match, regex_string) 22 | if match_1: 23 | return True 24 | return False 25 | -------------------------------------------------------------------------------- /.github/disabled/docker_disabled.yml: -------------------------------------------------------------------------------- 1 | name: BuildDocker 2 | 3 | on: 4 | 5 | push: 6 | 7 | branches: [ shiken ] 8 | 9 | pull_request: 10 | 11 | branches: [ shiken ] 12 | 13 | jobs: 14 | 15 | build: 16 | 17 | runs-on: ubuntu-20.04 18 | 19 | steps: 20 | 21 | - uses: actions/checkout@master 22 | - run: bash exp.sh 23 | - uses: elgohr/Publish-Docker-Github-Action@master 24 | 25 | with: 26 | 27 | name: HuntingBots/GabiBraunRobot 28 | 29 | username: ${{ secrets.DOCKER_USERNAME }} 30 | 31 | password: ${{ secrets.DOCKER_PASSWORD }} 32 | 33 | dockerfile: Dockerfile 34 | 35 | buildoptions: --force-rm --compress --no-cache --squash 36 | 37 | tags: "shiken" 38 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | future 2 | emoji 3 | lxml 4 | beautifulsoup4 5 | requests 6 | sqlalchemy==1.3.20 7 | python-telegram-bot==12.8 8 | pyrogram 9 | psycopg2-binary 10 | feedparser 11 | pynewtonmath 12 | spongemock 13 | zalgo-text 14 | geopy 15 | nltk 16 | psutil 17 | aiohttp>=2.2.5 18 | Pillow>=4.2.0 19 | CurrencyConverter 20 | googletrans 21 | jikanpy 22 | speedtest-cli 23 | coffeehouse 24 | cachetools 25 | google_trans_new 26 | regex 27 | redis 28 | bleach 29 | git+https://github.com/starry69/python-markdown2.git 30 | wikipedia 31 | telethon==1.16.4 32 | tswift 33 | fontTools 34 | spamwatch 35 | alphabet_detector 36 | pyrate-limiter 37 | gtts 38 | nekos.py 39 | pybase64 40 | youtube_dl 41 | pySmartDL 42 | pytz 43 | validators 44 | pyrogram 45 | tgcrypto 46 | hachoir 47 | configparser 48 | cloudscraper 49 | -------------------------------------------------------------------------------- /.github/workflows/yapf.yml: -------------------------------------------------------------------------------- 1 | name: Format Python code 2 | on: 3 | push: 4 | branches: [shiken] 5 | pull_request: 6 | branches: [shiken] 7 | jobs: 8 | Autoyapf: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Auto yapf 13 | uses: Accipiter7/yapf-action@master 14 | - name: Create Pull Request 15 | uses: peter-evans/create-pull-request@master 16 | with: 17 | commit-message: Automated code formatting. 18 | committer: Accipiter7 19 | title: Automated code formatting. 20 | body: This is an automated code formatting pull request. 21 | labels: Code Formatting 22 | reviewers: Accipiter7 23 | branch: autofix 24 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/readable_time.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | def get_readable_time(seconds: int) -> str: 4 | count = 0 5 | readable_time = "" 6 | time_list = [] 7 | time_suffix_list = ["s", "m", "h", "days"] 8 | 9 | while count < 4: 10 | count += 1 11 | if count < 3: 12 | remainder, result = divmod(seconds, 60) 13 | else: 14 | remainder, result = divmod(seconds, 24) 15 | if seconds == 0 and remainder == 0: 16 | break 17 | time_list.append(int(result)) 18 | seconds = int(remainder) 19 | 20 | for x in range(len(time_list)): 21 | time_list[x] = str(time_list[x]) + time_suffix_list[x] 22 | if len(time_list) == 4: 23 | readable_time += time_list.pop() + ", " 24 | 25 | time_list.reverse() 26 | readable_time += ":".join(time_list) 27 | 28 | return readable_time 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Gabi Braun Robot 6 | 7 | 8 | ## How to setup on Heroku 9 | For starters click on this button 10 | 11 |

12 | 13 | 14 | ## Our Telegram Channel and Group 15 | 16 | * [Support](https://telegram.dog/GabiHelpSupport) 17 | * [Discussion](https://telegram.dog/helpcenterbot1) 18 | * [Second Group](https://telegram.dog/Ast_Official_Channel) 19 | 20 | ## Credits, 21 | * [The Ghost Hunter](https://telegram.dog/The_Ghost_Hunter) 22 | 23 | ## Bot Owner 24 | * [Falco Grice](https://telegram.dog/Official_Flying_Titan) 25 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/truth_and_dare.py: -------------------------------------------------------------------------------- 1 | import html 2 | import random 3 | import GabiBraunRobot.modules.truth_and_dare_string as truth_and_dare_string 4 | from GabiBraunRobot import dispatcher 5 | from telegram import ParseMode, Update, Bot 6 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 7 | from telegram.ext import CallbackContext, run_async 8 | 9 | @run_async 10 | def truth(update: Update, context: CallbackContext): 11 | args = context.args 12 | update.effective_message.reply_text(random.choice(truth_and_dare_string.TRUTH)) 13 | 14 | @run_async 15 | def dare(update: Update, context: CallbackContext): 16 | args = context.args 17 | update.effective_message.reply_text(random.choice(truth_and_dare_string.DARE)) 18 | 19 | TRUTH_HANDLER = DisableAbleCommandHandler("truth", truth) 20 | DARE_HANDLER = DisableAbleCommandHandler("dare", dare) 21 | 22 | dispatcher.add_handler(TRUTH_HANDLER) 23 | dispatcher.add_handler(DARE_HANDLER) 24 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/ud.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from GabiBraunRobot import dispatcher 3 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 4 | from telegram import ParseMode, Update 5 | from telegram.ext import CallbackContext, run_async 6 | 7 | 8 | @run_async 9 | def ud(update: Update, context: CallbackContext): 10 | message = update.effective_message 11 | text = message.text[len('/ud '):] 12 | results = requests.get( 13 | f'https://api.urbandictionary.com/v0/define?term={text}').json() 14 | try: 15 | reply_text = f'*{text}*\n\n{results["list"][0]["definition"]}\n\n_{results["list"][0]["example"]}_' 16 | except: 17 | reply_text = "No results found." 18 | message.reply_text(reply_text, parse_mode=ParseMode.MARKDOWN) 19 | 20 | 21 | UD_HANDLER = DisableAbleCommandHandler(["ud"], ud) 22 | 23 | dispatcher.add_handler(UD_HANDLER) 24 | 25 | __command_list__ = ["ud"] 26 | __handlers__ = [UD_HANDLER] 27 | -------------------------------------------------------------------------------- /start_service.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: This runs the batch file as an admin - required UAC to be off 3 | :: This is just an asty hack in to get job done cause we host it on windows dedi. 4 | :: BatchGotAdmin 5 | :------------------------------------- 6 | REM --> Check for permissions 7 | >nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" 8 | 9 | REM --> If error flag set, we do not have admin. 10 | if '%errorlevel%' NEQ '0' ( 11 | echo Requesting administrative privileges... 12 | goto UACPrompt 13 | ) else ( goto gotAdmin ) 14 | 15 | :UACPrompt 16 | echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" 17 | set params = %*:"="" 18 | echo UAC.ShellExecute "cmd.exe", "/c %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" 19 | 20 | "%temp%\getadmin.vbs" 21 | del "%temp%\getadmin.vbs" 22 | exit /B 23 | 24 | :gotAdmin 25 | pushd "%CD%" 26 | CD /D "%~dp0" 27 | :-------------------------------------- 28 | :: your commands begin from this point. 29 | :: stops the service and then starts it 30 | net stop GabiBraunRobot 31 | net start GabiBraunRobot 32 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/paste.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from GabiBraunRobot import dispatcher 3 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 4 | from telegram import ParseMode, Update 5 | from telegram.ext import CallbackContext, run_async 6 | 7 | 8 | @run_async 9 | def paste(update: Update, context: CallbackContext): 10 | args = context.args 11 | message = update.effective_message 12 | 13 | if message.reply_to_message: 14 | data = message.reply_to_message.text 15 | 16 | elif len(args) >= 1: 17 | data = message.text.split(None, 1)[1] 18 | 19 | else: 20 | message.reply_text("What am I supposed to do with this?") 21 | return 22 | 23 | key = requests.post( 24 | 'https://nekobin.com/api/documents', json={ 25 | "content": data 26 | }).json().get('result').get('key') 27 | 28 | url = f'https://nekobin.com/{key}' 29 | 30 | reply_text = f'Nekofied to *Nekobin* : {url}' 31 | 32 | message.reply_text( 33 | reply_text, 34 | parse_mode=ParseMode.MARKDOWN, 35 | disable_web_page_preview=True) 36 | 37 | 38 | PASTE_HANDLER = DisableAbleCommandHandler("paste", paste) 39 | dispatcher.add_handler(PASTE_HANDLER) 40 | 41 | __command_list__ = ["paste"] 42 | __handlers__ = [PASTE_HANDLER] 43 | -------------------------------------------------------------------------------- /GabiBraunRobot/mwt.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class MWT(object): 5 | """Memorize With Timeout""" 6 | 7 | _caches = {} 8 | _timeouts = {} 9 | 10 | def __init__(self, timeout=2): 11 | self.timeout = timeout 12 | 13 | def collect(self): 14 | """Clear cache of results which have timed out""" 15 | for func in self._caches: 16 | cache = {} 17 | for key in self._caches[func]: 18 | if (time.time() - 19 | self._caches[func][key][1]) < self._timeouts[func]: 20 | cache[key] = self._caches[func][key] 21 | self._caches[func] = cache 22 | 23 | def __call__(self, f): 24 | self.cache = self._caches[f] = {} 25 | self._timeouts[f] = self.timeout 26 | 27 | def func(*args, **kwargs): 28 | kw = sorted(kwargs.items()) 29 | key = (args, tuple(kw)) 30 | try: 31 | v = self.cache[key] 32 | # print("cache") 33 | if (time.time() - v[1]) > self.timeout: 34 | raise KeyError 35 | except KeyError: 36 | # print("new") 37 | v = self.cache[key] = f(*args, **kwargs), time.time() 38 | return v[0] 39 | 40 | func.func_name = f.__name__ 41 | 42 | return func 43 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/alternate.py: -------------------------------------------------------------------------------- 1 | from telegram.error import BadRequest 2 | from functools import wraps 3 | from telegram import ChatAction 4 | 5 | 6 | def send_message(message, text, *args, **kwargs): 7 | try: 8 | return message.reply_text(text, *args, **kwargs) 9 | except error.BadRequest as err: 10 | if str(err) == "Reply message not found": 11 | return message.reply_text(text, quote=False, *args, **kwargs) 12 | 13 | 14 | def typing_action(func): 15 | """Sends typing action while processing func command.""" 16 | 17 | @wraps(func) 18 | def command_func(update, context, *args, **kwargs): 19 | context.bot.send_chat_action( 20 | chat_id=update.effective_chat.id, action=ChatAction.TYPING 21 | ) 22 | return func(update, context, *args, **kwargs) 23 | 24 | return command_func 25 | 26 | 27 | def send_action(action): 28 | """Sends `action` while processing func command.""" 29 | 30 | def decorator(func): 31 | @wraps(func) 32 | def command_func(update, context, *args, **kwargs): 33 | context.bot.send_chat_action( 34 | chat_id=update.effective_chat.id, action=action 35 | ) 36 | return func(update, context, *args, **kwargs) 37 | 38 | return command_func 39 | 40 | return decorator 41 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/animequotes.py: -------------------------------------------------------------------------------- 1 | import html 2 | import random 3 | import time 4 | 5 | from telegram import ParseMode, Update, ChatPermissions 6 | from telegram.ext import CallbackContext, run_async 7 | from telegram.error import BadRequest 8 | 9 | import GabiBraunRobot.modules.animequotes_strings as animequotes_strings 10 | from GabiBraunRobot import dispatcher 11 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 12 | from GabiBraunRobot.modules.helper_funcs.chat_status import (is_user_admin) 13 | from GabiBraunRobot.modules.helper_funcs.extraction import extract_user 14 | 15 | @run_async 16 | def animequotes(update: Update, context: CallbackContext): 17 | message = update.effective_message 18 | name = message.reply_to_message.from_user.first_name if message.reply_to_message else message.from_user.first_name 19 | reply_photo = message.reply_to_message.reply_photo if message.reply_to_message else message.reply_photo 20 | reply_photo( 21 | random.choice(animequotes_strings.QUOTES_IMG)) 22 | 23 | __help__ = """ 24 | • `/animequotes`*:* gives random anime quotes 25 | 26 | """ 27 | ANIMEQUOTES_HANDLER = DisableAbleCommandHandler("animequotes", animequotes) 28 | 29 | dispatcher.add_handler(ANIMEQUOTES_HANDLER) 30 | 31 | __mod_name__ = "AnimeQuotes" 32 | __command_list__ = [ 33 | "animequotes" 34 | ] 35 | __handlers__ = [ 36 | ANIMEQUOTES_HANDLER 37 | ] 38 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/__init__.py: -------------------------------------------------------------------------------- 1 | from GabiBraunRobot import LOAD, LOGGER, NO_LOAD 2 | 3 | 4 | def __list_all_modules(): 5 | import glob 6 | from os.path import basename, dirname, isfile 7 | 8 | # This generates a list of modules in this folder for the * in __main__ to work. 9 | mod_paths = glob.glob(dirname(__file__) + "/*.py") 10 | all_modules = [ 11 | basename(f)[:-3] 12 | for f in mod_paths 13 | if isfile(f) and f.endswith(".py") and not f.endswith('__init__.py') 14 | ] 15 | 16 | if LOAD or NO_LOAD: 17 | to_load = LOAD 18 | if to_load: 19 | if not all( 20 | any(mod == module_name 21 | for module_name in all_modules) 22 | for mod in to_load): 23 | LOGGER.error("Invalid loadorder names. Quitting.") 24 | quit(1) 25 | 26 | all_modules = sorted(set(all_modules) - set(to_load)) 27 | to_load = list(all_modules) + to_load 28 | 29 | else: 30 | to_load = all_modules 31 | 32 | if NO_LOAD: 33 | LOGGER.info("Not loading: {}".format(NO_LOAD)) 34 | return [item for item in to_load if item not in NO_LOAD] 35 | 36 | return to_load 37 | 38 | return all_modules 39 | 40 | 41 | ALL_MODULES = __list_all_modules() 42 | LOGGER.info("Modules to load: %s", str(ALL_MODULES)) 43 | __all__ = ALL_MODULES + ["ALL_MODULES"] 44 | -------------------------------------------------------------------------------- /GabiBraunRobot/utils/progress.py: -------------------------------------------------------------------------------- 1 | import math 2 | import time 3 | 4 | from .exceptions import CancelProcess 5 | from .tools import humanbytes, time_formatter 6 | 7 | 8 | async def progress( 9 | current, total, gdrive, start, prog_type, file_name=None, is_cancelled=False 10 | ): 11 | now = time.time() 12 | diff = now - start 13 | if is_cancelled is True: 14 | raise CancelProcess 15 | 16 | if round(diff % 10.00) == 0 or current == total: 17 | percentage = current * 100 / total 18 | speed = current / diff 19 | elapsed_time = round(diff) 20 | eta = round((total - current) / speed) 21 | if "upload" in prog_type.lower(): 22 | status = "Uploading" 23 | elif "download" in prog_type.lower(): 24 | status = "Downloading" 25 | else: 26 | status = "Unknown" 27 | progress_str = "`{0}` | [{1}{2}] `{3}%`".format( 28 | status, 29 | "".join("●" for i in range(math.floor(percentage / 10))), 30 | "".join("○" for i in range(10 - math.floor(percentage / 10))), 31 | round(percentage, 2), 32 | ) 33 | tmp = ( 34 | f"{progress_str}\n" 35 | f"`{humanbytes(current)} of {humanbytes(total)}" 36 | f" @ {humanbytes(speed)}`\n" 37 | f"`ETA` -> {time_formatter(eta)}\n" 38 | f"`Duration` -> {time_formatter(elapsed_time)}" 39 | ) 40 | await gdrive.edit(f"`{prog_type}`\n\n" f"`Status`\n{tmp}") -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/filters.py: -------------------------------------------------------------------------------- 1 | from GabiBraunRobot import DEV_USERS, DRAGONS, DEMONS 2 | from telegram import Message 3 | from telegram.ext import BaseFilter 4 | 5 | 6 | class CustomFilters(object): 7 | 8 | class _Supporters(BaseFilter): 9 | 10 | def filter(self, message: Message): 11 | return bool(message.from_user and message.from_user.id in DEMONS) 12 | 13 | support_filter = _Supporters() 14 | 15 | class _Sudoers(BaseFilter): 16 | 17 | def filter(self, message: Message): 18 | return bool(message.from_user and message.from_user.id in DRAGONS) 19 | 20 | sudo_filter = _Sudoers() 21 | 22 | class _Developers(BaseFilter): 23 | 24 | def filter(self, message: Message): 25 | return bool(message.from_user and message.from_user.id in DEV_USERS) 26 | 27 | dev_filter = _Developers() 28 | 29 | class _MimeType(BaseFilter): 30 | 31 | def __init__(self, mimetype): 32 | self.mime_type = mimetype 33 | self.name = "CustomFilters.mime_type({})".format(self.mime_type) 34 | 35 | def filter(self, message: Message): 36 | return bool(message.document and 37 | message.document.mime_type == self.mime_type) 38 | 39 | mime_type = _MimeType 40 | 41 | class _HasText(BaseFilter): 42 | 43 | def filter(self, message: Message): 44 | return bool(message.text or message.sticker or message.photo or 45 | message.document or message.video) 46 | 47 | has_text = _HasText() 48 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/rules_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, String, UnicodeText, distinct, func 5 | 6 | 7 | class Rules(BASE): 8 | __tablename__ = "rules" 9 | chat_id = Column(String(14), primary_key=True) 10 | rules = Column(UnicodeText, default="") 11 | 12 | def __init__(self, chat_id): 13 | self.chat_id = chat_id 14 | 15 | def __repr__(self): 16 | return "".format(self.chat_id, self.rules) 17 | 18 | 19 | Rules.__table__.create(checkfirst=True) 20 | 21 | INSERTION_LOCK = threading.RLock() 22 | 23 | 24 | def set_rules(chat_id, rules_text): 25 | with INSERTION_LOCK: 26 | rules = SESSION.query(Rules).get(str(chat_id)) 27 | if not rules: 28 | rules = Rules(str(chat_id)) 29 | rules.rules = rules_text 30 | 31 | SESSION.add(rules) 32 | SESSION.commit() 33 | 34 | 35 | def get_rules(chat_id): 36 | rules = SESSION.query(Rules).get(str(chat_id)) 37 | ret = "" 38 | if rules: 39 | ret = rules.rules 40 | 41 | SESSION.close() 42 | return ret 43 | 44 | 45 | def num_chats(): 46 | try: 47 | return SESSION.query(func.count(distinct(Rules.chat_id))).scalar() 48 | finally: 49 | SESSION.close() 50 | 51 | 52 | def migrate_chat(old_chat_id, new_chat_id): 53 | with INSERTION_LOCK: 54 | chat = SESSION.query(Rules).get(str(old_chat_id)) 55 | if chat: 56 | chat.chat_id = str(new_chat_id) 57 | SESSION.commit() 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are very welcome! Here are some guidelines on how the project is designed. 4 | 5 | ### CodeStyle 6 | 7 | - Adhere to PEP8 as much as possible. 8 | 9 | - Line lengths should be under 120 characters, use list comprehensions over map/filter, don't leave trailing whitespace. 10 | 11 | - More complex pieces of code should be commented for future reference. 12 | 13 | ### Structure 14 | 15 | There are a few self-imposed rules on the project structure, to keep the project as tidy as possible. 16 | - All modules should go into the `modules/` directory. 17 | - Any database accesses should be done in `modules/sql/` - no instances of SESSION should be imported anywhere else. 18 | - Make sure your database sessions are properly scoped! Always close them properly. 19 | - When creating a new module, there should be as few changes to other files as possible required to incorporate it. 20 | Removing the module file should result in a bot which is still in perfect working condition. 21 | - If a module is dependent on multiple other files, which might not be loaded, then create a list of at module 22 | load time, in `__main__`, by looking at attributes. This is how migration, /help, /stats, /info, and many other things 23 | are based off of. It allows the bot to work fine with the LOAD and NO_LOAD configurations. 24 | - Keep in mind that some things might clash; eg a regex handler could clash with a command handler - in this case, you 25 | should put them in different dispatcher groups. 26 | 27 | Might seem complicated, but it'll make sense when you get into it. Feel free to ask me for a hand/advice (in `@onepunchsupport`)! 28 | -------------------------------------------------------------------------------- /GabiBraunRobot/utils/tools.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import re 3 | 4 | 5 | async def md5(fname: str) -> str: 6 | hash_md5 = hashlib.md5() 7 | with open(fname, "rb") as f: 8 | for chunk in iter(lambda: f.read(4096), b""): 9 | hash_md5.update(chunk) 10 | return hash_md5.hexdigest() 11 | 12 | 13 | def humanbytes(size: int) -> str: 14 | if size is None or isinstance(size, str): 15 | return "" 16 | 17 | power = 2 ** 10 18 | raised_to_pow = 0 19 | dict_power_n = {0: "", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} 20 | while size > power: 21 | size /= power 22 | raised_to_pow += 1 23 | return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B" 24 | 25 | 26 | def time_formatter(seconds: int) -> str: 27 | minutes, seconds = divmod(seconds, 60) 28 | hours, minutes = divmod(minutes, 60) 29 | days, hours = divmod(hours, 24) 30 | tmp = ( 31 | ((str(days) + " day(s), ") if days else "") 32 | + ((str(hours) + " hour(s), ") if hours else "") 33 | + ((str(minutes) + " minute(s), ") if minutes else "") 34 | + ((str(seconds) + " second(s), ") if seconds else "") 35 | ) 36 | return tmp[:-2] 37 | 38 | 39 | def human_to_bytes(size: str) -> int: 40 | units = { 41 | "M": 2 ** 20, 42 | "MB": 2 ** 20, 43 | "G": 2 ** 30, 44 | "GB": 2 ** 30, 45 | "T": 2 ** 40, 46 | "TB": 2 ** 40, 47 | } 48 | 49 | size = size.upper() 50 | if not re.match(r" ", size): 51 | size = re.sub(r"([KMGT])", r" \1", size) 52 | number, unit = [string.strip() for string in size.split()] 53 | return int(float(number) * units[unit]) -------------------------------------------------------------------------------- /GabiBraunRobot/igris.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from GabiBraunRobot import telethn 3 | 4 | """Triggers start command in pm and in groupchats""" 5 | def igrisbot(**args): 6 | """New message.""" 7 | pattern = args.get('pattern', None) 8 | r_pattern = r'^[/!]' 9 | if pattern is not None and not pattern.startswith('(?i)'): 10 | args['pattern'] = '(?i)' + pattern 11 | args['pattern'] = pattern.replace('^/', r_pattern, 1) 12 | 13 | def decorator(func): 14 | telethn.add_event_handler(func, events.NewMessage(**args)) 15 | return func 16 | 17 | return decorator 18 | 19 | 20 | def inlinequery(**args): 21 | """Inline query.""" 22 | pattern = args.get('pattern', None) 23 | if pattern is not None and not pattern.startswith('(?i)'): 24 | args['pattern'] = '(?i)' + pattern 25 | 26 | def decorator(func): 27 | telethn.add_event_handler(func, events.InlineQuery(**args)) 28 | return func 29 | 30 | return decorator 31 | 32 | 33 | def userupdate(**args): 34 | """User updates.""" 35 | 36 | def decorator(func): 37 | telethn.add_event_handler(func, events.UserUpdate(**args)) 38 | return func 39 | 40 | return decorator 41 | 42 | 43 | def callbackquery(**args): 44 | """Callback query.""" 45 | 46 | def decorator(func): 47 | telethn.add_event_handler(func, events.CallbackQuery(**args)) 48 | return func 49 | 50 | return decorator 51 | 52 | 53 | def chataction(**args): 54 | """Chat actions.""" 55 | 56 | def decorator(func): 57 | telethn.add_event_handler(func, events.ChatAction(**args)) 58 | return func 59 | 60 | return decorator 61 | -------------------------------------------------------------------------------- /GabiBraunRobot/events.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from GabiBraunRobot import telethn 3 | 4 | 5 | def register(**args): 6 | """ Registers a new message. """ 7 | pattern = args.get('pattern', None) 8 | 9 | r_pattern = r'^[/!]' 10 | 11 | if pattern is not None and not pattern.startswith('(?i)'): 12 | args['pattern'] = '(?i)' + pattern 13 | 14 | args['pattern'] = pattern.replace('^/', r_pattern, 1) 15 | 16 | def decorator(func): 17 | telethn.add_event_handler(func, events.NewMessage(**args)) 18 | return func 19 | 20 | return decorator 21 | 22 | 23 | def chataction(**args): 24 | """ Registers chat actions. """ 25 | def decorator(func): 26 | telethn.add_event_handler(func, events.ChatAction(**args)) 27 | return func 28 | 29 | return decorator 30 | 31 | 32 | def userupdate(**args): 33 | """ Registers user updates. """ 34 | def decorator(func): 35 | telethn.add_event_handler(func, events.UserUpdate(**args)) 36 | return func 37 | 38 | return decorator 39 | 40 | 41 | def inlinequery(**args): 42 | """ Registers inline query. """ 43 | pattern = args.get('pattern', None) 44 | 45 | if pattern is not None and not pattern.startswith('(?i)'): 46 | args['pattern'] = '(?i)' + pattern 47 | 48 | def decorator(func): 49 | telethn.add_event_handler(func, events.InlineQuery(**args)) 50 | return func 51 | 52 | return decorator 53 | 54 | 55 | def callbackquery(**args): 56 | """ Registers inline query. """ 57 | def decorator(func): 58 | telethn.add_event_handler(func, events.CallbackQuery(**args)) 59 | return func 60 | 61 | return decorator 62 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/approve_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import Column, Integer, String 4 | 5 | from GabiBraunRobot.modules.sql import BASE, SESSION 6 | 7 | 8 | class Approvals(BASE): 9 | __tablename__ = "approval" 10 | chat_id = Column(String(14), primary_key=True) 11 | user_id = Column(Integer, primary_key=True) 12 | 13 | def __init__(self, chat_id, user_id): 14 | self.chat_id = str(chat_id) # ensure string 15 | self.user_id = user_id 16 | 17 | def __repr__(self): 18 | return "" % self.user_id 19 | 20 | 21 | Approvals.__table__.create(checkfirst=True) 22 | 23 | APPROVE_INSERTION_LOCK = threading.RLock() 24 | 25 | 26 | def approve(chat_id, user_id): 27 | with APPROVE_INSERTION_LOCK: 28 | approve_user = Approvals(str(chat_id), user_id) 29 | SESSION.add(approve_user) 30 | SESSION.commit() 31 | 32 | 33 | def is_approved(chat_id, user_id): 34 | try: 35 | return SESSION.query(Approvals).get((str(chat_id), user_id)) 36 | finally: 37 | SESSION.close() 38 | 39 | 40 | def disapprove(chat_id, user_id): 41 | with APPROVE_INSERTION_LOCK: 42 | disapprove_user = SESSION.query(Approvals).get((str(chat_id), user_id)) 43 | if disapprove_user: 44 | SESSION.delete(disapprove_user) 45 | SESSION.commit() 46 | return True 47 | else: 48 | SESSION.close() 49 | return False 50 | 51 | 52 | def list_approved(chat_id): 53 | try: 54 | return ( 55 | SESSION.query(Approvals) 56 | .filter(Approvals.chat_id == str(chat_id)) 57 | .order_by(Approvals.user_id.asc()) 58 | .all() 59 | ) 60 | finally: 61 | SESSION.close() 62 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/shell.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from GabiBraunRobot import LOGGER, dispatcher 4 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus 5 | from telegram import ParseMode, Update 6 | from telegram.ext import CallbackContext, CommandHandler 7 | from telegram.ext.dispatcher import run_async 8 | 9 | 10 | @dev_plus 11 | @run_async 12 | def shell(update: Update, context: CallbackContext): 13 | message = update.effective_message 14 | cmd = message.text.split(' ', 1) 15 | if len(cmd) == 1: 16 | message.reply_text('No command to execute was given.') 17 | return 18 | cmd = cmd[1] 19 | process = subprocess.Popen( 20 | cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 21 | stdout, stderr = process.communicate() 22 | reply = '' 23 | stderr = stderr.decode() 24 | stdout = stdout.decode() 25 | if stdout: 26 | reply += f"*Stdout*\n`{stdout}`\n" 27 | LOGGER.info(f"Shell - {cmd} - {stdout}") 28 | if stderr: 29 | reply += f"*Stderr*\n`{stderr}`\n" 30 | LOGGER.error(f"Shell - {cmd} - {stderr}") 31 | if len(reply) > 3000: 32 | with open('shell_output.txt', 'w') as file: 33 | file.write(reply) 34 | with open('shell_output.txt', 'rb') as doc: 35 | context.bot.send_document( 36 | document=doc, 37 | filename=doc.name, 38 | reply_to_message_id=message.message_id, 39 | chat_id=message.chat_id) 40 | else: 41 | message.reply_text(reply, parse_mode=ParseMode.MARKDOWN) 42 | 43 | 44 | SHELL_HANDLER = CommandHandler(['sh'], shell) 45 | dispatcher.add_handler(SHELL_HANDLER) 46 | __mod_name__ = "Shell" 47 | __command_list__ = ['sh'] 48 | __handlers__ = [SHELL_HANDLER] 49 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/blacklistusers_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, String, UnicodeText 5 | 6 | 7 | class BlacklistUsers(BASE): 8 | __tablename__ = "blacklistusers" 9 | user_id = Column(String(14), primary_key=True) 10 | reason = Column(UnicodeText) 11 | 12 | def __init__(self, user_id, reason=None): 13 | self.user_id = user_id 14 | self.reason = reason 15 | 16 | 17 | BlacklistUsers.__table__.create(checkfirst=True) 18 | 19 | BLACKLIST_LOCK = threading.RLock() 20 | BLACKLIST_USERS = set() 21 | 22 | 23 | def blacklist_user(user_id, reason=None): 24 | with BLACKLIST_LOCK: 25 | user = SESSION.query(BlacklistUsers).get(str(user_id)) 26 | if not user: 27 | user = BlacklistUsers(str(user_id), reason) 28 | else: 29 | user.reason = reason 30 | 31 | SESSION.add(user) 32 | SESSION.commit() 33 | __load_blacklist_userid_list() 34 | 35 | 36 | def unblacklist_user(user_id): 37 | with BLACKLIST_LOCK: 38 | user = SESSION.query(BlacklistUsers).get(str(user_id)) 39 | if user: 40 | SESSION.delete(user) 41 | 42 | SESSION.commit() 43 | __load_blacklist_userid_list() 44 | 45 | 46 | def get_reason(user_id): 47 | user = SESSION.query(BlacklistUsers).get(str(user_id)) 48 | rep = "" 49 | if user: 50 | rep = user.reason 51 | 52 | SESSION.close() 53 | return rep 54 | 55 | 56 | def is_user_blacklisted(user_id): 57 | return user_id in BLACKLIST_USERS 58 | 59 | 60 | def __load_blacklist_userid_list(): 61 | global BLACKLIST_USERS 62 | try: 63 | BLACKLIST_USERS = { 64 | int(x.user_id) for x in SESSION.query(BlacklistUsers).all() 65 | } 66 | finally: 67 | SESSION.close() 68 | 69 | 70 | __load_blacklist_userid_list() 71 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/get_common_chats.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import sleep 3 | 4 | from GabiBraunRobot import OWNER_ID, dispatcher 5 | from GabiBraunRobot.modules.helper_funcs.extraction import extract_user 6 | from GabiBraunRobot.modules.sql.users_sql import get_user_com_chats 7 | from telegram import Update 8 | from telegram.error import BadRequest, RetryAfter, Unauthorized 9 | from telegram.ext import CallbackContext, CommandHandler, Filters 10 | from telegram.ext.dispatcher import run_async 11 | 12 | 13 | @run_async 14 | def get_user_common_chats(update: Update, context: CallbackContext): 15 | bot, args = context.bot, context.args 16 | msg = update.effective_message 17 | user = extract_user(msg, args) 18 | if not user: 19 | msg.reply_text("I share no common chats with the void.") 20 | return 21 | common_list = get_user_com_chats(user) 22 | if not common_list: 23 | msg.reply_text("No common chats with this user!") 24 | return 25 | name = bot.get_chat(user).first_name 26 | text = f"Common chats with {name}\n" 27 | for chat in common_list: 28 | try: 29 | chat_name = bot.get_chat(chat).title 30 | sleep(0.3) 31 | text += f"• {chat_name}\n" 32 | except BadRequest: 33 | pass 34 | except Unauthorized: 35 | pass 36 | except RetryAfter as e: 37 | sleep(e.retry_after) 38 | 39 | if len(text) < 4096: 40 | msg.reply_text(text, parse_mode="HTML") 41 | else: 42 | with open("common_chats.txt", 'w') as f: 43 | f.write(text) 44 | with open("common_chats.txt", 'rb') as f: 45 | msg.reply_document(f) 46 | os.remove("common_chats.txt") 47 | 48 | 49 | COMMON_CHATS_HANDLER = CommandHandler( 50 | "getchats", get_user_common_chats, filters=Filters.user(OWNER_ID)) 51 | 52 | dispatcher.add_handler(COMMON_CHATS_HANDLER) 53 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/chatbot_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, String 5 | 6 | 7 | class ChatbotChats(BASE): 8 | __tablename__ = "chatbot_chats" 9 | chat_id = Column(String(14), primary_key=True) 10 | ses_id = Column(String(70)) 11 | expires = Column(String(15)) 12 | 13 | def __init__(self, chat_id, ses_id, expires): 14 | self.chat_id = chat_id 15 | self.ses_id = ses_id 16 | self.expires = expires 17 | 18 | 19 | ChatbotChats.__table__.create(checkfirst=True) 20 | 21 | INSERTION_LOCK = threading.RLock() 22 | 23 | 24 | def is_chat(chat_id): 25 | try: 26 | chat = SESSION.query(ChatbotChats).get(str(chat_id)) 27 | if chat: 28 | return True 29 | else: 30 | return False 31 | finally: 32 | SESSION.close() 33 | 34 | 35 | def set_ses(chat_id, ses_id, expires): 36 | with INSERTION_LOCK: 37 | autochat = SESSION.query(ChatbotChats).get(str(chat_id)) 38 | if not autochat: 39 | autochat = ChatbotChats(str(chat_id), str(ses_id), str(expires)) 40 | else: 41 | autochat.ses_id = str(ses_id) 42 | autochat.expires = str(expires) 43 | 44 | SESSION.add(autochat) 45 | SESSION.commit() 46 | 47 | 48 | def get_ses(chat_id): 49 | autochat = SESSION.query(ChatbotChats).get(str(chat_id)) 50 | sesh = "" 51 | exp = "" 52 | if autochat: 53 | sesh = str(autochat.ses_id) 54 | exp = str(autochat.expires) 55 | 56 | SESSION.close() 57 | return sesh, exp 58 | 59 | 60 | def rem_chat(chat_id): 61 | with INSERTION_LOCK: 62 | autochat = SESSION.query(ChatbotChats).get(str(chat_id)) 63 | if autochat: 64 | SESSION.delete(autochat) 65 | 66 | SESSION.commit() 67 | 68 | 69 | def get_all_chats(): 70 | try: 71 | return SESSION.query(ChatbotChats.chat_id).all() 72 | finally: 73 | SESSION.close() 74 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/currency_converter.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from GabiBraunRobot import CASH_API_KEY, dispatcher 3 | from telegram import Update, ParseMode 4 | from telegram.ext import CallbackContext, CommandHandler, run_async 5 | 6 | 7 | @run_async 8 | def convert(update: Update, context: CallbackContext): 9 | args = update.effective_message.text.split(" ") 10 | 11 | if len(args) == 4: 12 | try: 13 | orig_cur_amount = float(args[1]) 14 | 15 | except ValueError: 16 | update.effective_message.reply_text("Invalid Amount Of Currency") 17 | return 18 | 19 | orig_cur = args[2].upper() 20 | 21 | new_cur = args[3].upper() 22 | 23 | request_url = (f"https://www.alphavantage.co/query" 24 | f"?function=CURRENCY_EXCHANGE_RATE" 25 | f"&from_currency={orig_cur}" 26 | f"&to_currency={new_cur}" 27 | f"&apikey={CASH_API_KEY}") 28 | response = requests.get(request_url).json() 29 | try: 30 | current_rate = float( 31 | response['Realtime Currency Exchange Rate']['5. Exchange Rate']) 32 | except KeyError: 33 | update.effective_message.reply_text("Currency Not Supported.") 34 | return 35 | new_cur_amount = round(orig_cur_amount * current_rate, 5) 36 | update.effective_message.reply_text( 37 | f"{orig_cur_amount} {orig_cur} = {new_cur_amount} {new_cur}") 38 | 39 | elif len(args) == 1: 40 | update.effective_message.reply_text( 41 | __help__, parse_mode=ParseMode.MARKDOWN) 42 | 43 | else: 44 | update.effective_message.reply_text( 45 | f"*Invalid Args!!:* Required 3 But Passed {len(args) -1}", 46 | parse_mode=ParseMode.MARKDOWN) 47 | 48 | 49 | CONVERTER_HANDLER = CommandHandler('cash', convert) 50 | 51 | dispatcher.add_handler(CONVERTER_HANDLER) 52 | 53 | __command_list__ = ["cash"] 54 | __handlers__ = [CONVERTER_HANDLER] 55 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/userinfo_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, Integer, UnicodeText 5 | 6 | 7 | class UserInfo(BASE): 8 | __tablename__ = "userinfo" 9 | user_id = Column(Integer, primary_key=True) 10 | info = Column(UnicodeText) 11 | 12 | def __init__(self, user_id, info): 13 | self.user_id = user_id 14 | self.info = info 15 | 16 | def __repr__(self): 17 | return "" % self.user_id 18 | 19 | 20 | class UserBio(BASE): 21 | __tablename__ = "userbio" 22 | user_id = Column(Integer, primary_key=True) 23 | bio = Column(UnicodeText) 24 | 25 | def __init__(self, user_id, bio): 26 | self.user_id = user_id 27 | self.bio = bio 28 | 29 | def __repr__(self): 30 | return "" % self.user_id 31 | 32 | 33 | UserInfo.__table__.create(checkfirst=True) 34 | UserBio.__table__.create(checkfirst=True) 35 | 36 | INSERTION_LOCK = threading.RLock() 37 | 38 | 39 | def get_user_me_info(user_id): 40 | userinfo = SESSION.query(UserInfo).get(user_id) 41 | SESSION.close() 42 | if userinfo: 43 | return userinfo.info 44 | return None 45 | 46 | 47 | def set_user_me_info(user_id, info): 48 | with INSERTION_LOCK: 49 | userinfo = SESSION.query(UserInfo).get(user_id) 50 | if userinfo: 51 | userinfo.info = info 52 | else: 53 | userinfo = UserInfo(user_id, info) 54 | SESSION.add(userinfo) 55 | SESSION.commit() 56 | 57 | 58 | def get_user_bio(user_id): 59 | userbio = SESSION.query(UserBio).get(user_id) 60 | SESSION.close() 61 | if userbio: 62 | return userbio.bio 63 | return None 64 | 65 | 66 | def set_user_bio(user_id, bio): 67 | with INSERTION_LOCK: 68 | userbio = SESSION.query(UserBio).get(user_id) 69 | if userbio: 70 | userbio.bio = bio 71 | else: 72 | userbio = UserBio(user_id, bio) 73 | 74 | SESSION.add(userbio) 75 | SESSION.commit() 76 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # We're using Debian Slim Buster image 2 | FROM python:3.8.5-slim-buster 3 | 4 | ENV PIP_NO_CACHE_DIR 1 5 | 6 | RUN sed -i.bak 's/us-west-2\.ec2\.//' /etc/apt/sources.list 7 | 8 | # Installing Required Packages 9 | RUN apt update && apt upgrade -y && \ 10 | apt install --no-install-recommends -y \ 11 | debian-keyring \ 12 | debian-archive-keyring \ 13 | bash \ 14 | bzip2 \ 15 | curl \ 16 | figlet \ 17 | git \ 18 | util-linux \ 19 | libffi-dev \ 20 | libjpeg-dev \ 21 | libjpeg62-turbo-dev \ 22 | libwebp-dev \ 23 | linux-headers-amd64 \ 24 | musl-dev \ 25 | musl \ 26 | neofetch \ 27 | php-pgsql \ 28 | python3-lxml \ 29 | postgresql \ 30 | postgresql-client \ 31 | python3-psycopg2 \ 32 | libpq-dev \ 33 | libcurl4-openssl-dev \ 34 | libxml2-dev \ 35 | libxslt1-dev \ 36 | python3-pip \ 37 | python3-requests \ 38 | python3-sqlalchemy \ 39 | python3-tz \ 40 | python3-aiohttp \ 41 | openssl \ 42 | pv \ 43 | jq \ 44 | wget \ 45 | python3 \ 46 | python3-dev \ 47 | libreadline-dev \ 48 | libyaml-dev \ 49 | gcc \ 50 | sqlite3 \ 51 | libsqlite3-dev \ 52 | sudo \ 53 | zlib1g \ 54 | ffmpeg \ 55 | libssl-dev \ 56 | libgconf-2-4 \ 57 | libxi6 \ 58 | xvfb \ 59 | unzip \ 60 | libopus0 \ 61 | libopus-dev \ 62 | && rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp 63 | 64 | # Pypi package Repo upgrade 65 | RUN pip3 install --upgrade pip setuptools 66 | 67 | # Copy Python Requirements to /root/GabiBraunRobot 68 | RUN git clone -b shiken https://github.com/HuntingBots/GabiBraunRobot /root/GabiBraunRobot 69 | WORKDIR /root/GabiBraunRobot 70 | 71 | #Copy config file to /root/GabiBraunRobot/GabiBraunRobot 72 | COPY ./GabiBraunRobot/sample_config.py ./GabiBraunRobot/config.py* /root/GabiBraunRobot/GabiBraunRobot/ 73 | 74 | ENV PATH="/home/bot/bin:$PATH" 75 | 76 | # Install requirements 77 | RUN pip3 install -U -r requirements.txt 78 | 79 | # Starting Worker 80 | CMD ["python3","-m","GabiBraunRobot"] 81 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/wiki.py: -------------------------------------------------------------------------------- 1 | import wikipedia 2 | from GabiBraunRobot import dispatcher 3 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 4 | from telegram import ParseMode, Update 5 | from telegram.ext import CallbackContext, run_async 6 | from wikipedia.exceptions import DisambiguationError, PageError 7 | 8 | 9 | @run_async 10 | def wiki(update: Update, context: CallbackContext): 11 | msg = update.effective_message.reply_to_message if update.effective_message.reply_to_message else update.effective_message 12 | res = "" 13 | if msg == update.effective_message: 14 | search = msg.text.split(" ", maxsplit=1)[1] 15 | else: 16 | search = msg.text 17 | try: 18 | res = wikipedia.summary(search) 19 | except DisambiguationError as e: 20 | update.message.reply_text( 21 | "Disambiguated pages found! Adjust your query accordingly.\n{}" 22 | .format(e), 23 | parse_mode=ParseMode.HTML) 24 | except PageError as e: 25 | update.message.reply_text( 26 | "{}".format(e), parse_mode=ParseMode.HTML) 27 | if res: 28 | result = f"{search}\n\n" 29 | result += f"{res}\n" 30 | result += f"""Read more...""" 31 | if len(result) > 4000: 32 | with open("result.txt", 'w') as f: 33 | f.write(f"{result}\n\nUwU OwO OmO UmU") 34 | with open("result.txt", 'rb') as f: 35 | context.bot.send_document( 36 | document=f, 37 | filename=f.name, 38 | reply_to_message_id=update.message.message_id, 39 | chat_id=update.effective_chat.id, 40 | parse_mode=ParseMode.HTML) 41 | else: 42 | update.message.reply_text( 43 | result, 44 | parse_mode=ParseMode.HTML, 45 | disable_web_page_preview=True) 46 | 47 | 48 | WIKI_HANDLER = DisableAbleCommandHandler("wiki", wiki) 49 | dispatcher.add_handler(WIKI_HANDLER) 50 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/tts.py: -------------------------------------------------------------------------------- 1 | 2 | import html 3 | import re 4 | from datetime import datetime 5 | from typing import List 6 | import random 7 | from telegram import ChatAction 8 | from gtts import gTTS 9 | import time 10 | from telegram import ChatAction 11 | from feedparser import parse 12 | import json 13 | import urllib.request 14 | import urllib.parse 15 | import requests 16 | from GabiBraunRobot import (DEV_USERS, OWNER_ID, DRAGONS, SUPPORT_CHAT, DEMONS, 17 | TIGERS, WOLVES, dispatcher,updater) 18 | from GabiBraunRobot.__main__ import STATS, TOKEN, USER_INFO 19 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 20 | from GabiBraunRobot.modules.helper_funcs.filters import CustomFilters 21 | from GabiBraunRobot.modules.helper_funcs.chat_status import sudo_plus, user_admin 22 | from telegram import MessageEntity, ParseMode, Update, constants 23 | from telegram.error import BadRequest 24 | from emoji import UNICODE_EMOJI 25 | from telegram.ext import CallbackContext, CommandHandler, Filters, run_async 26 | from telegram.utils.helpers import mention_html 27 | 28 | @run_async 29 | def tts(update: Update, context: CallbackContext): 30 | args = context.args 31 | current_time = datetime.strftime(datetime.now(), "%d.%m.%Y %H:%M:%S") 32 | filename = datetime.now().strftime("%d%m%y-%H%M%S%f") 33 | reply = " ".join(args) 34 | update.message.chat.send_action(ChatAction.RECORD_AUDIO) 35 | lang="ml" 36 | tts = gTTS(reply, lang) 37 | tts.save("k.mp3") 38 | with open("k.mp3", "rb") as f: 39 | linelist = list(f) 40 | linecount = len(linelist) 41 | if linecount == 1: 42 | update.message.chat.send_action(ChatAction.RECORD_AUDIO) 43 | lang = "en" 44 | tts = gTTS(reply, lang) 45 | tts.save("k.mp3") 46 | with open("k.mp3", "rb") as speech: 47 | update.message.reply_voice(speech, quote=False) 48 | 49 | __help__ = """ 50 | - /tts : convert text to speech 51 | """ 52 | TTS_HANDLER = DisableAbleCommandHandler("tts", tts, pass_args=True) 53 | dispatcher.add_handler(TTS_HANDLER) 54 | 55 | __mod_name__ = "Tts" 56 | __command_list__ = ["tts"] 57 | __handlers__ = [TTS_HANDLER] 58 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/dev.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | from time import sleep 5 | 6 | from GabiBraunRobot import dispatcher 7 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus 8 | from telegram import TelegramError, Update 9 | from telegram.ext import CallbackContext, CommandHandler, run_async 10 | 11 | 12 | @run_async 13 | @dev_plus 14 | def leave(update: Update, context: CallbackContext): 15 | bot = context.bot 16 | args = context.args 17 | if args: 18 | chat_id = str(args[0]) 19 | try: 20 | bot.leave_chat(int(chat_id)) 21 | update.effective_message.reply_text("Beep boop, I left that soup!.") 22 | except TelegramError: 23 | update.effective_message.reply_text( 24 | "Beep boop, I could not leave that group(dunno why tho).") 25 | else: 26 | update.effective_message.reply_text("Send a valid chat ID") 27 | 28 | 29 | @run_async 30 | @dev_plus 31 | def gitpull(update: Update, context: CallbackContext): 32 | sent_msg = update.effective_message.reply_text( 33 | "Pulling all changes from remote and then attempting to restart.") 34 | subprocess.Popen('git pull', stdout=subprocess.PIPE, shell=True) 35 | 36 | sent_msg_text = sent_msg.text + "\n\nChanges pulled...I guess.. Restarting in " 37 | 38 | for i in reversed(range(5)): 39 | sent_msg.edit_text(sent_msg_text + str(i + 1)) 40 | sleep(1) 41 | 42 | sent_msg.edit_text("Restarted.") 43 | 44 | os.system('restart.bat') 45 | os.execv('start.bat', sys.argv) 46 | 47 | 48 | @run_async 49 | @dev_plus 50 | def restart(update: Update, context: CallbackContext): 51 | update.effective_message.reply_text( 52 | "Starting a new instance and shutting down this one") 53 | 54 | os.system('restart.bat') 55 | os.execv('start.bat', sys.argv) 56 | 57 | 58 | LEAVE_HANDLER = CommandHandler("leave", leave) 59 | GITPULL_HANDLER = CommandHandler("gitpull", gitpull) 60 | RESTART_HANDLER = CommandHandler("reboot", restart) 61 | 62 | dispatcher.add_handler(LEAVE_HANDLER) 63 | dispatcher.add_handler(GITPULL_HANDLER) 64 | dispatcher.add_handler(RESTART_HANDLER) 65 | 66 | __mod_name__ = "Dev" 67 | __handlers__ = [LEAVE_HANDLER, GITPULL_HANDLER, RESTART_HANDLER] 68 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/wallpaper.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | import requests as r 4 | from GabiBraunRobot import SUPPORT_CHAT, WALL_API, dispatcher 5 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 6 | from telegram import Update 7 | from telegram.ext import CallbackContext, run_async 8 | 9 | # Wallpapers module by @TheRealPhoenix using wall.alphacoders.com 10 | 11 | 12 | @run_async 13 | def wall(update: Update, context: CallbackContext): 14 | chat_id = update.effective_chat.id 15 | msg = update.effective_message 16 | args = context.args 17 | msg_id = update.effective_message.message_id 18 | bot = context.bot 19 | query = " ".join(args) 20 | if not query: 21 | msg.reply_text("Please enter a query!") 22 | return 23 | else: 24 | caption = query 25 | term = query.replace(" ", "%20") 26 | json_rep = r.get( 27 | f"https://wall.alphacoders.com/api2.0/get.php?auth={WALL_API}&method=search&term={term}" 28 | ).json() 29 | if not json_rep.get("success"): 30 | msg.reply_text(f"An error occurred! Report this @{SUPPORT_CHAT}") 31 | else: 32 | wallpapers = json_rep.get("wallpapers") 33 | if not wallpapers: 34 | msg.reply_text("No results found! Refine your search.") 35 | return 36 | else: 37 | index = randint(0, len(wallpapers) - 1) # Choose random index 38 | wallpaper = wallpapers[index] 39 | wallpaper = wallpaper.get("url_image") 40 | wallpaper = wallpaper.replace("\\", "") 41 | bot.send_photo( 42 | chat_id, 43 | photo=wallpaper, 44 | caption='Preview', 45 | reply_to_message_id=msg_id, 46 | timeout=60) 47 | bot.send_document( 48 | chat_id, 49 | document=wallpaper, 50 | filename='wallpaper', 51 | caption=caption, 52 | reply_to_message_id=msg_id, 53 | timeout=60) 54 | 55 | 56 | WALLPAPER_HANDLER = DisableAbleCommandHandler("wall", wall) 57 | dispatcher.add_handler(WALLPAPER_HANDLER) 58 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/rss_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, Integer, UnicodeText 5 | 6 | 7 | class RSS(BASE): 8 | __tablename__ = "rss_feed" 9 | id = Column(Integer, primary_key=True) 10 | chat_id = Column(UnicodeText, nullable=False) 11 | feed_link = Column(UnicodeText) 12 | old_entry_link = Column(UnicodeText) 13 | 14 | def __init__(self, chat_id, feed_link, old_entry_link): 15 | self.chat_id = chat_id 16 | self.feed_link = feed_link 17 | self.old_entry_link = old_entry_link 18 | 19 | def __repr__(self): 20 | return "".format( 21 | self.chat_id, self.feed_link, self.old_entry_link) 22 | 23 | 24 | RSS.__table__.create(checkfirst=True) 25 | INSERTION_LOCK = threading.RLock() 26 | 27 | 28 | def check_url_availability(tg_chat_id, tg_feed_link): 29 | try: 30 | return SESSION.query(RSS).filter(RSS.feed_link == tg_feed_link, 31 | RSS.chat_id == tg_chat_id).all() 32 | finally: 33 | SESSION.close() 34 | 35 | 36 | def add_url(tg_chat_id, tg_feed_link, tg_old_entry_link): 37 | with INSERTION_LOCK: 38 | action = RSS(tg_chat_id, tg_feed_link, tg_old_entry_link) 39 | 40 | SESSION.add(action) 41 | SESSION.commit() 42 | 43 | 44 | def remove_url(tg_chat_id, tg_feed_link): 45 | with INSERTION_LOCK: 46 | # this loops to delete any possible duplicates for the same TG User ID, TG Chat ID and link 47 | for row in check_url_availability(tg_chat_id, tg_feed_link): 48 | # add the action to the DB query 49 | SESSION.delete(row) 50 | 51 | SESSION.commit() 52 | 53 | 54 | def get_urls(tg_chat_id): 55 | try: 56 | return SESSION.query(RSS).filter(RSS.chat_id == tg_chat_id).all() 57 | finally: 58 | SESSION.close() 59 | 60 | 61 | def get_all(): 62 | try: 63 | return SESSION.query(RSS).all() 64 | finally: 65 | SESSION.close() 66 | 67 | 68 | def update_url(row_id, new_entry_links): 69 | with INSERTION_LOCK: 70 | row = SESSION.query(RSS).get(row_id) 71 | 72 | # set the new old_entry_link with the latest update from the RSS Feed 73 | row.old_entry_link = new_entry_links[0] 74 | 75 | # commit the changes to the DB 76 | SESSION.commit() 77 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/log_channel_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, String, distinct, func 5 | 6 | 7 | class GroupLogs(BASE): 8 | __tablename__ = "log_channels" 9 | chat_id = Column(String(14), primary_key=True) 10 | log_channel = Column(String(14), nullable=False) 11 | 12 | def __init__(self, chat_id, log_channel): 13 | self.chat_id = str(chat_id) 14 | self.log_channel = str(log_channel) 15 | 16 | 17 | GroupLogs.__table__.create(checkfirst=True) 18 | 19 | LOGS_INSERTION_LOCK = threading.RLock() 20 | 21 | CHANNELS = {} 22 | 23 | 24 | def set_chat_log_channel(chat_id, log_channel): 25 | with LOGS_INSERTION_LOCK: 26 | res = SESSION.query(GroupLogs).get(str(chat_id)) 27 | if res: 28 | res.log_channel = log_channel 29 | else: 30 | res = GroupLogs(chat_id, log_channel) 31 | SESSION.add(res) 32 | 33 | CHANNELS[str(chat_id)] = log_channel 34 | SESSION.commit() 35 | 36 | 37 | def get_chat_log_channel(chat_id): 38 | return CHANNELS.get(str(chat_id)) 39 | 40 | 41 | def stop_chat_logging(chat_id): 42 | with LOGS_INSERTION_LOCK: 43 | res = SESSION.query(GroupLogs).get(str(chat_id)) 44 | if res: 45 | if str(chat_id) in CHANNELS: 46 | del CHANNELS[str(chat_id)] 47 | 48 | log_channel = res.log_channel 49 | SESSION.delete(res) 50 | SESSION.commit() 51 | return log_channel 52 | 53 | 54 | def num_logchannels(): 55 | try: 56 | return SESSION.query(func.count(distinct(GroupLogs.chat_id))).scalar() 57 | finally: 58 | SESSION.close() 59 | 60 | 61 | def migrate_chat(old_chat_id, new_chat_id): 62 | with LOGS_INSERTION_LOCK: 63 | chat = SESSION.query(GroupLogs).get(str(old_chat_id)) 64 | if chat: 65 | chat.chat_id = str(new_chat_id) 66 | SESSION.add(chat) 67 | if str(old_chat_id) in CHANNELS: 68 | CHANNELS[str(new_chat_id)] = CHANNELS.get(str(old_chat_id)) 69 | 70 | SESSION.commit() 71 | 72 | 73 | def __load_log_channels(): 74 | global CHANNELS 75 | try: 76 | all_chats = SESSION.query(GroupLogs).all() 77 | CHANNELS = {chat.chat_id: chat.log_channel for chat in all_chats} 78 | finally: 79 | SESSION.close() 80 | 81 | 82 | __load_log_channels() 83 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/speed_test.py: -------------------------------------------------------------------------------- 1 | import speedtest 2 | from GabiBraunRobot import DEV_USERS, dispatcher 3 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 4 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus 5 | from telegram import (InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, 6 | Update) 7 | from telegram.ext import CallbackContext, CallbackQueryHandler, run_async 8 | 9 | 10 | def convert(speed): 11 | return round(int(speed) / 1048576, 2) 12 | 13 | 14 | @dev_plus 15 | @run_async 16 | def speedtestxyz(update: Update, context: CallbackContext): 17 | buttons = [[ 18 | InlineKeyboardButton("Image", callback_data="speedtest_image"), 19 | InlineKeyboardButton("Text", callback_data="speedtest_text") 20 | ]] 21 | update.effective_message.reply_text( 22 | "Select SpeedTest Mode", reply_markup=InlineKeyboardMarkup(buttons)) 23 | 24 | 25 | @run_async 26 | def speedtestxyz_callback(update: Update, context: CallbackContext): 27 | query = update.callback_query 28 | 29 | if query.from_user.id in DEV_USERS: 30 | msg = update.effective_message.edit_text('Running a speedtest....') 31 | speed = speedtest.Speedtest() 32 | speed.get_best_server() 33 | speed.download() 34 | speed.upload() 35 | replymsg = 'SpeedTest Results:' 36 | 37 | if query.data == 'speedtest_image': 38 | speedtest_image = speed.results.share() 39 | update.effective_message.reply_photo( 40 | photo=speedtest_image, caption=replymsg) 41 | msg.delete() 42 | 43 | elif query.data == 'speedtest_text': 44 | result = speed.results.dict() 45 | replymsg += f"\nDownload: `{convert(result['download'])}Mb/s`\nUpload: `{convert(result['upload'])}Mb/s`\nPing: `{result['ping']}`" 46 | update.effective_message.edit_text( 47 | replymsg, parse_mode=ParseMode.MARKDOWN) 48 | else: 49 | query.answer( 50 | "You are required to join Heroes Association to use this command.") 51 | 52 | 53 | SPEED_TEST_HANDLER = DisableAbleCommandHandler("speedtest", speedtestxyz) 54 | SPEED_TEST_CALLBACKHANDLER = CallbackQueryHandler( 55 | speedtestxyz_callback, pattern='speedtest_.*') 56 | 57 | dispatcher.add_handler(SPEED_TEST_HANDLER) 58 | dispatcher.add_handler(SPEED_TEST_CALLBACKHANDLER) 59 | 60 | __mod_name__ = "SpeedTest" 61 | __command_list__ = ["speedtest"] 62 | __handlers__ = [SPEED_TEST_HANDLER, SPEED_TEST_CALLBACKHANDLER] 63 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/debug.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | 4 | from telethon import events 5 | from telegram import Update 6 | from telegram.ext import CallbackContext, CommandHandler, run_async 7 | 8 | from GabiBraunRobot import telethn, dispatcher 9 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus 10 | 11 | DEBUG_MODE = False 12 | 13 | 14 | @run_async 15 | @dev_plus 16 | def debug(update: Update, context: CallbackContext): 17 | global DEBUG_MODE 18 | args = update.effective_message.text.split(None, 1) 19 | message = update.effective_message 20 | print(DEBUG_MODE) 21 | if len(args) > 1: 22 | if args[1] in ('yes', 'on'): 23 | DEBUG_MODE = True 24 | message.reply_text("Debug mode is now on.") 25 | elif args[1] in ('no', 'off'): 26 | DEBUG_MODE = False 27 | message.reply_text("Debug mode is now off.") 28 | else: 29 | if DEBUG_MODE: 30 | message.reply_text("Debug mode is currently on.") 31 | else: 32 | message.reply_text("Debug mode is currently off.") 33 | 34 | 35 | @telethn.on(events.NewMessage(pattern="[/!].*")) 36 | async def i_do_nothing_yes(event): 37 | global DEBUG_MODE 38 | if DEBUG_MODE: 39 | print(f"-{event.from_id} ({event.chat_id}) : {event.text}") 40 | if os.path.exists('updates.txt'): 41 | with open('updates.txt', 'r') as f: 42 | text = f.read() 43 | with open('updates.txt', 'w+') as f: 44 | f.write(text + 45 | f"\n-{event.from_id} ({event.chat_id}) : {event.text}") 46 | else: 47 | with open('updates.txt', 'w+') as f: 48 | f.write( 49 | f"- {event.from_id} ({event.chat_id}) : {event.text} | {datetime.datetime.now()}" 50 | ) 51 | 52 | 53 | support_chat = os.getenv('SUPPORT_CHAT') 54 | 55 | 56 | @run_async 57 | @dev_plus 58 | def logs(update: Update, context: CallbackContext): 59 | chat_username = update.effective_chat.username 60 | if not chat_username: 61 | return 62 | if chat_username != support_chat: 63 | return 64 | user = update.effective_user 65 | with open('log.txt', 'rb') as f: 66 | 67 | context.bot.send_document(document=f, filename=f.name, chat_id=user.id) 68 | 69 | 70 | LOG_HANDLER = CommandHandler('logs', logs) 71 | dispatcher.add_handler(LOG_HANDLER) 72 | 73 | DEBUG_HANDLER = CommandHandler("debug", debug) 74 | dispatcher.add_handler(DEBUG_HANDLER) 75 | 76 | __mod_name__ = "Debug" 77 | __command_list__ = ["debug"] 78 | __handlers__ = [DEBUG_HANDLER] 79 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/reporting_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from typing import Union 3 | 4 | from GabiBraunRobot.modules.sql import BASE, SESSION 5 | from sqlalchemy import Boolean, Column, Integer, String 6 | 7 | 8 | class ReportingUserSettings(BASE): 9 | __tablename__ = "user_report_settings" 10 | user_id = Column(Integer, primary_key=True) 11 | should_report = Column(Boolean, default=True) 12 | 13 | def __init__(self, user_id): 14 | self.user_id = user_id 15 | 16 | def __repr__(self): 17 | return "".format(self.user_id) 18 | 19 | 20 | class ReportingChatSettings(BASE): 21 | __tablename__ = "chat_report_settings" 22 | chat_id = Column(String(14), primary_key=True) 23 | should_report = Column(Boolean, default=True) 24 | 25 | def __init__(self, chat_id): 26 | self.chat_id = str(chat_id) 27 | 28 | def __repr__(self): 29 | return "".format(self.chat_id) 30 | 31 | 32 | ReportingUserSettings.__table__.create(checkfirst=True) 33 | ReportingChatSettings.__table__.create(checkfirst=True) 34 | 35 | CHAT_LOCK = threading.RLock() 36 | USER_LOCK = threading.RLock() 37 | 38 | 39 | def chat_should_report(chat_id: Union[str, int]) -> bool: 40 | try: 41 | chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) 42 | if chat_setting: 43 | return chat_setting.should_report 44 | return False 45 | finally: 46 | SESSION.close() 47 | 48 | 49 | def user_should_report(user_id: int) -> bool: 50 | try: 51 | user_setting = SESSION.query(ReportingUserSettings).get(user_id) 52 | if user_setting: 53 | return user_setting.should_report 54 | return True 55 | finally: 56 | SESSION.close() 57 | 58 | 59 | def set_chat_setting(chat_id: Union[int, str], setting: bool): 60 | with CHAT_LOCK: 61 | chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) 62 | if not chat_setting: 63 | chat_setting = ReportingChatSettings(chat_id) 64 | 65 | chat_setting.should_report = setting 66 | SESSION.add(chat_setting) 67 | SESSION.commit() 68 | 69 | 70 | def set_user_setting(user_id: int, setting: bool): 71 | with USER_LOCK: 72 | user_setting = SESSION.query(ReportingUserSettings).get(user_id) 73 | if not user_setting: 74 | user_setting = ReportingUserSettings(user_id) 75 | 76 | user_setting.should_report = setting 77 | SESSION.add(user_setting) 78 | SESSION.commit() 79 | 80 | 81 | def migrate_chat(old_chat_id, new_chat_id): 82 | with CHAT_LOCK: 83 | chat_notes = SESSION.query(ReportingChatSettings).filter( 84 | ReportingChatSettings.chat_id == str(old_chat_id)).all() 85 | for note in chat_notes: 86 | note.chat_id = str(new_chat_id) 87 | SESSION.commit() 88 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/animequotes_strings.py: -------------------------------------------------------------------------------- 1 | QUOTES_IMG = ( 2 | "https://i.imgur.com/Iub4RYj.jpg", 3 | "https://i.imgur.com/uvNMdIl.jpg", 4 | "https://i.imgur.com/YOBOntg.jpg", 5 | "https://i.imgur.com/fFpO2ZQ.jpg", 6 | "https://i.imgur.com/f0xZceK.jpg", 7 | "https://i.imgur.com/RlVcCip.jpg", 8 | "https://i.imgur.com/CjpqLRF.jpg", 9 | "https://i.imgur.com/8BHZDk6.jpg", 10 | "https://i.imgur.com/8bHeMgy.jpg", 11 | "https://i.imgur.com/5K3lMvr.jpg", 12 | "https://i.imgur.com/NTzw4RN.jpg", 13 | "https://i.imgur.com/wJxryAn.jpg", 14 | "https://i.imgur.com/9L0DWzC.jpg", 15 | "https://i.imgur.com/sBe8TTs.jpg", 16 | "https://i.imgur.com/1Au8gdf.jpg", 17 | "https://i.imgur.com/28hFQeU.jpg", 18 | "https://i.imgur.com/Qvc03JY.jpg", 19 | "https://i.imgur.com/gSX6Xlf.jpg", 20 | "https://i.imgur.com/iP26Hwa.jpg", 21 | "https://i.imgur.com/uSsJoX8.jpg", 22 | "https://i.imgur.com/OvX3oHB.jpg", 23 | "https://i.imgur.com/JMWuksm.jpg", 24 | "https://i.imgur.com/lhM3fib.jpg", 25 | "https://i.imgur.com/64IYKkw.jpg", 26 | "https://i.imgur.com/nMbyA3J.jpg", 27 | "https://i.imgur.com/7KFQhY3.jpg", 28 | "https://i.imgur.com/mlKb7zt.jpg", 29 | "https://i.imgur.com/JCQGJVw.jpg", 30 | "https://i.imgur.com/hSFYDEz.jpg", 31 | "https://i.imgur.com/PQRjAgl.jpg", 32 | "https://i.imgur.com/ot9624U.jpg", 33 | "https://i.imgur.com/iXmqN9y.jpg", 34 | "https://i.imgur.com/RhNBeGr.jpg", 35 | "https://i.imgur.com/tcMVNa8.jpg", 36 | "https://i.imgur.com/LrVg810.jpg", 37 | "https://i.imgur.com/TcWfQlz.jpg", 38 | "https://i.imgur.com/muAUdvJ.jpg", 39 | "https://i.imgur.com/AtC7ZRV.jpg", 40 | "https://i.imgur.com/sCObQCQ.jpg", 41 | "https://i.imgur.com/AJFDI1r.jpg", 42 | "https://i.imgur.com/TCgmRrH.jpg", 43 | "https://i.imgur.com/LMdmhJU.jpg", 44 | "https://i.imgur.com/eyyax0N.jpg", 45 | "https://i.imgur.com/YtYxV66.jpg", 46 | "https://i.imgur.com/292w4ye.jpg", 47 | "https://i.imgur.com/6Fm1vdw.jpg", 48 | "https://i.imgur.com/2vnBOZd.jpg", 49 | "https://i.imgur.com/j5hI9Eb.jpg", 50 | "https://i.imgur.com/cAv7pJB.jpg", 51 | "https://i.imgur.com/jvI7Vil.jpg", 52 | "https://i.imgur.com/fANpjsg.jpg", 53 | "https://i.imgur.com/5o1SJyo.jpg", 54 | "https://i.imgur.com/dSVxmh8.jpg", 55 | "https://i.imgur.com/02dXlAD.jpg", 56 | "https://i.imgur.com/htvIoGY.jpg", 57 | "https://i.imgur.com/hy6BXOj.jpg", 58 | "https://i.imgur.com/OuwzNYu.jpg", 59 | "https://i.imgur.com/L8vwvc2.jpg", 60 | "https://i.imgur.com/3VMVF9y.jpg", 61 | "https://i.imgur.com/yzjq2n2.jpg", 62 | "https://i.imgur.com/0qK7TAN.jpg", 63 | "https://i.imgur.com/zvcxSOX.jpg", 64 | "https://i.imgur.com/FO7bApW.jpg", 65 | "https://i.imgur.com/KK06gwg.jpg", 66 | "https://i.imgur.com/6lG4tsO.jpg" 67 | 68 | ) 69 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/purge.py: -------------------------------------------------------------------------------- 1 | import time 2 | from telethon import events 3 | 4 | from GabiBraunRobot import telethn 5 | from GabiBraunRobot.modules.helper_funcs.telethn.chatstatus import ( 6 | can_delete_messages, user_is_admin) 7 | 8 | 9 | async def purge_messages(event): 10 | start = time.perf_counter() 11 | if event.from_id is None: 12 | return 13 | 14 | if not await user_is_admin( 15 | user_id=event.sender_id, message=event) and event.from_id not in [ 16 | 1087968824 17 | ]: 18 | await event.reply("Only Admins are allowed to use this command") 19 | return 20 | 21 | if not await can_delete_messages(message=event): 22 | await event.reply("Can't seem to purge the message") 23 | return 24 | 25 | reply_msg = await event.get_reply_message() 26 | if not reply_msg: 27 | await event.reply( 28 | "Reply to a message to select where to start purging from.") 29 | return 30 | messages = [] 31 | message_id = reply_msg.id 32 | delete_to = event.message.id 33 | 34 | messages.append(event.reply_to_msg_id) 35 | for msg_id in range(message_id, delete_to + 1): 36 | messages.append(msg_id) 37 | if len(messages) == 100: 38 | await event.client.delete_messages(event.chat_id, messages) 39 | messages = [] 40 | 41 | try: 42 | await event.client.delete_messages(event.chat_id, messages) 43 | except: 44 | pass 45 | time_ = time.perf_counter() - start 46 | text = f"Purged Successfully in {time_:0.2f} Second(s)" 47 | await event.respond(text, parse_mode='markdown') 48 | 49 | 50 | async def delete_messages(event): 51 | if event.from_id is None: 52 | return 53 | 54 | if not await user_is_admin( 55 | user_id=event.sender_id, message=event) and event.from_id not in [ 56 | 1087968824 57 | ]: 58 | await event.reply("Only Admins are allowed to use this command") 59 | return 60 | 61 | if not await can_delete_messages(message=event): 62 | await event.reply("Can't seem to delete this?") 63 | return 64 | 65 | message = await event.get_reply_message() 66 | if not message: 67 | await event.reply("Whadya want to delete?") 68 | return 69 | chat = await event.get_input_chat() 70 | del_message = [message, event.message] 71 | await event.client.delete_messages(chat, del_message) 72 | 73 | 74 | __help__ = """ 75 | *Admin only:* 76 | - /del: deletes the message you replied to 77 | - /purge: deletes all messages between this and the replied to message. 78 | - /purge : deletes the replied message, and X messages following it if replied to a message. 79 | """ 80 | 81 | PURGE_HANDLER = purge_messages, events.NewMessage(pattern="^[!/]purge$") 82 | DEL_HANDLER = delete_messages, events.NewMessage(pattern="^[!/]del$") 83 | 84 | telethn.add_event_handler(*PURGE_HANDLER) 85 | telethn.add_event_handler(*DEL_HANDLER) 86 | 87 | __mod_name__ = "Purges" 88 | __command_list__ = ["del", "purge"] 89 | __handlers__ = [PURGE_HANDLER, DEL_HANDLER] 90 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/disable_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, String, UnicodeText, distinct, func 5 | 6 | 7 | class Disable(BASE): 8 | __tablename__ = "disabled_commands" 9 | chat_id = Column(String(14), primary_key=True) 10 | command = Column(UnicodeText, primary_key=True) 11 | 12 | def __init__(self, chat_id, command): 13 | self.chat_id = chat_id 14 | self.command = command 15 | 16 | def __repr__(self): 17 | return "Disabled cmd {} in {}".format(self.command, self.chat_id) 18 | 19 | 20 | Disable.__table__.create(checkfirst=True) 21 | DISABLE_INSERTION_LOCK = threading.RLock() 22 | 23 | DISABLED = {} 24 | 25 | 26 | def disable_command(chat_id, disable): 27 | with DISABLE_INSERTION_LOCK: 28 | disabled = SESSION.query(Disable).get((str(chat_id), disable)) 29 | 30 | if not disabled: 31 | DISABLED.setdefault(str(chat_id), set()).add(disable) 32 | 33 | disabled = Disable(str(chat_id), disable) 34 | SESSION.add(disabled) 35 | SESSION.commit() 36 | return True 37 | 38 | SESSION.close() 39 | return False 40 | 41 | 42 | def enable_command(chat_id, enable): 43 | with DISABLE_INSERTION_LOCK: 44 | disabled = SESSION.query(Disable).get((str(chat_id), enable)) 45 | 46 | if disabled: 47 | if enable in DISABLED.get(str(chat_id)): # sanity check 48 | DISABLED.setdefault(str(chat_id), set()).remove(enable) 49 | 50 | SESSION.delete(disabled) 51 | SESSION.commit() 52 | return True 53 | 54 | SESSION.close() 55 | return False 56 | 57 | 58 | def is_command_disabled(chat_id, cmd): 59 | return str(cmd).lower() in DISABLED.get(str(chat_id), set()) 60 | 61 | 62 | def get_all_disabled(chat_id): 63 | return DISABLED.get(str(chat_id), set()) 64 | 65 | 66 | def num_chats(): 67 | try: 68 | return SESSION.query(func.count(distinct(Disable.chat_id))).scalar() 69 | finally: 70 | SESSION.close() 71 | 72 | 73 | def num_disabled(): 74 | try: 75 | return SESSION.query(Disable).count() 76 | finally: 77 | SESSION.close() 78 | 79 | 80 | def migrate_chat(old_chat_id, new_chat_id): 81 | with DISABLE_INSERTION_LOCK: 82 | chats = SESSION.query(Disable).filter( 83 | 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 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/telethn/chatstatus.py: -------------------------------------------------------------------------------- 1 | from GabiBraunRobot.modules.helper_funcs.telethn import IMMUNE_USERS, telethn 2 | from GabiBraunRobot import DRAGONS 3 | from telethon.tl.types import ChannelParticipantsAdmins 4 | 5 | 6 | async def user_is_ban_protected(user_id: int, message): 7 | status = False 8 | if message.is_private or user_id in (IMMUNE_USERS): 9 | return True 10 | 11 | async for user in telethn.iter_participants( 12 | message.chat_id, filter=ChannelParticipantsAdmins): 13 | if user_id == user.id: 14 | status = True 15 | break 16 | return status 17 | 18 | 19 | async def user_is_admin(user_id: int, message): 20 | status = False 21 | if message.is_private: 22 | return True 23 | 24 | async for user in telethn.iter_participants( 25 | message.chat_id, filter=ChannelParticipantsAdmins): 26 | if user_id == user.id or user_id in DRAGONS: 27 | status = True 28 | break 29 | return status 30 | 31 | 32 | async def is_user_admin(user_id: int, chat_id): 33 | status = False 34 | async for user in telethn.iter_participants( 35 | chat_id, filter=ChannelParticipantsAdmins): 36 | if user_id == user.id or user_id in DRAGONS: 37 | status = True 38 | break 39 | return status 40 | 41 | 42 | async def saitama_is_admin(chat_id: int): 43 | status = False 44 | saitama = await telethn.get_me() 45 | async for user in telethn.iter_participants( 46 | chat_id, filter=ChannelParticipantsAdmins): 47 | if saitama.id == user.id: 48 | status = True 49 | break 50 | return status 51 | 52 | 53 | async def is_user_in_chat(chat_id: int, user_id: int): 54 | status = False 55 | async for user in telethn.iter_participants(chat_id): 56 | if user_id == user.id: 57 | status = True 58 | break 59 | return status 60 | 61 | 62 | async def can_change_info(message): 63 | status = False 64 | if message.chat.admin_rights: 65 | status = message.chat.admin_rights.change_info 66 | return status 67 | 68 | 69 | async def can_ban_users(message): 70 | status = False 71 | if message.chat.admin_rights: 72 | status = message.chat.admin_rights.ban_users 73 | return status 74 | 75 | 76 | async def can_pin_messages(message): 77 | status = False 78 | if message.chat.admin_rights: 79 | status = message.chat.admin_rights.pin_messages 80 | return status 81 | 82 | 83 | async def can_invite_users(message): 84 | status = False 85 | if message.chat.admin_rights: 86 | status = message.chat.admin_rights.invite_users 87 | return status 88 | 89 | 90 | async def can_add_admins(message): 91 | status = False 92 | if message.chat.admin_rights: 93 | status = message.chat.admin_rights.add_admins 94 | return status 95 | 96 | 97 | async def can_delete_messages(message): 98 | 99 | if message.is_private: 100 | return True 101 | elif message.chat.admin_rights: 102 | status = message.chat.admin_rights.delete_messages 103 | return status 104 | else: 105 | return False 106 | -------------------------------------------------------------------------------- /GabiBraunRobot/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 | 6 | def get_user_list(config, key): 7 | with open('{}/SaitamaRobot/{}'.format(os.getcwd(), config), 8 | 'r') as json_file: 9 | return json.load(json_file)[key] 10 | 11 | 12 | # Create a new config.py or rename this to config.py file in same dir and import, then extend this class. 13 | class Config(object): 14 | LOGGER = True 15 | # REQUIRED 16 | #Login to https://my.telegram.org and fill in these slots with the details given by it 17 | 18 | API_ID = 123456 # integer value, dont use "" 19 | API_HASH = "awoo" 20 | TOKEN = "BOT_TOKEN" #This var used to be API_KEY but it is now TOKEN, adjust accordingly. 21 | OWNER_ID = 792109647 # If you dont know, run the bot and do /id in your private chat with it, also an integer 22 | OWNER_USERNAME = "Sawada" 23 | SUPPORT_CHAT = 'OnePunchSupport' #Your own group for support, do not add the @ 24 | JOIN_LOGGER = -1001253661229 #Prints any new group the bot is added to, prints just the name and ID. 25 | EVENT_LOGS = -1001190806654 #Prints information like gbans, sudo promotes, AI enabled disable states that may help in debugging and shit 26 | 27 | #RECOMMENDED 28 | SQLALCHEMY_DATABASE_URI = 'something://somewhat:user@hosturl:port/databasename' # needed for any database modules 29 | REDIS_URI = " " 30 | LOAD = [] 31 | NO_LOAD = ['rss', 'cleaner', 'connection', 'math'] 32 | WEBHOOK = False 33 | INFOPIC = True 34 | URL = None 35 | SPAMWATCH_API = "" # go to support.spamwat.ch to get key 36 | SPAMWATCH_SUPPORT_CHAT = "@SpamWatchSupport" 37 | 38 | #OPTIONAL 39 | ##List of id's - (not usernames) for users which have sudo access to the bot. 40 | DRAGONS = get_user_list('elevated_users.json', 'sudos') 41 | ##List of id's - (not usernames) for developers who will have the same perms as the owner 42 | DEV_USERS = get_user_list('elevated_users.json', 'devs') 43 | ##List of id's (not usernames) for users which are allowed to gban, but can also be banned. 44 | DEMONS = get_user_list('elevated_users.json', 'supports') 45 | #List of id's (not usernames) for users which WONT be banned/kicked by the bot. 46 | TIGERS = get_user_list('elevated_users.json', 'tigers') 47 | WOLVES = get_user_list('elevated_users.json', 'whitelists') 48 | DONATION_LINK = None # EG, paypal 49 | CERT_PATH = None 50 | PORT = 5000 51 | DEL_CMDS = True #Delete commands that users dont have access to, like delete /ban if a non admin uses it. 52 | STRICT_GBAN = True 53 | WORKERS = 8 # Number of subthreads to use. Set as number of threads your processor uses 54 | BAN_STICKER = '' # banhammer marie sticker id, the bot will send this sticker before banning or kicking a user in chat. 55 | ALLOW_EXCL = True # Allow ! commands as well as / (Leave this to true so that blacklist can work) 56 | CASH_API_KEY = 'awoo' # Get your API key from https://www.alphavantage.co/support/#api-key 57 | TIME_API_KEY = 'awoo' # Get your API key from https://timezonedb.com/api 58 | WALL_API = 'awoo' #For wallpapers, get one from https://wall.alphacoders.com/api.php 59 | AI_API_KEY = 'awoo' #For chatbot, get one from https://coffeehouse.intellivoid.net/dashboard 60 | BL_CHATS = [] # List of groups that you want blacklisted. 61 | SPAMMERS = None 62 | 63 | 64 | class Production(Config): 65 | LOGGER = True 66 | 67 | 68 | class Development(Config): 69 | LOGGER = True 70 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/truth_and_dare_string.py: -------------------------------------------------------------------------------- 1 | TRUTH = ( 2 | "Have you ghosted someone?" 3 | "Have you ever walked in on your parents doing 'it'?", 4 | "Who was the last person you liked the most? Why?", 5 | "Have you ever been suspended from school?", 6 | "If you had to choose between going naked or having your thoughts appear in thought bubbles above your head for everyone to read, which would you choose?", 7 | "What’s the one thing you’re afraid to lose?", 8 | "Do you like someone as of the moment?", 9 | "One thing about your best friend you are jealous of?", 10 | "Would you cheat on your boyfriend for a rich guy?", 11 | "What is your biggest turn on?", 12 | "When’s the last time you lied to your parents and why?", 13 | "Describe your ideal partner.", 14 | "What’s the scariest thing you’ve ever done?", 15 | "Have you ever picked your nose and eaten it?", 16 | "When’s the last time you lied to your parents and why?", 17 | "Have you ever lied about your age to participate in a contest?", 18 | "Have you ever been caught checking someone out?", 19 | 20 | ) 21 | 22 | DARE = ( 23 | "Show the most embarrassing photo on your phone" 24 | "Show the last five people you texted and what the messages said", 25 | "Let the rest of the group DM someone from your Instagram account", 26 | "Eat a raw piece of garlic", 27 | "Do 100 squats", 28 | "Keep three ice cubes in your mouth until they melt", 29 | "Say something dirty to the person on your leftYou've got company!", 30 | "Give a foot massage to the person on your right", 31 | "Put 10 different available liquids into a cup and drink it", 32 | "*Yell out the first word that comes to your mind", 33 | "Give a lap dance to someone of your choice", 34 | "Remove four items of clothing", 35 | "Like the first 15 posts on your Facebook newsfeed", 36 | "Eat a spoonful of mustard", 37 | "Keep your eyes closed until it's your go again", 38 | "Send a sext to the last person in your phonebook", 39 | "Show off your orgasm face", 40 | "Seductively eat a banana", 41 | "Empty out your wallet/purse and show everyone what's inside", 42 | "Do your best sexy crawl", 43 | "Pretend to be the person to your right for 10 minutes", 44 | "Eat a snack without using your hands", 45 | "Say two honest things about everyone else in the group", 46 | "Twerk for a minute", 47 | "Try and make the group laugh as quickly as possible", 48 | "Try to put your whole fist in your mouth", 49 | "Tell everyone an embarrassing story about yourself", 50 | "Try to lick your elbow", 51 | "Post the oldest selfie on your phone on Instagram Stories", 52 | "Tell the saddest story you know", 53 | "Howl like a wolf for two minutes", 54 | "Dance without music for two minutes", 55 | "Pole dance with an imaginary pole", 56 | "Let someone else tickle you and try not to laugh", 57 | "Put as many snacks into your mouth at once as you can", 58 | "Send your most recent selfie.", 59 | "Send your ugliest selfie.", 60 | "Send a screenshot of your facebook search history", 61 | "Send a screenshot of your gallery.", 62 | "Send a screenshot of your messenger inbox", 63 | "Tell something very intimate.", 64 | "Send a screenshot of your twitter inbox", 65 | "Send a screenshot of your homescreen.", 66 | "Send a cover of your favorite song. 🎤", 67 | "Do a lyric prank on someone and send proof.", 68 | "Confess to your current crush. ❤️", 69 | "Declare who is your true love.", 70 | "Send a screenshot of your gallery.", 71 | "Set your crush’s picture as your dp.", 72 | "Suggest me more dares.", 73 | ) 74 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/reactions.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from GabiBraunRobot import dispatcher 4 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 5 | from telegram import Update 6 | from telegram.ext import CallbackContext, run_async 7 | 8 | reactions = [ 9 | "( ͡° ͜ʖ ͡°)", "( . •́ _ʖ •̀ .)", "( ಠ ͜ʖ ಠ)", "( ͡ ͜ʖ ͡ )", "(ʘ ͜ʖ ʘ)", 10 | "ヾ(´〇`)ノ♪♪♪", "ヽ(o´∀`)ノ♪♬", "♪♬((d⌒ω⌒b))♬♪", "└(^^)┐", "( ̄▽ ̄)/♫•*¨*•.¸¸♪", 11 | "ヾ(⌐■_■)ノ♪", "乁( • ω •乁)", "♬♫♪◖(● o ●)◗♪♫♬", "(っ˘ڡ˘ς)", "( ˘▽˘)っ♨", 12 | "( ・ω・)⊃-[二二]", "(*´ー`)旦 旦( ̄ω ̄*)", "(  ̄▽ ̄)[] [](≧▽≦ )", "(* ̄▽ ̄)旦 且(´∀`*)", 13 | "(ノ ˘_˘)ノ ζ|||ζ ζ|||ζ ζ|||ζ", "(ノ°∀°)ノ⌒・*:.。. .。.:*・゜゚・*☆", 14 | "(⊃。•́‿•̀。)⊃━✿✿✿✿✿✿", "(∩` ロ ´)⊃━炎炎炎炎炎", "( ・∀・)・・・--------☆", 15 | "( -ω-)/占~~~~~", "○∞∞∞∞ヽ(^ー^ )", "(*^^)/~~~~~~~~~~◎", "(((  ̄□)_/", 16 | "(メ ̄▽ ̄)︻┳═一", "ヽ( ・∀・)ノ_θ彡☆Σ(ノ `Д´)ノ", "(*`0´)θ☆(メ°皿°)ノ", 17 | "(; -_-)――――――C<―_-)", "ヽ(>_<ヽ) ―⊂|=0ヘ(^‿^ )", "(҂` ロ ´)︻デ═一 \(º □ º l|l)/", 18 | "/( .□.)\ ︵╰(°益°)╯︵ /(.□. /)", "(`⌒*)O-(`⌒´Q)", "(っ•﹏•)っ ✴==≡눈٩(`皿´҂)ง", 19 | "ヾ(・ω・)メ(・ω・)ノ", "(*^ω^)八(⌒▽⌒)八(-‿‿- )ヽ", "ヽ( ⌒ω⌒)人(=^‥^= )ノ", 20 | "。*:☆(・ω・人・ω・)。:゜☆。", "(°(°ω(°ω°(☆ω☆)°ω°)ω°)°)", "(っ˘▽˘)(˘▽˘)˘▽˘ς)", 21 | "(*^ω^)人(^ω^*)", "\(▽ ̄ \ ( ̄▽ ̄) /  ̄▽)/", "( ̄Θ ̄)", "\( ˋ Θ ´ )/", 22 | "( ´(00)ˋ )", "\( ̄(oo) ̄)/", "/(≧ x ≦)\", "/(=・ x ・=)\", "(=^・ω・^=)", 23 | "(= ; ェ ; =)", "(=⌒‿‿⌒=)", "(^• ω •^)", "ଲ(ⓛ ω ⓛ)ଲ", "ଲ(ⓛ ω ⓛ)ଲ", "(^◔ᴥ◔^)", 24 | "[(--)]..zzZ", "( ̄o ̄) zzZZzzZZ", "(_ _*) Z z z", "☆ミ(o*・ω・)ノ", 25 | "ε=ε=ε=ε=┌(; ̄▽ ̄)┘", "ε===(っ≧ω≦)っ", "__φ(..)", "ヾ( `ー´)シφ__", "( ^▽^)ψ__", 26 | "|・ω・)", "|д・)", "┬┴┬┴┤・ω・)ノ", "|・д・)ノ", "(* ̄ii ̄)", "(^〃^)", "m(_ _)m", 27 | "人(_ _*)", "(シ. .)シ", "(^_~)", "(>ω^)", "(^_<)〜☆", "(^_<)", "(づ ̄ ³ ̄)づ", 28 | "(⊃。•́‿•̀。)⊃", "⊂(´• ω •`⊂)", "(*・ω・)ノ", "(^-^*)/", "ヾ(*'▽'*)", "(^0^)ノ", 29 | "(*°ー°)ノ", "( ̄ω ̄)/", "(≧▽≦)/", "w(°o°)w", "(⊙_⊙)", "(°ロ°) !", "∑(O_O;)", 30 | "(¬_¬)", "(¬_¬ )", "(↼_↼)", "( ̄ω ̄;)", "┐('~`;)┌", "(・_・;)", "(@_@)", 31 | "(•ิ_•ิ)?", "ヽ(ー_ー )ノ", "┐( ̄ヘ ̄)┌", "┐( ̄~ ̄)┌", "┐( ´ д ` )┌", "╮(︶▽︶)╭", 32 | "ᕕ( ᐛ )ᕗ", "(ノωヽ)", "(″ロ゛)", "(/ω\)", "(((><)))", "~(>_<~)", "(×_×)", 33 | "(×﹏×)", "(ノ_<。)", "(μ_μ)", "o(TヘTo)", "( ゚,_ゝ`)", "( ╥ω╥ )", "(/ˍ・、)", 34 | "(つω`。)", "(T_T)", "o(〒﹏〒)o", "(#`Д´)", "(・`ω´・)", "( `ε´ )", "(メ` ロ ´)", 35 | "Σ(▼□▼メ)", "(҂ `з´ )", "٩(╬ʘ益ʘ╬)۶", "↑_(ΦwΦ)Ψ", "(ノಥ益ಥ)ノ", "(#><)", 36 | "(; ̄Д ̄)", "(¬_¬;)", "(^^#)", "( ̄︿ ̄)", "ヾ(  ̄O ̄)ツ", "(ᗒᗣᗕ)՞", 37 | "(ノ_<。)ヾ(´ ▽ ` )", "ヽ( ̄ω ̄(。。 )ゝ", "(ノ_;)ヾ(´ ∀ ` )", "(´-ω-`( _ _ )", 38 | "(⌒_⌒;)", "(*/_\)", "( ◡‿◡ *)", "(//ω//)", "( ̄▽ ̄*)ゞ", "(„ಡωಡ„)", 39 | "(ノ´ з `)ノ", "(♡-_-♡)", "(─‿‿─)♡", "(´ ω `♡)", "(ღ˘⌣˘ღ)", "(´• ω •`) ♡", 40 | "╰(*´︶`*)╯♡", "(≧◡≦) ♡", "♡ (˘▽˘>ԅ( ˘⌣˘)", "σ(≧ε≦σ) ♡", "(˘∀˘)/(μ‿μ) ❤", 41 | "Σ>―(〃°ω°〃)♡→", "(* ^ ω ^)", "(o^▽^o)", "ヽ(・∀・)ノ", "(o・ω・o)", "(^人^)", 42 | "( ´ ω ` )", "(´• ω •`)", "╰(▔∀▔)╯", "(✯◡✯)", "(⌒‿⌒)", "(*°▽°*)", 43 | "(´。• ᵕ •。`)", "ヽ(>∀<☆)ノ", "\( ̄▽ ̄)/", "(o˘◡˘o)", "(╯✧▽✧)╯", "( ‾́ ◡ ‾́ )", 44 | "(๑˘︶˘๑)", "(´・ᴗ・ ` )", "( ͡° ʖ̯ ͡°)", "( ఠ ͟ʖ ఠ)", "( ಥ ʖ̯ ಥ)", "(≖ ͜ʖ≖)", 45 | "ヘ( ̄ω ̄ヘ)", "(ノ≧∀≦)ノ", "└( ̄- ̄└))", "┌(^^)┘", "(^_^♪)", "(〜 ̄△ ̄)〜", 46 | "(「• ω •)「", "( ˘ ɜ˘) ♬♪♫", "( o˘◡˘o) ┌iii┐", "♨o(>_<)o♨", 47 | "( ・・)つ―{}@{}@{}-", "(*´з`)口゚。゚口(・∀・ )", "( *^^)o∀*∀o(^^* )", "-●●●-c(・・ )", 48 | "(ノ≧∀≦)ノ ‥…━━━★", "╰( ͡° ͜ʖ ͡° )つ──☆*:・゚", "(∩ᄑ_ᄑ)⊃━☆゚*・。*・:≡( ε:)" 49 | ] 50 | 51 | 52 | @run_async 53 | def react(update: Update, context: CallbackContext): 54 | message = update.effective_message 55 | react = random.choice(reactions) 56 | if message.reply_to_message: 57 | message.reply_to_message.reply_text(react) 58 | else: 59 | message.reply_text(react) 60 | 61 | 62 | REACT_HANDLER = DisableAbleCommandHandler("react", react) 63 | 64 | dispatcher.add_handler(REACT_HANDLER) 65 | 66 | __command_list__ = ["react"] 67 | __handlers__ = [REACT_HANDLER] 68 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/ping.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import List 3 | 4 | import requests 5 | from telegram import ParseMode, Update 6 | from telegram.ext import CallbackContext, run_async 7 | 8 | from GabiBraunRobot import StartTime, dispatcher 9 | from GabiBraunRobot.modules.helper_funcs.chat_status import sudo_plus 10 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 11 | 12 | sites_list = { 13 | "Telegram": "https://api.telegram.org", 14 | "Kaizoku": "https://animekaizoku.com", 15 | "Kayo": "https://animekayo.com", 16 | "Jikan": "https://api.jikan.moe/v3" 17 | } 18 | 19 | 20 | def get_readable_time(seconds: int) -> str: 21 | count = 0 22 | ping_time = "" 23 | time_list = [] 24 | time_suffix_list = ["s", "m", "h", "days"] 25 | 26 | while count < 4: 27 | count += 1 28 | if count < 3: 29 | remainder, result = divmod(seconds, 60) 30 | else: 31 | remainder, result = divmod(seconds, 24) 32 | if seconds == 0 and remainder == 0: 33 | break 34 | time_list.append(int(result)) 35 | seconds = int(remainder) 36 | 37 | for x in range(len(time_list)): 38 | time_list[x] = str(time_list[x]) + time_suffix_list[x] 39 | if len(time_list) == 4: 40 | ping_time += time_list.pop() + ", " 41 | 42 | time_list.reverse() 43 | ping_time += ":".join(time_list) 44 | 45 | return ping_time 46 | 47 | 48 | def ping_func(to_ping: List[str]) -> List[str]: 49 | ping_result = [] 50 | 51 | for each_ping in to_ping: 52 | 53 | start_time = time.time() 54 | site_to_ping = sites_list[each_ping] 55 | r = requests.get(site_to_ping) 56 | end_time = time.time() 57 | ping_time = str(round((end_time - start_time), 2)) + "s" 58 | 59 | pinged_site = f"{each_ping}" 60 | 61 | if each_ping == "Kaizoku" or each_ping == "Kayo": 62 | pinged_site = f'{each_ping}' 63 | ping_time = f"{ping_time} (Status: {r.status_code})" 64 | 65 | ping_text = f"{pinged_site}: {ping_time}" 66 | ping_result.append(ping_text) 67 | 68 | return ping_result 69 | 70 | 71 | @run_async 72 | @sudo_plus 73 | def ping(update: Update, context: CallbackContext): 74 | msg = update.effective_message 75 | 76 | start_time = time.time() 77 | message = msg.reply_text("Pinging...") 78 | end_time = time.time() 79 | telegram_ping = str(round((end_time - start_time) * 1000, 3)) + " ms" 80 | uptime = get_readable_time((time.time() - StartTime)) 81 | 82 | message.edit_text( 83 | "PONG!!\n" 84 | "Time Taken: {}\n" 85 | "Service uptime: {}".format(telegram_ping, uptime), 86 | parse_mode=ParseMode.HTML) 87 | 88 | 89 | @run_async 90 | @sudo_plus 91 | def pingall(update: Update, context: CallbackContext): 92 | to_ping = ["Kaizoku", "Kayo", "Telegram", "Jikan"] 93 | pinged_list = ping_func(to_ping) 94 | pinged_list.insert(2, '') 95 | uptime = get_readable_time((time.time() - StartTime)) 96 | 97 | reply_msg = "⏱Ping results are:\n" 98 | reply_msg += "\n".join(pinged_list) 99 | reply_msg += '\nService uptime: {}'.format(uptime) 100 | 101 | update.effective_message.reply_text( 102 | reply_msg, parse_mode=ParseMode.HTML, disable_web_page_preview=True) 103 | 104 | 105 | PING_HANDLER = DisableAbleCommandHandler("ping", ping) 106 | PINGALL_HANDLER = DisableAbleCommandHandler("pingall", pingall) 107 | 108 | dispatcher.add_handler(PING_HANDLER) 109 | dispatcher.add_handler(PINGALL_HANDLER) 110 | 111 | __command_list__ = ["ping", "pingall"] 112 | __handlers__ = [PING_HANDLER, PINGALL_HANDLER] 113 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/Zombies.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from asyncio import sleep 3 | 4 | from telethon import events 5 | from telethon.errors import ChatAdminRequiredError, UserAdminInvalidError 6 | from telethon.tl.functions.channels import EditBannedRequest 7 | from telethon.tl.types import ChatBannedRights, ChannelParticipantsAdmins 8 | 9 | from GabiBraunRobot import telethn, OWNER_ID, DEV_USERS, DRAGONS, DEMONS 10 | 11 | # =================== CONSTANT =================== 12 | 13 | BANNED_RIGHTS = ChatBannedRights( 14 | until_date=None, 15 | view_messages=True, 16 | send_messages=True, 17 | send_media=True, 18 | send_stickers=True, 19 | send_gifs=True, 20 | send_games=True, 21 | send_inline=True, 22 | embed_links=True, 23 | ) 24 | 25 | 26 | UNBAN_RIGHTS = ChatBannedRights( 27 | until_date=None, 28 | send_messages=None, 29 | send_media=None, 30 | send_stickers=None, 31 | send_gifs=None, 32 | send_games=None, 33 | send_inline=None, 34 | embed_links=None, 35 | ) 36 | 37 | OFFICERS = [OWNER_ID] + DEV_USERS + DRAGONS + DEMONS 38 | 39 | # Check if user has admin rights 40 | async def is_administrator(user_id: int, message): 41 | admin = False 42 | async for user in telethn.iter_participants( 43 | message.chat_id, filter=ChannelParticipantsAdmins 44 | ): 45 | if user_id == user.id or user_id in OFFICERS: 46 | admin = True 47 | break 48 | return admin 49 | 50 | 51 | 52 | @telethn.on(events.NewMessage(pattern=f"^[!/]zombies ?(.*)")) 53 | async def zombies(event): 54 | """ For .zombies command, list all the zombies in a chat. """ 55 | 56 | con = event.pattern_match.group(1).lower() 57 | del_u = 0 58 | del_status = "No Deleted Accounts Found, Group Is Clean." 59 | 60 | if con != "clean": 61 | find_zombies = await event.respond("Searching For Zombies...") 62 | async for user in event.client.iter_participants(event.chat_id): 63 | 64 | if user.deleted: 65 | del_u += 1 66 | await sleep(1) 67 | if del_u > 0: 68 | del_status = f"Found **{del_u}** Zombies In This Group.\ 69 | \nClean Them By Using - `/zombies clean`" 70 | await find_zombies.edit(del_status) 71 | return 72 | 73 | # Here laying the sanity check 74 | chat = await event.get_chat() 75 | admin = chat.admin_rights 76 | creator = chat.creator 77 | 78 | # Well 79 | if not await is_administrator(user_id=event.from_id, message=event): 80 | await event.respond("You're Not An Admin!") 81 | return 82 | 83 | if not admin and not creator: 84 | await event.respond("I Am Not An Admin Here!") 85 | return 86 | 87 | cleaning_zombies = await event.respond("Cleaning Zombies...") 88 | del_u = 0 89 | del_a = 0 90 | 91 | async for user in event.client.iter_participants(event.chat_id): 92 | if user.deleted: 93 | try: 94 | await event.client( 95 | EditBannedRequest(event.chat_id, user.id, BANNED_RIGHTS) 96 | ) 97 | except ChatAdminRequiredError: 98 | await cleaning_zombies.edit("I Don't Have Ban Rights In This Group.") 99 | return 100 | except UserAdminInvalidError: 101 | del_u -= 1 102 | del_a += 1 103 | await event.client(EditBannedRequest(event.chat_id, user.id, UNBAN_RIGHTS)) 104 | del_u += 1 105 | 106 | if del_u > 0: 107 | del_status = f"Cleaned `{del_u}` Zombies" 108 | 109 | if del_a > 0: 110 | del_status = f"Cleaned `{del_u}` Zombies \ 111 | \n`{del_a}` Zombie Admin Accounts Are Not Removed!" 112 | 113 | await cleaning_zombies.edit(del_status) 114 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/gettime.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List 3 | 4 | import requests 5 | from GabiBraunRobot import TIME_API_KEY, dispatcher 6 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 7 | from telegram import ParseMode, Update 8 | from telegram.ext import CallbackContext, run_async 9 | 10 | 11 | def generate_time(to_find: str, findtype: List[str]) -> str: 12 | data = requests.get( 13 | f"https://api.timezonedb.com/v2.1/list-time-zone" 14 | f"?key={TIME_API_KEY}" 15 | f"&format=json" 16 | f"&fields=countryCode,countryName,zoneName,gmtOffset,timestamp,dst" 17 | ).json() 18 | 19 | for zone in data["zones"]: 20 | for eachtype in findtype: 21 | if to_find in zone[eachtype].lower(): 22 | country_name = zone['countryName'] 23 | country_zone = zone['zoneName'] 24 | country_code = zone['countryCode'] 25 | 26 | if zone['dst'] == 1: 27 | daylight_saving = "Yes" 28 | else: 29 | daylight_saving = "No" 30 | 31 | date_fmt = r"%d-%m-%Y" 32 | time_fmt = r"%H:%M:%S" 33 | day_fmt = r"%A" 34 | gmt_offset = zone['gmtOffset'] 35 | timestamp = datetime.datetime.now( 36 | datetime.timezone.utc) + datetime.timedelta( 37 | seconds=gmt_offset) 38 | current_date = timestamp.strftime(date_fmt) 39 | current_time = timestamp.strftime(time_fmt) 40 | current_day = timestamp.strftime(day_fmt) 41 | 42 | break 43 | 44 | try: 45 | result = ( 46 | f'Country: {country_name}\n' 47 | f'Zone Name: {country_zone}\n' 48 | f'Country Code: {country_code}\n' 49 | f'Daylight saving: {daylight_saving}\n' 50 | f'Day: {current_day}\n' 51 | f'Current Time: {current_time}\n' 52 | f'Current Date: {current_date}\n' 53 | 'Timezones: List here' 54 | ) 55 | except: 56 | result = None 57 | 58 | return result 59 | 60 | 61 | @run_async 62 | def gettime(update: Update, context: CallbackContext): 63 | message = update.effective_message 64 | 65 | try: 66 | query = message.text.strip().split(" ", 1)[1] 67 | except: 68 | message.reply_text( 69 | "Provide a country name/abbreviation/timezone to find.") 70 | return 71 | send_message = message.reply_text( 72 | f"Finding timezone info for {query}", parse_mode=ParseMode.HTML) 73 | 74 | query_timezone = query.lower() 75 | if len(query_timezone) == 2: 76 | result = generate_time(query_timezone, ["countryCode"]) 77 | else: 78 | result = generate_time(query_timezone, ["zoneName", "countryName"]) 79 | 80 | if not result: 81 | send_message.edit_text( 82 | f'Timezone info not available for {query}\n' 83 | 'All Timezones: List here', 84 | parse_mode=ParseMode.HTML, 85 | disable_web_page_preview=True) 86 | return 87 | 88 | send_message.edit_text( 89 | result, parse_mode=ParseMode.HTML, disable_web_page_preview=True) 90 | 91 | 92 | __help__ = """ 93 | • `/time `*:* Gives information about a timezone. 94 | 95 | *Available queries:* Country Code/Country Name/Timezone Name 96 | • 🕐 [Timezones list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) 97 | """ 98 | 99 | TIME_HANDLER = DisableAbleCommandHandler("time", gettime) 100 | 101 | dispatcher.add_handler(TIME_HANDLER) 102 | 103 | __mod_name__ = "Time" 104 | __command_list__ = ["time"] 105 | __handlers__ = [TIME_HANDLER] 106 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/eval.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | # Common imports for eval 4 | import textwrap 5 | import traceback 6 | from contextlib import redirect_stdout 7 | 8 | from GabiBraunRobot import LOGGER, dispatcher 9 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus 10 | from telegram import ParseMode, Update 11 | from telegram.ext import CallbackContext, CommandHandler, run_async 12 | 13 | namespaces = {} 14 | 15 | 16 | def namespace_of(chat, update, bot): 17 | if chat not in namespaces: 18 | namespaces[chat] = { 19 | '__builtins__': globals()['__builtins__'], 20 | 'bot': bot, 21 | 'effective_message': update.effective_message, 22 | 'effective_user': update.effective_user, 23 | 'effective_chat': update.effective_chat, 24 | 'update': update 25 | } 26 | 27 | return namespaces[chat] 28 | 29 | 30 | def log_input(update): 31 | user = update.effective_user.id 32 | chat = update.effective_chat.id 33 | LOGGER.info( 34 | f"IN: {update.effective_message.text} (user={user}, chat={chat})") 35 | 36 | 37 | def send(msg, bot, update): 38 | if len(str(msg)) > 2000: 39 | with io.BytesIO(str.encode(msg)) as out_file: 40 | out_file.name = "output.txt" 41 | bot.send_document( 42 | chat_id=update.effective_chat.id, document=out_file) 43 | else: 44 | LOGGER.info(f"OUT: '{msg}'") 45 | bot.send_message( 46 | chat_id=update.effective_chat.id, 47 | text=f"`{msg}`", 48 | parse_mode=ParseMode.MARKDOWN) 49 | 50 | 51 | @dev_plus 52 | @run_async 53 | def evaluate(update: Update, context: CallbackContext): 54 | bot = context.bot 55 | send(do(eval, bot, update), bot, update) 56 | 57 | 58 | @dev_plus 59 | @run_async 60 | def execute(update: Update, context: CallbackContext): 61 | bot = context.bot 62 | send(do(exec, bot, update), bot, update) 63 | 64 | 65 | def cleanup_code(code): 66 | if code.startswith('```') and code.endswith('```'): 67 | return '\n'.join(code.split('\n')[1:-1]) 68 | return code.strip('` \n') 69 | 70 | 71 | def do(func, bot, update): 72 | log_input(update) 73 | content = update.message.text.split(' ', 1)[-1] 74 | body = cleanup_code(content) 75 | env = namespace_of(update.message.chat_id, update, bot) 76 | 77 | os.chdir(os.getcwd()) 78 | with open( 79 | os.path.join(os.getcwd(), 80 | 'GabiBraunRobot/modules/helper_funcs/temp.txt'), 81 | 'w') as temp: 82 | temp.write(body) 83 | 84 | stdout = io.StringIO() 85 | 86 | to_compile = f'def func():\n{textwrap.indent(body, " ")}' 87 | 88 | try: 89 | exec(to_compile, env) 90 | except Exception as e: 91 | return f'{e.__class__.__name__}: {e}' 92 | 93 | func = env['func'] 94 | 95 | try: 96 | with redirect_stdout(stdout): 97 | func_return = func() 98 | except Exception as e: 99 | value = stdout.getvalue() 100 | return f'{value}{traceback.format_exc()}' 101 | else: 102 | value = stdout.getvalue() 103 | result = None 104 | if func_return is None: 105 | if value: 106 | result = f'{value}' 107 | else: 108 | try: 109 | result = f'{repr(eval(body, env))}' 110 | except: 111 | pass 112 | else: 113 | result = f'{value}{func_return}' 114 | if result: 115 | return result 116 | 117 | 118 | @dev_plus 119 | @run_async 120 | def clear(update: Update, context: CallbackContext): 121 | bot = context.bot 122 | log_input(update) 123 | global namespaces 124 | if update.message.chat_id in namespaces: 125 | del namespaces[update.message.chat_id] 126 | send("Cleared locals.", bot, update) 127 | 128 | 129 | EVAL_HANDLER = CommandHandler(('e', 'ev', 'eva', 'eval'), evaluate) 130 | EXEC_HANDLER = CommandHandler(('x', 'ex', 'exe', 'exec', 'py'), execute) 131 | CLEAR_HANDLER = CommandHandler('clearlocals', clear) 132 | 133 | dispatcher.add_handler(EVAL_HANDLER) 134 | dispatcher.add_handler(EXEC_HANDLER) 135 | dispatcher.add_handler(CLEAR_HANDLER) 136 | 137 | __mod_name__ = "Eval Module" 138 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/gtranslator.py: -------------------------------------------------------------------------------- 1 | from emoji import UNICODE_EMOJI 2 | from google_trans_new import LANGUAGES, google_translator 3 | from telegram import ParseMode, Update 4 | from telegram.ext import CallbackContext, run_async 5 | 6 | from GabiBraunRobot import dispatcher 7 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 8 | 9 | 10 | @run_async 11 | def totranslate(update: Update, context: CallbackContext): 12 | message = update.effective_message 13 | problem_lang_code = [] 14 | for key in LANGUAGES: 15 | if "-" in key: 16 | problem_lang_code.append(key) 17 | 18 | try: 19 | if message.reply_to_message: 20 | args = update.effective_message.text.split(None, 1) 21 | if message.reply_to_message.text: 22 | text = message.reply_to_message.text 23 | elif message.reply_to_message.caption: 24 | text = message.reply_to_message.caption 25 | 26 | try: 27 | source_lang = args[1].split(None, 1)[0] 28 | except (IndexError, AttributeError): 29 | source_lang = "en" 30 | 31 | else: 32 | args = update.effective_message.text.split(None, 2) 33 | text = args[2] 34 | source_lang = args[1] 35 | 36 | if source_lang.count('-') == 2: 37 | for lang in problem_lang_code: 38 | if lang in source_lang: 39 | if source_lang.startswith(lang): 40 | dest_lang = source_lang.rsplit("-", 1)[1] 41 | source_lang = source_lang.rsplit("-", 1)[0] 42 | else: 43 | dest_lang = source_lang.split("-", 1)[1] 44 | source_lang = source_lang.split("-", 1)[0] 45 | elif source_lang.count('-') == 1: 46 | for lang in problem_lang_code: 47 | if lang in source_lang: 48 | dest_lang = source_lang 49 | source_lang = None 50 | break 51 | if dest_lang is None: 52 | dest_lang = source_lang.split("-")[1] 53 | source_lang = source_lang.split("-")[0] 54 | else: 55 | dest_lang = source_lang 56 | source_lang = None 57 | 58 | exclude_list = UNICODE_EMOJI.keys() 59 | for emoji in exclude_list: 60 | if emoji in text: 61 | text = text.replace(emoji, '') 62 | 63 | trl = google_translator() 64 | if source_lang is None: 65 | detection = trl.detect(text) 66 | trans_str = trl.translate(text, lang_tgt=dest_lang) 67 | return message.reply_text( 68 | f"Translated from `{detection[0]}` to `{dest_lang}`:\n`{trans_str}`", 69 | parse_mode=ParseMode.MARKDOWN) 70 | else: 71 | trans_str = trl.translate( 72 | text, lang_tgt=dest_lang, lang_src=source_lang) 73 | message.reply_text( 74 | f"Translated from `{source_lang}` to `{dest_lang}`:\n`{trans_str}`", 75 | parse_mode=ParseMode.MARKDOWN) 76 | 77 | except IndexError: 78 | update.effective_message.reply_text( 79 | "Reply to messages or write messages from other languages ​​for translating into the intended language\n\n" 80 | "Example: `/tr en-ml` to translate from English to Malayalam\n" 81 | "Or use: `/tr ml` for automatic detection and translating it into Malayalam.\n" 82 | "See [List of Language Codes](t.me/OnePunchSupport/12823) for a list of language codes.", 83 | parse_mode="markdown", 84 | disable_web_page_preview=True) 85 | except ValueError: 86 | update.effective_message.reply_text( 87 | "The intended language is not found!") 88 | else: 89 | return 90 | 91 | 92 | __help__ = """ 93 | • `/tr` or `/tl` (language code) as reply to a long message 94 | *Example:* 95 | `/tr en`*:* translates something to english 96 | `/tr hi-en`*:* translates hindi to english 97 | """ 98 | 99 | TRANSLATE_HANDLER = DisableAbleCommandHandler(["tr", "tl"], totranslate) 100 | 101 | dispatcher.add_handler(TRANSLATE_HANDLER) 102 | 103 | __mod_name__ = "Translator" 104 | __command_list__ = ["tr", "tl"] 105 | __handlers__ = [TRANSLATE_HANDLER] 106 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/antiflood_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import String, Column, Integer, UnicodeText 4 | 5 | from GabiBraunRobot.modules.sql import SESSION, BASE 6 | DEF_COUNT = 1 7 | DEF_LIMIT = 0 8 | DEF_OBJ = (None, DEF_COUNT, DEF_LIMIT) 9 | 10 | 11 | class FloodControl(BASE): 12 | __tablename__ = "antiflood" 13 | chat_id = Column(String(14), primary_key=True) 14 | user_id = Column(Integer) 15 | count = Column(Integer, default=DEF_COUNT) 16 | limit = Column(Integer, default=DEF_LIMIT) 17 | 18 | def __init__(self, chat_id): 19 | self.chat_id = str(chat_id) # ensure string 20 | 21 | def __repr__(self): 22 | return "" % self.chat_id 23 | 24 | 25 | class FloodSettings(BASE): 26 | __tablename__ = "antiflood_settings" 27 | chat_id = Column(String(14), primary_key=True) 28 | flood_type = Column(Integer, default=1) 29 | value = Column(UnicodeText, default="0") 30 | 31 | def __init__(self, chat_id, flood_type=1, value="0"): 32 | self.chat_id = str(chat_id) 33 | self.flood_type = flood_type 34 | self.value = value 35 | 36 | def __repr__(self): 37 | return "<{} will executing {} for flood.>".format( 38 | self.chat_id, self.flood_type) 39 | 40 | 41 | FloodControl.__table__.create(checkfirst=True) 42 | FloodSettings.__table__.create(checkfirst=True) 43 | 44 | INSERTION_FLOOD_LOCK = threading.RLock() 45 | INSERTION_FLOOD_SETTINGS_LOCK = threading.RLock() 46 | 47 | CHAT_FLOOD = {} 48 | 49 | 50 | def set_flood(chat_id, amount): 51 | with INSERTION_FLOOD_LOCK: 52 | flood = SESSION.query(FloodControl).get(str(chat_id)) 53 | if not flood: 54 | flood = FloodControl(str(chat_id)) 55 | 56 | flood.user_id = None 57 | flood.limit = amount 58 | 59 | CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, amount) 60 | 61 | SESSION.add(flood) 62 | SESSION.commit() 63 | 64 | 65 | def update_flood(chat_id: str, user_id) -> bool: 66 | if str(chat_id) in CHAT_FLOOD: 67 | curr_user_id, count, limit = CHAT_FLOOD.get(str(chat_id), DEF_OBJ) 68 | 69 | if limit == 0: # no antiflood 70 | return False 71 | 72 | if user_id != curr_user_id or user_id is None: # other user 73 | CHAT_FLOOD[str(chat_id)] = (user_id, DEF_COUNT, limit) 74 | return False 75 | 76 | count += 1 77 | if count > limit: # too many msgs, kick 78 | CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, limit) 79 | return True 80 | 81 | # default -> update 82 | CHAT_FLOOD[str(chat_id)] = (user_id, count, limit) 83 | return False 84 | 85 | 86 | def get_flood_limit(chat_id): 87 | return CHAT_FLOOD.get(str(chat_id), DEF_OBJ)[2] 88 | 89 | 90 | def set_flood_strength(chat_id, flood_type, value): 91 | # for flood_type 92 | # 1 = ban 93 | # 2 = kick 94 | # 3 = mute 95 | # 4 = tban 96 | # 5 = tmute 97 | with INSERTION_FLOOD_SETTINGS_LOCK: 98 | curr_setting = SESSION.query(FloodSettings).get(str(chat_id)) 99 | if not curr_setting: 100 | curr_setting = FloodSettings( 101 | chat_id, flood_type=int(flood_type), value=value) 102 | 103 | curr_setting.flood_type = int(flood_type) 104 | curr_setting.value = str(value) 105 | 106 | SESSION.add(curr_setting) 107 | SESSION.commit() 108 | 109 | 110 | def get_flood_setting(chat_id): 111 | try: 112 | setting = SESSION.query(FloodSettings).get(str(chat_id)) 113 | if setting: 114 | return setting.flood_type, setting.value 115 | else: 116 | return 1, "0" 117 | 118 | finally: 119 | SESSION.close() 120 | 121 | 122 | def migrate_chat(old_chat_id, new_chat_id): 123 | with INSERTION_FLOOD_LOCK: 124 | flood = SESSION.query(FloodControl).get(str(old_chat_id)) 125 | if flood: 126 | CHAT_FLOOD[str(new_chat_id)] = CHAT_FLOOD.get( 127 | str(old_chat_id), DEF_OBJ) 128 | flood.chat_id = str(new_chat_id) 129 | SESSION.commit() 130 | 131 | SESSION.close() 132 | 133 | 134 | def __load_flood_settings(): 135 | global CHAT_FLOOD 136 | try: 137 | all_chats = SESSION.query(FloodControl).all() 138 | CHAT_FLOOD = { 139 | chat.chat_id: (None, DEF_COUNT, chat.limit) for chat in all_chats 140 | } 141 | finally: 142 | SESSION.close() 143 | 144 | 145 | __load_flood_settings() 146 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/misc.py: -------------------------------------------------------------------------------- 1 | from math import ceil 2 | from typing import Dict, List 3 | 4 | from GabiBraunRobot import NO_LOAD 5 | from telegram import MAX_MESSAGE_LENGTH, Bot, InlineKeyboardButton, ParseMode 6 | from telegram.error import TelegramError 7 | 8 | 9 | class EqInlineKeyboardButton(InlineKeyboardButton): 10 | def __eq__(self, other): 11 | return self.text == other.text 12 | 13 | def __lt__(self, other): 14 | return self.text < other.text 15 | 16 | def __gt__(self, other): 17 | return self.text > other.text 18 | 19 | 20 | def split_message(msg: str) -> List[str]: 21 | if len(msg) < MAX_MESSAGE_LENGTH: 22 | return [msg] 23 | 24 | lines = msg.splitlines(True) 25 | small_msg = "" 26 | result = [] 27 | for line in lines: 28 | if len(small_msg) + len(line) < MAX_MESSAGE_LENGTH: 29 | small_msg += line 30 | else: 31 | result.append(small_msg) 32 | small_msg = line 33 | else: 34 | # Else statement at the end of the for loop, so append the leftover string. 35 | result.append(small_msg) 36 | 37 | return result 38 | 39 | 40 | def paginate_modules(page_n: int, module_dict: Dict, prefix, chat=None) -> List: 41 | if not chat: 42 | modules = sorted( 43 | [EqInlineKeyboardButton(x.__mod_name__, 44 | callback_data="{}_module({})".format(prefix, x.__mod_name__.lower())) for x 45 | in module_dict.values()]) 46 | else: 47 | modules = sorted( 48 | [EqInlineKeyboardButton(x.__mod_name__, 49 | callback_data="{}_module({},{})".format(prefix, chat, x.__mod_name__.lower())) for x 50 | in module_dict.values()]) 51 | 52 | pairs = [ 53 | modules[i * 3:(i + 1) * 3] for i in range((len(modules) + 3 - 1) // 3) 54 | ] 55 | 56 | round_num = len(modules) / 3 57 | calc = len(modules) - round(round_num) 58 | if calc == 1: 59 | pairs.append((modules[-1], )) 60 | elif calc == 2: 61 | pairs.append((modules[-1], )) 62 | 63 | max_num_pages = ceil(len(pairs) / 10) 64 | modulo_page = page_n % max_num_pages 65 | 66 | # can only have a certain amount of buttons side by side 67 | if len(pairs) > 8: 68 | pairs = pairs[modulo_page * 8:8 * (modulo_page + 1)] + [ 69 | (EqInlineKeyboardButton("🖕", callback_data="{}_prev({})".format(prefix, modulo_page)), 70 | EqInlineKeyboardButton("Back", callback_data="gabi_back"), 71 | EqInlineKeyboardButton("👇", callback_data="{}_next({})".format(prefix, modulo_page)))] 72 | 73 | else: 74 | pairs += [[EqInlineKeyboardButton("Back", callback_data="gabi_back")]] 75 | 76 | return pairs 77 | 78 | 79 | def send_to_list( 80 | bot: Bot, send_to: list, message: str, markdown=False, html=False 81 | ) -> None: 82 | if html and markdown: 83 | raise Exception("Can only send with either markdown or HTML!") 84 | for user_id in set(send_to): 85 | try: 86 | if markdown: 87 | bot.send_message(user_id, message, parse_mode=ParseMode.MARKDOWN) 88 | elif html: 89 | bot.send_message(user_id, message, parse_mode=ParseMode.HTML) 90 | else: 91 | bot.send_message(user_id, message) 92 | except TelegramError: 93 | pass # ignore users who fail 94 | 95 | 96 | def build_keyboard(buttons): 97 | keyb = [] 98 | for btn in buttons: 99 | if btn.same_line and keyb: 100 | keyb[-1].append(InlineKeyboardButton(btn.name, url=btn.url)) 101 | else: 102 | keyb.append([InlineKeyboardButton(btn.name, url=btn.url)]) 103 | 104 | return keyb 105 | 106 | 107 | def revert_buttons(buttons): 108 | res = "" 109 | for btn in buttons: 110 | if btn.same_line: 111 | res += "\n[{}](buttonurl://{}:same)".format(btn.name, btn.url) 112 | else: 113 | res += "\n[{}](buttonurl://{})".format(btn.name, btn.url) 114 | 115 | return res 116 | 117 | 118 | def build_keyboard_parser(bot, chat_id, buttons): 119 | keyb = [] 120 | for btn in buttons: 121 | if btn.url == "{rules}": 122 | btn.url = "http://t.me/{}?start={}".format(bot.username, chat_id) 123 | if btn.same_line and keyb: 124 | keyb[-1].append(InlineKeyboardButton(btn.name, url=btn.url)) 125 | else: 126 | keyb.append([InlineKeyboardButton(btn.name, url=btn.url)]) 127 | 128 | return keyb 129 | 130 | 131 | def is_module_loaded(name): 132 | return name not in NO_LOAD 133 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/misc.py: -------------------------------------------------------------------------------- 1 | from GabiBraunRobot.modules.helper_funcs.chat_status import user_admin 2 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 3 | from GabiBraunRobot import dispatcher 4 | 5 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup 6 | from telegram import ParseMode, Update 7 | from telegram.ext.dispatcher import run_async 8 | from telegram.ext import CallbackContext, Filters, CommandHandler 9 | 10 | MARKDOWN_HELP = f""" 11 | Markdown is a very powerful formatting tool supported by telegram. {dispatcher.bot.first_name} has some enhancements, to make sure that \ 12 | saved messages are correctly parsed, and to allow you to create buttons. 13 | 14 | • _italic_: wrapping text with '_' will produce italic text 15 | • *bold*: wrapping text with '*' will produce bold text 16 | • `code`: wrapping text with '`' will produce monospaced text, also known as 'code' 17 | • [sometext](someURL): this will create a link - the message will just show sometext, \ 18 | and tapping on it will open the page at someURL. 19 | Example:[test](example.com) 20 | 21 | • [buttontext](buttonurl:someURL): this is a special enhancement to allow users to have telegram \ 22 | buttons in their markdown. buttontext will be what is displayed on the button, and someurl \ 23 | will be the url which is opened. 24 | Example: [This is a button](buttonurl:example.com) 25 | 26 | If you want multiple buttons on the same line, use :same, as such: 27 | [one](buttonurl://example.com) 28 | [two](buttonurl://google.com:same) 29 | This will create two buttons on a single line, instead of one button per line. 30 | 31 | Keep in mind that your message MUST contain some text other than just a button! 32 | """ 33 | 34 | 35 | @run_async 36 | @user_admin 37 | def echo(update: Update, context: CallbackContext): 38 | args = update.effective_message.text.split(None, 1) 39 | message = update.effective_message 40 | 41 | if message.reply_to_message: 42 | message.reply_to_message.reply_text( 43 | args[1], parse_mode="MARKDOWN", disable_web_page_preview=True) 44 | else: 45 | message.reply_text( 46 | args[1], 47 | quote=False, 48 | parse_mode="MARKDOWN", 49 | disable_web_page_preview=True) 50 | message.delete() 51 | 52 | 53 | def markdown_help_sender(update: Update): 54 | update.effective_message.reply_text( 55 | MARKDOWN_HELP, parse_mode=ParseMode.HTML) 56 | update.effective_message.reply_text( 57 | "Try forwarding the following message to me, and you'll see, and Use #test!" 58 | ) 59 | update.effective_message.reply_text( 60 | "/save test This is a markdown test. _italics_, *bold*, code, " 61 | "[URL](example.com) [button](buttonurl:github.com) " 62 | "[button2](buttonurl://google.com:same)") 63 | 64 | 65 | @run_async 66 | def markdown_help(update: Update, context: CallbackContext): 67 | if update.effective_chat.type != "private": 68 | update.effective_message.reply_text( 69 | 'Contact me in pm', 70 | reply_markup=InlineKeyboardMarkup([[ 71 | InlineKeyboardButton( 72 | "Markdown help", 73 | url=f"t.me/{context.bot.username}?start=markdownhelp") 74 | ]])) 75 | return 76 | markdown_help_sender(update) 77 | 78 | 79 | __help__ = """ 80 | *Available commands:* 81 | *Markdown:* 82 | • `/markdownhelp`*:* quick summary of how markdown works in telegram - can only be called in private chats 83 | *Paste:* 84 | • `/paste`*:* Saves replied content to `nekobin.com` and replies with a url 85 | *React:* 86 | • `/react`*:* Reacts with a random reaction 87 | *Urban Dictonary:* 88 | • `/ud `*:* Type the word or expression you want to search use 89 | *Wikipedia:* 90 | • `/wiki `*:* wikipedia your query 91 | *Wallpapers:* 92 | • `/wall `*:* get a wallpaper from wall.alphacoders.com 93 | *Currency converter:* 94 | • `/cash`*:* currency converter 95 | Example: 96 | `/cash 1 USD INR` 97 | _OR_ 98 | `/cash 1 usd inr` 99 | Output: `1.0 USD = 75.505 INR` 100 | """ 101 | 102 | ECHO_HANDLER = DisableAbleCommandHandler("echo", echo, filters=Filters.group) 103 | MD_HELP_HANDLER = CommandHandler("markdownhelp", markdown_help) 104 | 105 | dispatcher.add_handler(ECHO_HANDLER) 106 | dispatcher.add_handler(MD_HELP_HANDLER) 107 | 108 | __mod_name__ = "Extras" 109 | __command_list__ = ["id", "echo"] 110 | __handlers__ = [ 111 | ECHO_HANDLER, 112 | MD_HELP_HANDLER, 113 | ] 114 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/rules.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import GabiBraunRobot.modules.sql.rules_sql as sql 4 | from GabiBraunRobot import dispatcher 5 | from GabiBraunRobot.modules.helper_funcs.chat_status import user_admin 6 | from GabiBraunRobot.modules.helper_funcs.string_handling import markdown_parser 7 | from telegram import (InlineKeyboardButton, InlineKeyboardMarkup, Message, 8 | ParseMode, Update, User) 9 | from telegram.error import BadRequest 10 | from telegram.ext import CallbackContext, CommandHandler, Filters, run_async 11 | from telegram.utils.helpers import escape_markdown 12 | 13 | 14 | @run_async 15 | def get_rules(update: Update, context: CallbackContext): 16 | chat_id = update.effective_chat.id 17 | send_rules(update, chat_id) 18 | 19 | 20 | # Do not async - not from a handler 21 | def send_rules(update, chat_id, from_pm=False): 22 | bot = dispatcher.bot 23 | user = update.effective_user # type: Optional[User] 24 | try: 25 | chat = bot.get_chat(chat_id) 26 | except BadRequest as excp: 27 | if excp.message == "Chat not found" and from_pm: 28 | bot.send_message( 29 | user.id, 30 | "The rules shortcut for this chat hasn't been set properly! Ask admins to " 31 | "fix this.\nMaybe they forgot the hyphen in ID") 32 | return 33 | else: 34 | raise 35 | 36 | rules = sql.get_rules(chat_id) 37 | text = f"The rules for *{escape_markdown(chat.title)}* are:\n\n{rules}" 38 | 39 | if from_pm and rules: 40 | bot.send_message( 41 | user.id, 42 | text, 43 | parse_mode=ParseMode.MARKDOWN, 44 | disable_web_page_preview=True) 45 | elif from_pm: 46 | bot.send_message( 47 | user.id, 48 | "The group admins haven't set any rules for this chat yet. " 49 | "This probably doesn't mean it's lawless though...!") 50 | elif rules: 51 | update.effective_message.reply_text( 52 | "Please click the button below to see the rules.", 53 | reply_markup=InlineKeyboardMarkup([[ 54 | InlineKeyboardButton( 55 | text="Rules", url=f"t.me/{bot.username}?start={chat_id}") 56 | ]])) 57 | else: 58 | update.effective_message.reply_text( 59 | "The group admins haven't set any rules for this chat yet. " 60 | "This probably doesn't mean it's lawless though...!") 61 | 62 | 63 | @run_async 64 | @user_admin 65 | def set_rules(update: Update, context: CallbackContext): 66 | chat_id = update.effective_chat.id 67 | msg = update.effective_message # type: Optional[Message] 68 | raw_text = msg.text 69 | args = raw_text.split(None, 70 | 1) # use python's maxsplit to separate cmd and args 71 | if len(args) == 2: 72 | txt = args[1] 73 | offset = len(txt) - len( 74 | raw_text) # set correct offset relative to command 75 | markdown_rules = markdown_parser( 76 | txt, entities=msg.parse_entities(), offset=offset) 77 | 78 | sql.set_rules(chat_id, markdown_rules) 79 | update.effective_message.reply_text( 80 | "Successfully set rules for this group.") 81 | 82 | 83 | @run_async 84 | @user_admin 85 | def clear_rules(update: Update, context: CallbackContext): 86 | chat_id = update.effective_chat.id 87 | sql.set_rules(chat_id, "") 88 | update.effective_message.reply_text("Successfully cleared rules!") 89 | 90 | 91 | def __stats__(): 92 | return f"• {sql.num_chats()} chats have rules set." 93 | 94 | 95 | def __import_data__(chat_id, data): 96 | # set chat rules 97 | rules = data.get('info', {}).get('rules', "") 98 | sql.set_rules(chat_id, rules) 99 | 100 | 101 | def __migrate__(old_chat_id, new_chat_id): 102 | sql.migrate_chat(old_chat_id, new_chat_id) 103 | 104 | 105 | def __chat_settings__(chat_id, user_id): 106 | return f"This chat has had it's rules set: `{bool(sql.get_rules(chat_id))}`" 107 | 108 | 109 | __help__ = """ 110 | • `/rules`*:* get the rules for this chat. 111 | 112 | *Admins only:* 113 | • `/setrules `*:* set the rules for this chat. 114 | • `/clearrules`*:* clear the rules for this chat. 115 | """ 116 | 117 | __mod_name__ = "Rules" 118 | 119 | GET_RULES_HANDLER = CommandHandler("rules", get_rules, filters=Filters.group) 120 | SET_RULES_HANDLER = CommandHandler("setrules", set_rules, filters=Filters.group) 121 | RESET_RULES_HANDLER = CommandHandler( 122 | "clearrules", clear_rules, filters=Filters.group) 123 | 124 | dispatcher.add_handler(GET_RULES_HANDLER) 125 | dispatcher.add_handler(SET_RULES_HANDLER) 126 | dispatcher.add_handler(RESET_RULES_HANDLER) 127 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/global_bans_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Boolean, Column, Integer, String, UnicodeText 5 | 6 | 7 | class GloballyBannedUsers(BASE): 8 | __tablename__ = "gbans" 9 | user_id = Column(Integer, primary_key=True) 10 | name = Column(UnicodeText, nullable=False) 11 | reason = Column(UnicodeText) 12 | 13 | def __init__(self, user_id, name, reason=None): 14 | self.user_id = user_id 15 | self.name = name 16 | self.reason = reason 17 | 18 | def __repr__(self): 19 | return "".format(self.name, self.user_id) 20 | 21 | def to_dict(self): 22 | return { 23 | "user_id": self.user_id, 24 | "name": self.name, 25 | "reason": self.reason 26 | } 27 | 28 | 29 | class GbanSettings(BASE): 30 | __tablename__ = "gban_settings" 31 | chat_id = Column(String(14), primary_key=True) 32 | setting = Column(Boolean, default=True, nullable=False) 33 | 34 | def __init__(self, chat_id, enabled): 35 | self.chat_id = str(chat_id) 36 | self.setting = enabled 37 | 38 | def __repr__(self): 39 | return "".format(self.chat_id, self.setting) 40 | 41 | 42 | GloballyBannedUsers.__table__.create(checkfirst=True) 43 | GbanSettings.__table__.create(checkfirst=True) 44 | 45 | GBANNED_USERS_LOCK = threading.RLock() 46 | GBAN_SETTING_LOCK = threading.RLock() 47 | GBANNED_LIST = set() 48 | GBANSTAT_LIST = set() 49 | 50 | 51 | def gban_user(user_id, name, reason=None): 52 | with GBANNED_USERS_LOCK: 53 | user = SESSION.query(GloballyBannedUsers).get(user_id) 54 | if not user: 55 | user = GloballyBannedUsers(user_id, name, reason) 56 | else: 57 | user.name = name 58 | user.reason = reason 59 | 60 | SESSION.merge(user) 61 | SESSION.commit() 62 | __load_gbanned_userid_list() 63 | 64 | 65 | def update_gban_reason(user_id, name, reason=None): 66 | with GBANNED_USERS_LOCK: 67 | user = SESSION.query(GloballyBannedUsers).get(user_id) 68 | if not user: 69 | return None 70 | old_reason = user.reason 71 | user.name = name 72 | user.reason = reason 73 | 74 | SESSION.merge(user) 75 | SESSION.commit() 76 | return old_reason 77 | 78 | 79 | def ungban_user(user_id): 80 | with GBANNED_USERS_LOCK: 81 | user = SESSION.query(GloballyBannedUsers).get(user_id) 82 | if user: 83 | SESSION.delete(user) 84 | 85 | SESSION.commit() 86 | __load_gbanned_userid_list() 87 | 88 | 89 | def is_user_gbanned(user_id): 90 | return user_id in GBANNED_LIST 91 | 92 | 93 | def get_gbanned_user(user_id): 94 | try: 95 | return SESSION.query(GloballyBannedUsers).get(user_id) 96 | finally: 97 | SESSION.close() 98 | 99 | 100 | def get_gban_list(): 101 | try: 102 | return [x.to_dict() for x in SESSION.query(GloballyBannedUsers).all()] 103 | finally: 104 | SESSION.close() 105 | 106 | 107 | def enable_gbans(chat_id): 108 | with GBAN_SETTING_LOCK: 109 | chat = SESSION.query(GbanSettings).get(str(chat_id)) 110 | if not chat: 111 | chat = GbanSettings(chat_id, True) 112 | 113 | chat.setting = True 114 | SESSION.add(chat) 115 | SESSION.commit() 116 | if str(chat_id) in GBANSTAT_LIST: 117 | GBANSTAT_LIST.remove(str(chat_id)) 118 | 119 | 120 | def disable_gbans(chat_id): 121 | with GBAN_SETTING_LOCK: 122 | chat = SESSION.query(GbanSettings).get(str(chat_id)) 123 | if not chat: 124 | chat = GbanSettings(chat_id, False) 125 | 126 | chat.setting = False 127 | SESSION.add(chat) 128 | SESSION.commit() 129 | GBANSTAT_LIST.add(str(chat_id)) 130 | 131 | 132 | def does_chat_gban(chat_id): 133 | return str(chat_id) not in GBANSTAT_LIST 134 | 135 | 136 | def num_gbanned_users(): 137 | return len(GBANNED_LIST) 138 | 139 | 140 | def __load_gbanned_userid_list(): 141 | global GBANNED_LIST 142 | try: 143 | GBANNED_LIST = { 144 | x.user_id for x in SESSION.query(GloballyBannedUsers).all() 145 | } 146 | finally: 147 | SESSION.close() 148 | 149 | 150 | def __load_gban_stat_list(): 151 | global GBANSTAT_LIST 152 | try: 153 | GBANSTAT_LIST = { 154 | x.chat_id 155 | for x in SESSION.query(GbanSettings).all() 156 | if not x.setting 157 | } 158 | finally: 159 | SESSION.close() 160 | 161 | 162 | def migrate_chat(old_chat_id, new_chat_id): 163 | with GBAN_SETTING_LOCK: 164 | chat = SESSION.query(GbanSettings).get(str(old_chat_id)) 165 | if chat: 166 | chat.chat_id = new_chat_id 167 | SESSION.add(chat) 168 | 169 | SESSION.commit() 170 | 171 | 172 | # Create in memory userid to avoid disk access 173 | __load_gbanned_userid_list() 174 | __load_gban_stat_list() 175 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/dbcleanup.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | import GabiBraunRobot.modules.sql.global_bans_sql as gban_sql 4 | import GabiBraunRobot.modules.sql.users_sql as user_sql 5 | from GabiBraunRobot import DEV_USERS, OWNER_ID, dispatcher 6 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus 7 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update 8 | from telegram.error import BadRequest, Unauthorized 9 | from telegram.ext import (CallbackContext, CallbackQueryHandler, CommandHandler, 10 | run_async) 11 | 12 | 13 | def get_invalid_chats(update: Update, 14 | context: CallbackContext, 15 | remove: bool = False): 16 | bot = context.bot 17 | chat_id = update.effective_chat.id 18 | chats = user_sql.get_all_chats() 19 | kicked_chats, progress = 0, 0 20 | chat_list = [] 21 | progress_message = None 22 | 23 | for chat in chats: 24 | 25 | if ((100 * chats.index(chat)) / len(chats)) > progress: 26 | progress_bar = f"{progress}% completed in getting invalid chats." 27 | if progress_message: 28 | try: 29 | bot.editMessageText(progress_bar, chat_id, 30 | progress_message.message_id) 31 | except: 32 | pass 33 | else: 34 | progress_message = bot.sendMessage(chat_id, progress_bar) 35 | progress += 5 36 | 37 | cid = chat.chat_id 38 | sleep(0.1) 39 | try: 40 | bot.get_chat(cid, timeout=60) 41 | except (BadRequest, Unauthorized): 42 | kicked_chats += 1 43 | chat_list.append(cid) 44 | except: 45 | pass 46 | 47 | try: 48 | progress_message.delete() 49 | except: 50 | pass 51 | 52 | if not remove: 53 | return kicked_chats 54 | else: 55 | for muted_chat in chat_list: 56 | sleep(0.1) 57 | user_sql.rem_chat(muted_chat) 58 | return kicked_chats 59 | 60 | 61 | def get_invalid_gban(update: Update, 62 | context: CallbackContext, 63 | remove: bool = False): 64 | bot = context.bot 65 | banned = gban_sql.get_gban_list() 66 | ungbanned_users = 0 67 | ungban_list = [] 68 | 69 | for user in banned: 70 | user_id = user["user_id"] 71 | sleep(0.1) 72 | try: 73 | bot.get_chat(user_id) 74 | except BadRequest: 75 | ungbanned_users += 1 76 | ungban_list.append(user_id) 77 | except: 78 | pass 79 | 80 | if not remove: 81 | return ungbanned_users 82 | else: 83 | for user_id in ungban_list: 84 | sleep(0.1) 85 | gban_sql.ungban_user(user_id) 86 | return ungbanned_users 87 | 88 | 89 | @run_async 90 | @dev_plus 91 | def dbcleanup(update: Update, context: CallbackContext): 92 | msg = update.effective_message 93 | 94 | msg.reply_text("Getting invalid chat count ...") 95 | invalid_chat_count = get_invalid_chats(update, context) 96 | 97 | msg.reply_text("Getting invalid gbanned count ...") 98 | invalid_gban_count = get_invalid_gban(update, context) 99 | 100 | reply = f"Total invalid chats - {invalid_chat_count}\n" 101 | reply += f"Total invalid gbanned users - {invalid_gban_count}" 102 | 103 | buttons = [[InlineKeyboardButton("Cleanup DB", callback_data="db_cleanup")]] 104 | 105 | update.effective_message.reply_text( 106 | reply, reply_markup=InlineKeyboardMarkup(buttons)) 107 | 108 | 109 | @run_async 110 | def callback_button(update: Update, context: CallbackContext): 111 | bot = context.bot 112 | query = update.callback_query 113 | message = query.message 114 | chat_id = update.effective_chat.id 115 | query_type = query.data 116 | 117 | admin_list = [OWNER_ID] + DEV_USERS 118 | 119 | bot.answer_callback_query(query.id) 120 | 121 | if query_type == "db_leave_chat": 122 | if query.from_user.id in admin_list: 123 | bot.editMessageText("Leaving chats ...", chat_id, 124 | message.message_id) 125 | chat_count = get_muted_chats(update, context, True) 126 | bot.sendMessage(chat_id, f"Left {chat_count} chats.") 127 | else: 128 | query.answer("You are not allowed to use this.") 129 | elif query_type == "db_cleanup": 130 | if query.from_user.id in admin_list: 131 | bot.editMessageText("Cleaning up DB ...", chat_id, 132 | message.message_id) 133 | invalid_chat_count = get_invalid_chats(update, context, True) 134 | invalid_gban_count = get_invalid_gban(update, context, True) 135 | reply = "Cleaned up {} chats and {} gbanned users from db.".format( 136 | invalid_chat_count, invalid_gban_count) 137 | bot.sendMessage(chat_id, reply) 138 | else: 139 | query.answer("You are not allowed to use this.") 140 | 141 | 142 | DB_CLEANUP_HANDLER = CommandHandler("dbcleanup", dbcleanup) 143 | BUTTON_HANDLER = CallbackQueryHandler(callback_button, pattern='db_.*') 144 | 145 | dispatcher.add_handler(DB_CLEANUP_HANDLER) 146 | dispatcher.add_handler(BUTTON_HANDLER) 147 | 148 | __mod_name__ = "DB Cleanup" 149 | __handlers__ = [DB_CLEANUP_HANDLER, BUTTON_HANDLER] 150 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sed.py: -------------------------------------------------------------------------------- 1 | import sre_constants 2 | 3 | import regex 4 | import telegram 5 | from GabiBraunRobot import LOGGER, dispatcher 6 | from GabiBraunRobot.modules.disable import DisableAbleMessageHandler 7 | from GabiBraunRobot.modules.helper_funcs.regex_helper import (infinite_loop_check) 8 | from telegram import Update 9 | from telegram.ext import CallbackContext, Filters, run_async 10 | 11 | DELIMITERS = ("/", ":", "|", "_") 12 | 13 | 14 | def separate_sed(sed_string): 15 | if len(sed_string) >= 3 and sed_string[ 16 | 1] in DELIMITERS and sed_string.count(sed_string[1]) >= 2: 17 | delim = sed_string[1] 18 | start = counter = 2 19 | while counter < len(sed_string): 20 | if sed_string[counter] == "\\": 21 | counter += 1 22 | 23 | elif sed_string[counter] == delim: 24 | replace = sed_string[start:counter] 25 | counter += 1 26 | start = counter 27 | break 28 | 29 | counter += 1 30 | 31 | else: 32 | return None 33 | 34 | while counter < len(sed_string): 35 | if sed_string[counter] == "\\" and counter + 1 < len( 36 | sed_string) and sed_string[counter + 1] == delim: 37 | sed_string = sed_string[:counter] + sed_string[counter + 1:] 38 | 39 | elif sed_string[counter] == delim: 40 | replace_with = sed_string[start:counter] 41 | counter += 1 42 | break 43 | 44 | counter += 1 45 | else: 46 | return replace, sed_string[start:], "" 47 | 48 | flags = "" 49 | if counter < len(sed_string): 50 | flags = sed_string[counter:] 51 | return replace, replace_with, flags.lower() 52 | 53 | 54 | @run_async 55 | def sed(update: Update, context: CallbackContext): 56 | sed_result = separate_sed(update.effective_message.text) 57 | if sed_result and update.effective_message.reply_to_message: 58 | if update.effective_message.reply_to_message.text: 59 | to_fix = update.effective_message.reply_to_message.text 60 | elif update.effective_message.reply_to_message.caption: 61 | to_fix = update.effective_message.reply_to_message.caption 62 | else: 63 | return 64 | 65 | repl, repl_with, flags = sed_result 66 | if not repl: 67 | update.effective_message.reply_to_message.reply_text( 68 | "You're trying to replace... " 69 | "nothing with something?") 70 | return 71 | 72 | try: 73 | try: 74 | check = regex.match( 75 | repl, to_fix, flags=regex.IGNORECASE, timeout=5) 76 | except TimeoutError: 77 | return 78 | if check and check.group(0).lower() == to_fix.lower(): 79 | update.effective_message.reply_to_message.reply_text( 80 | "Hey everyone, {} is trying to make " 81 | "me say stuff I don't wanna " 82 | "say!".format(update.effective_user.first_name)) 83 | return 84 | if infinite_loop_check(repl): 85 | update.effective_message.reply_text( 86 | "I'm afraid I can't run that regex.") 87 | return 88 | if 'i' in flags and 'g' in flags: 89 | text = regex.sub( 90 | repl, repl_with, to_fix, flags=regex.I, timeout=3).strip() 91 | elif 'i' in flags: 92 | text = regex.sub( 93 | repl, repl_with, to_fix, count=1, flags=regex.I, 94 | timeout=3).strip() 95 | elif 'g' in flags: 96 | text = regex.sub(repl, repl_with, to_fix, timeout=3).strip() 97 | else: 98 | text = regex.sub( 99 | repl, repl_with, to_fix, count=1, timeout=3).strip() 100 | except TimeoutError: 101 | update.effective_message.reply_text('Timeout') 102 | return 103 | except sre_constants.error: 104 | LOGGER.warning(update.effective_message.text) 105 | LOGGER.exception("SRE constant error") 106 | update.effective_message.reply_text( 107 | "Do you even sed? Apparently not.") 108 | return 109 | 110 | # empty string errors -_- 111 | if len(text) >= telegram.MAX_MESSAGE_LENGTH: 112 | update.effective_message.reply_text( 113 | "The result of the sed command was too long for \ 114 | telegram!") 115 | elif text: 116 | update.effective_message.reply_to_message.reply_text(text) 117 | 118 | 119 | __help__ = """ 120 | • `s//(/)`*:* Reply to a message with this to perform a sed operation on that message, replacing all \ 121 | occurrences of 'text1' with 'text2'. Flags are optional, and currently include 'i' for ignore case, 'g' for global, \ 122 | or nothing. Delimiters include `/`, `_`, `|`, and `:`. Text grouping is supported. The resulting message cannot be \ 123 | larger than {}. 124 | *Reminder:* Sed uses some special characters to make matching easier, such as these: `+*.?\\` 125 | If you want to use these characters, make sure you escape them! 126 | *Example:* \\?. 127 | """.format(telegram.MAX_MESSAGE_LENGTH) 128 | 129 | __mod_name__ = "Sed/Regex" 130 | 131 | SED_HANDLER = DisableAbleMessageHandler( 132 | Filters.regex(r's([{}]).*?\1.*'.format("".join(DELIMITERS))), 133 | sed, 134 | friendly="sed") 135 | 136 | dispatcher.add_handler(SED_HANDLER) 137 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/blacklistusers.py: -------------------------------------------------------------------------------- 1 | # Module to blacklist users and prevent them from using commands by @TheRealPhoenix 2 | import html 3 | import GabiBraunRobot.modules.sql.blacklistusers_sql as sql 4 | from GabiBraunRobot import (DEV_USERS, OWNER_ID, DRAGONS, DEMONS, TIGERS, WOLVES, 5 | dispatcher) 6 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus 7 | from GabiBraunRobot.modules.helper_funcs.extraction import (extract_user, 8 | extract_user_and_text) 9 | from GabiBraunRobot.modules.log_channel import gloggable 10 | from telegram import ParseMode, Update 11 | from telegram.error import BadRequest 12 | from telegram.ext import CallbackContext, CommandHandler, run_async 13 | from telegram.utils.helpers import mention_html 14 | 15 | BLACKLISTWHITELIST = [OWNER_ID] + DEV_USERS + DRAGONS + WOLVES + DEMONS 16 | BLABLEUSERS = [OWNER_ID] + DEV_USERS 17 | 18 | 19 | @run_async 20 | @dev_plus 21 | @gloggable 22 | def bl_user(update: Update, context: CallbackContext) -> str: 23 | message = update.effective_message 24 | user = update.effective_user 25 | bot, args = context.bot, context.args 26 | user_id, reason = extract_user_and_text(message, args) 27 | 28 | if not user_id: 29 | message.reply_text("I doubt that's a user.") 30 | return "" 31 | 32 | if user_id == bot.id: 33 | message.reply_text( 34 | "How am I supposed to do my work if I am ignoring myself?") 35 | return "" 36 | 37 | if user_id in BLACKLISTWHITELIST: 38 | message.reply_text("No!\nNoticing Disasters is my job.") 39 | return "" 40 | 41 | try: 42 | target_user = bot.get_chat(user_id) 43 | except BadRequest as excp: 44 | if excp.message == "User not found": 45 | message.reply_text("I can't seem to find this user.") 46 | return "" 47 | else: 48 | raise 49 | 50 | sql.blacklist_user(user_id, reason) 51 | message.reply_text("I shall ignore the existence of this user!") 52 | log_message = ( 53 | f"#BLACKLIST\n" 54 | f"Admin: {mention_html(user.id, html.escape(user.first_name))}\n" 55 | f"User: {mention_html(target_user.id, html.escape(target_user.first_name))}" 56 | ) 57 | if reason: 58 | log_message += f"\nReason: {reason}" 59 | 60 | return log_message 61 | 62 | 63 | @run_async 64 | @dev_plus 65 | @gloggable 66 | def unbl_user(update: Update, context: CallbackContext) -> str: 67 | message = update.effective_message 68 | user = update.effective_user 69 | bot, args = context.bot, context.args 70 | user_id = extract_user(message, args) 71 | 72 | if not user_id: 73 | message.reply_text("I doubt that's a user.") 74 | return "" 75 | 76 | if user_id == bot.id: 77 | message.reply_text("I always notice myself.") 78 | return "" 79 | 80 | try: 81 | target_user = bot.get_chat(user_id) 82 | except BadRequest as excp: 83 | if excp.message == "User not found": 84 | message.reply_text("I can't seem to find this user.") 85 | return "" 86 | else: 87 | raise 88 | 89 | if sql.is_user_blacklisted(user_id): 90 | 91 | sql.unblacklist_user(user_id) 92 | message.reply_text("*notices user*") 93 | log_message = ( 94 | f"#UNBLACKLIST\n" 95 | f"Admin: {mention_html(user.id, html.escape(user.first_name))}\n" 96 | f"User: {mention_html(target_user.id, html.escape(target_user.first_name))}" 97 | ) 98 | 99 | return log_message 100 | 101 | else: 102 | message.reply_text("I am not ignoring them at all though!") 103 | return "" 104 | 105 | 106 | @run_async 107 | @dev_plus 108 | def bl_users(update: Update, context: CallbackContext): 109 | users = [] 110 | bot = context.bot 111 | for each_user in sql.BLACKLIST_USERS: 112 | user = bot.get_chat(each_user) 113 | reason = sql.get_reason(each_user) 114 | 115 | if reason: 116 | users.append( 117 | f"• {mention_html(user.id, html.escape(user.first_name))} :- {reason}" 118 | ) 119 | else: 120 | users.append( 121 | f"• {mention_html(user.id, html.escape(user.first_name))}") 122 | 123 | message = "Blacklisted Users\n" 124 | if not users: 125 | message += "Noone is being ignored as of yet." 126 | else: 127 | message += '\n'.join(users) 128 | 129 | update.effective_message.reply_text(message, parse_mode=ParseMode.HTML) 130 | 131 | 132 | def __user_info__(user_id): 133 | is_blacklisted = sql.is_user_blacklisted(user_id) 134 | 135 | text = "Blacklisted: {}" 136 | if user_id in [777000, 1087968824]: 137 | return "" 138 | if user_id == dispatcher.bot.id: 139 | return "" 140 | if int(user_id) in DRAGONS + TIGERS + WOLVES: 141 | return "" 142 | if is_blacklisted: 143 | text = text.format("Yes") 144 | reason = sql.get_reason(user_id) 145 | if reason: 146 | text += f"\nReason: {reason}" 147 | else: 148 | text = text.format("No") 149 | 150 | return text 151 | 152 | 153 | BL_HANDLER = CommandHandler("ignore", bl_user) 154 | UNBL_HANDLER = CommandHandler("notice", unbl_user) 155 | BLUSERS_HANDLER = CommandHandler("ignoredlist", bl_users) 156 | 157 | dispatcher.add_handler(BL_HANDLER) 158 | dispatcher.add_handler(UNBL_HANDLER) 159 | dispatcher.add_handler(BLUSERS_HANDLER) 160 | 161 | __mod_name__ = "Blacklisting Users" 162 | __handlers__ = [BL_HANDLER, UNBL_HANDLER, BLUSERS_HANDLER] 163 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/handlers.py: -------------------------------------------------------------------------------- 1 | import GabiBraunRobot.modules.sql.blacklistusers_sql as sql 2 | from GabiBraunRobot import ALLOW_EXCL 3 | from GabiBraunRobot import (DEV_USERS, DRAGONS, DEMONS, TIGERS, WOLVES) 4 | 5 | from telegram import Update 6 | from telegram.ext import CommandHandler, MessageHandler, RegexHandler, Filters 7 | from pyrate_limiter import (BucketFullException, Duration, RequestRate, Limiter, 8 | MemoryListBucket) 9 | 10 | if ALLOW_EXCL: 11 | CMD_STARTERS = ('/', '!') 12 | else: 13 | CMD_STARTERS = ('/',) 14 | 15 | 16 | class AntiSpam: 17 | 18 | def __init__(self): 19 | self.whitelist = (DEV_USERS or []) + (DRAGONS or []) + ( 20 | WOLVES or []) + (DEMONS or []) + ( 21 | TIGERS or []) 22 | #Values are HIGHLY experimental, its recommended you pay attention to our commits as we will be adjusting the values over time with what suits best. 23 | Duration.CUSTOM = 15 # Custom duration, 15 seconds 24 | self.sec_limit = RequestRate(6, Duration.CUSTOM) # 6 / Per 15 Seconds 25 | self.min_limit = RequestRate(20, Duration.MINUTE) # 20 / Per minute 26 | self.hour_limit = RequestRate(100, Duration.HOUR) # 100 / Per hour 27 | self.daily_limit = RequestRate(1000, Duration.DAY) # 1000 / Per day 28 | self.limiter = Limiter( 29 | self.sec_limit, 30 | self.min_limit, 31 | self.hour_limit, 32 | self.daily_limit, 33 | bucket_class=MemoryListBucket) 34 | 35 | def check_user(self, user): 36 | """ 37 | Return True if user is to be ignored else False 38 | """ 39 | if user in self.whitelist: 40 | return False 41 | try: 42 | self.limiter.try_acquire(user) 43 | return False 44 | except BucketFullException: 45 | return True 46 | 47 | 48 | SpamChecker = AntiSpam() 49 | MessageHandlerChecker = AntiSpam() 50 | 51 | 52 | class CustomCommandHandler(CommandHandler): 53 | 54 | def __init__(self, 55 | command, 56 | callback, 57 | admin_ok=False, 58 | allow_edit=False, 59 | **kwargs): 60 | super().__init__(command, callback, **kwargs) 61 | 62 | if allow_edit is False: 63 | self.filters &= ~( 64 | Filters.update.edited_message 65 | | Filters.update.edited_channel_post) 66 | 67 | def check_update(self, update): 68 | if isinstance(update, Update) and update.effective_message: 69 | message = update.effective_message 70 | 71 | try: 72 | user_id = update.effective_user.id 73 | except: 74 | user_id = None 75 | 76 | if user_id: 77 | if sql.is_user_blacklisted(user_id): 78 | return False 79 | 80 | if message.text and len(message.text) > 1: 81 | fst_word = message.text.split(None, 1)[0] 82 | if len(fst_word) > 1 and any( 83 | fst_word.startswith(start) for start in CMD_STARTERS): 84 | 85 | args = message.text.split()[1:] 86 | command = fst_word[1:].split("@") 87 | command.append(message.bot.username) 88 | if user_id == 1087968824: 89 | user_id = update.effective_chat.id 90 | if not (command[0].lower() in self.command and 91 | command[1].lower() == message.bot.username.lower()): 92 | return None 93 | if SpamChecker.check_user(user_id): 94 | return None 95 | filter_result = self.filters(update) 96 | if filter_result: 97 | return args, filter_result 98 | else: 99 | return False 100 | 101 | def handle_update(self, update, dispatcher, check_result, context=None): 102 | if context: 103 | self.collect_additional_context(context, update, dispatcher, 104 | check_result) 105 | return self.callback(update, context) 106 | else: 107 | optional_args = self.collect_optional_args(dispatcher, update, 108 | check_result) 109 | return self.callback(dispatcher.bot, update, **optional_args) 110 | 111 | def collect_additional_context(self, context, update, dispatcher, 112 | check_result): 113 | if isinstance(check_result, bool): 114 | context.args = update.effective_message.text.split()[1:] 115 | else: 116 | context.args = check_result[0] 117 | if isinstance(check_result[1], dict): 118 | context.update(check_result[1]) 119 | 120 | 121 | class CustomRegexHandler(RegexHandler): 122 | 123 | def __init__(self, pattern, callback, friendly="", **kwargs): 124 | super().__init__(pattern, callback, **kwargs) 125 | 126 | 127 | class CustomMessageHandler(MessageHandler): 128 | 129 | def __init__(self, 130 | filters, 131 | callback, 132 | friendly="", 133 | allow_edit=False, 134 | **kwargs): 135 | super().__init__(filters, callback, **kwargs) 136 | if allow_edit is False: 137 | self.filters &= ~( 138 | Filters.update.edited_message 139 | | Filters.update.edited_channel_post) 140 | 141 | def check_update(self, update): 142 | if isinstance(update, Update) and update.effective_message: 143 | return self.filters(update) 144 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/notes_sql.py: -------------------------------------------------------------------------------- 1 | # Note: chat_id's are stored as strings because the int is too large to be stored in a PSQL database. 2 | import threading 3 | 4 | from GabiBraunRobot.modules.helper_funcs.msg_types import Types 5 | from GabiBraunRobot.modules.sql import BASE, SESSION 6 | from sqlalchemy import (Boolean, Column, Integer, String, UnicodeText, distinct, 7 | func) 8 | 9 | 10 | class Notes(BASE): 11 | __tablename__ = "notes" 12 | chat_id = Column(String(14), primary_key=True) 13 | name = Column(UnicodeText, primary_key=True) 14 | value = Column(UnicodeText, nullable=False) 15 | file = Column(UnicodeText) 16 | is_reply = Column(Boolean, default=False) 17 | has_buttons = Column(Boolean, default=False) 18 | msgtype = Column(Integer, default=Types.BUTTON_TEXT.value) 19 | 20 | def __init__(self, chat_id, name, value, msgtype, file=None): 21 | self.chat_id = str(chat_id) # ensure string 22 | self.name = name 23 | self.value = value 24 | self.msgtype = msgtype 25 | self.file = file 26 | 27 | def __repr__(self): 28 | return "" % self.name 29 | 30 | 31 | class Buttons(BASE): 32 | __tablename__ = "note_urls" 33 | id = Column(Integer, primary_key=True, autoincrement=True) 34 | chat_id = Column(String(14), primary_key=True) 35 | note_name = Column(UnicodeText, primary_key=True) 36 | name = Column(UnicodeText, nullable=False) 37 | url = Column(UnicodeText, nullable=False) 38 | same_line = Column(Boolean, default=False) 39 | 40 | def __init__(self, chat_id, note_name, name, url, same_line=False): 41 | self.chat_id = str(chat_id) 42 | self.note_name = note_name 43 | self.name = name 44 | self.url = url 45 | self.same_line = same_line 46 | 47 | 48 | Notes.__table__.create(checkfirst=True) 49 | Buttons.__table__.create(checkfirst=True) 50 | 51 | NOTES_INSERTION_LOCK = threading.RLock() 52 | BUTTONS_INSERTION_LOCK = threading.RLock() 53 | 54 | 55 | def add_note_to_db(chat_id, 56 | note_name, 57 | note_data, 58 | msgtype, 59 | buttons=None, 60 | file=None): 61 | if not buttons: 62 | buttons = [] 63 | 64 | with NOTES_INSERTION_LOCK: 65 | prev = SESSION.query(Notes).get((str(chat_id), note_name)) 66 | if prev: 67 | with BUTTONS_INSERTION_LOCK: 68 | prev_buttons = SESSION.query(Buttons).filter( 69 | Buttons.chat_id == str(chat_id), 70 | Buttons.note_name == note_name).all() 71 | for btn in prev_buttons: 72 | SESSION.delete(btn) 73 | SESSION.delete(prev) 74 | note = Notes( 75 | str(chat_id), 76 | note_name, 77 | note_data or "", 78 | msgtype=msgtype.value, 79 | file=file) 80 | SESSION.add(note) 81 | SESSION.commit() 82 | 83 | for b_name, url, same_line in buttons: 84 | add_note_button_to_db(chat_id, note_name, b_name, url, same_line) 85 | 86 | 87 | def get_note(chat_id, note_name): 88 | try: 89 | return SESSION.query(Notes).filter( 90 | func.lower(Notes.name) == note_name, 91 | Notes.chat_id == str(chat_id)).first() 92 | finally: 93 | SESSION.close() 94 | 95 | 96 | def rm_note(chat_id, note_name): 97 | with NOTES_INSERTION_LOCK: 98 | note = SESSION.query(Notes).filter( 99 | func.lower(Notes.name) == note_name, 100 | Notes.chat_id == str(chat_id)).first() 101 | if note: 102 | with BUTTONS_INSERTION_LOCK: 103 | buttons = SESSION.query(Buttons).filter( 104 | Buttons.chat_id == str(chat_id), 105 | Buttons.note_name == note_name).all() 106 | for btn in buttons: 107 | SESSION.delete(btn) 108 | 109 | SESSION.delete(note) 110 | SESSION.commit() 111 | return True 112 | 113 | else: 114 | SESSION.close() 115 | return False 116 | 117 | 118 | def get_all_chat_notes(chat_id): 119 | try: 120 | return SESSION.query(Notes).filter( 121 | Notes.chat_id == str(chat_id)).order_by(Notes.name.asc()).all() 122 | finally: 123 | SESSION.close() 124 | 125 | 126 | def add_note_button_to_db(chat_id, note_name, b_name, url, same_line): 127 | with BUTTONS_INSERTION_LOCK: 128 | button = Buttons(chat_id, note_name, b_name, url, same_line) 129 | SESSION.add(button) 130 | SESSION.commit() 131 | 132 | 133 | def get_buttons(chat_id, note_name): 134 | try: 135 | return SESSION.query(Buttons).filter( 136 | Buttons.chat_id == str(chat_id), 137 | Buttons.note_name == note_name).order_by(Buttons.id).all() 138 | finally: 139 | SESSION.close() 140 | 141 | 142 | def num_notes(): 143 | try: 144 | return SESSION.query(Notes).count() 145 | finally: 146 | SESSION.close() 147 | 148 | 149 | def num_chats(): 150 | try: 151 | return SESSION.query(func.count(distinct(Notes.chat_id))).scalar() 152 | finally: 153 | SESSION.close() 154 | 155 | 156 | def migrate_chat(old_chat_id, new_chat_id): 157 | with NOTES_INSERTION_LOCK: 158 | chat_notes = SESSION.query(Notes).filter( 159 | Notes.chat_id == str(old_chat_id)).all() 160 | for note in chat_notes: 161 | note.chat_id = str(new_chat_id) 162 | 163 | with BUTTONS_INSERTION_LOCK: 164 | chat_buttons = SESSION.query(Buttons).filter( 165 | Buttons.chat_id == str(old_chat_id)).all() 166 | for btn in chat_buttons: 167 | btn.chat_id = str(new_chat_id) 168 | 169 | SESSION.commit() 170 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/helper_funcs/extraction.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from GabiBraunRobot import LOGGER 4 | from GabiBraunRobot.modules.users import get_user_id 5 | from telegram import Message, MessageEntity 6 | from telegram.error import BadRequest 7 | 8 | 9 | def id_from_reply(message): 10 | prev_message = message.reply_to_message 11 | if not prev_message: 12 | return None, None 13 | user_id = prev_message.from_user.id 14 | res = message.text.split(None, 1) 15 | if len(res) < 2: 16 | return user_id, "" 17 | return user_id, res[1] 18 | 19 | 20 | def extract_user(message: Message, args: List[str]) -> Optional[int]: 21 | return extract_user_and_text(message, args)[0] 22 | 23 | 24 | def extract_user_and_text(message: Message, 25 | 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( 44 | message.text) - len(text_to_parse): 45 | ent = entities[0] 46 | user_id = ent.user.id 47 | text = message.text[ent.offset + ent.length:] 48 | 49 | elif len(args) >= 1 and args[0][0] == '@': 50 | user = args[0] 51 | user_id = get_user_id(user) 52 | if not user_id: 53 | message.reply_text( 54 | "No idea who this user is. You'll be able to interact with them if " 55 | "you reply to that person's message instead, or forward one of that user's messages." 56 | ) 57 | return None, None 58 | 59 | else: 60 | user_id = user_id 61 | res = message.text.split(None, 2) 62 | if len(res) >= 3: 63 | text = res[2] 64 | 65 | elif len(args) >= 1 and args[0].isdigit(): 66 | user_id = int(args[0]) 67 | res = message.text.split(None, 2) 68 | if len(res) >= 3: 69 | text = res[2] 70 | 71 | elif prev_message: 72 | user_id, text = id_from_reply(message) 73 | 74 | else: 75 | return None, None 76 | 77 | try: 78 | message.bot.get_chat(user_id) 79 | except BadRequest as excp: 80 | if excp.message in ("User_id_invalid", "Chat not found"): 81 | message.reply_text( 82 | "I don't seem to have interacted with this user before - please forward a message from " 83 | "them to give me control! (like a voodoo doll, I need a piece of them to be able " 84 | "to execute certain commands...)") 85 | else: 86 | LOGGER.exception("Exception %s on user %s", excp.message, user_id) 87 | 88 | return None, None 89 | 90 | return user_id, text 91 | 92 | 93 | def extract_text(message) -> str: 94 | return message.text or message.caption or (message.sticker.emoji 95 | if message.sticker else None) 96 | 97 | 98 | def extract_unt_fedban(message: Message, 99 | args: List[str]) -> (Optional[int], Optional[str]): 100 | prev_message = message.reply_to_message 101 | split_text = message.text.split(None, 1) 102 | 103 | if len(split_text) < 2: 104 | return id_from_reply(message) # only option possible 105 | 106 | text_to_parse = split_text[1] 107 | 108 | text = "" 109 | 110 | entities = list(message.parse_entities([MessageEntity.TEXT_MENTION])) 111 | if len(entities) > 0: 112 | ent = entities[0] 113 | else: 114 | ent = None 115 | 116 | # if entity offset matches (command end/text start) then all good 117 | if entities and ent and ent.offset == len( 118 | message.text) - len(text_to_parse): 119 | ent = entities[0] 120 | user_id = ent.user.id 121 | text = message.text[ent.offset + ent.length:] 122 | 123 | elif len(args) >= 1 and args[0][0] == '@': 124 | user = args[0] 125 | user_id = get_user_id(user) 126 | if not user_id and not isinstance(user_id, int): 127 | message.reply_text( 128 | "I don't have that user in my db. " 129 | "You'll be able to interact with them if you reply to that person's message instead, or forward one of that user's messages." 130 | ) 131 | return None, None 132 | 133 | else: 134 | user_id = user_id 135 | res = message.text.split(None, 2) 136 | if len(res) >= 3: 137 | text = res[2] 138 | 139 | elif len(args) >= 1 and args[0].isdigit(): 140 | user_id = int(args[0]) 141 | res = message.text.split(None, 2) 142 | if len(res) >= 3: 143 | text = res[2] 144 | 145 | elif prev_message: 146 | user_id, text = id_from_reply(message) 147 | 148 | else: 149 | return None, None 150 | 151 | try: 152 | message.bot.get_chat(user_id) 153 | except BadRequest as excp: 154 | if excp.message in ("User_id_invalid", 155 | "Chat not found") and not isinstance(user_id, int): 156 | message.reply_text( 157 | "I don't seem to have interacted with this user before " 158 | "please forward a message from them to give me control! " 159 | "(like a voodoo doll, I need a piece of them to be able to execute certain commands...)" 160 | ) 161 | return None, None 162 | elif excp.message != "Chat not found": 163 | LOGGER.exception("Exception %s on user %s", excp.message, user_id) 164 | return None, None 165 | elif not isinstance(user_id, int): 166 | return None, None 167 | 168 | return user_id, text 169 | 170 | 171 | def extract_user_fban(message: Message, args: List[str]) -> Optional[int]: 172 | return extract_unt_fedban(message, args)[0] 173 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/blsticker_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot.modules.sql import BASE, SESSION 4 | from sqlalchemy import Column, Integer, String, UnicodeText, distinct, func 5 | 6 | 7 | class StickersFilters(BASE): 8 | __tablename__ = "blacklist_stickers" 9 | chat_id = Column(String(14), primary_key=True) 10 | trigger = Column(UnicodeText, primary_key=True, nullable=False) 11 | 12 | def __init__(self, chat_id, trigger): 13 | self.chat_id = str(chat_id) # ensure string 14 | self.trigger = trigger 15 | 16 | def __repr__(self): 17 | return "" % (self.trigger, self.chat_id) 18 | 19 | def __eq__(self, other): 20 | return bool( 21 | isinstance(other, StickersFilters) and 22 | self.chat_id == other.chat_id and self.trigger == other.trigger) 23 | 24 | 25 | class StickerSettings(BASE): 26 | __tablename__ = "blsticker_settings" 27 | chat_id = Column(String(14), primary_key=True) 28 | blacklist_type = Column(Integer, default=1) 29 | value = Column(UnicodeText, default="0") 30 | 31 | def __init__(self, chat_id, blacklist_type=1, value="0"): 32 | self.chat_id = str(chat_id) 33 | self.blacklist_type = blacklist_type 34 | self.value = value 35 | 36 | def __repr__(self): 37 | return "<{} will executing {} for blacklist trigger.>".format( 38 | self.chat_id, self.blacklist_type) 39 | 40 | 41 | StickersFilters.__table__.create(checkfirst=True) 42 | StickerSettings.__table__.create(checkfirst=True) 43 | 44 | STICKERS_FILTER_INSERTION_LOCK = threading.RLock() 45 | STICKSET_FILTER_INSERTION_LOCK = threading.RLock() 46 | 47 | CHAT_STICKERS = {} 48 | CHAT_BLSTICK_BLACKLISTS = {} 49 | 50 | 51 | def add_to_stickers(chat_id, trigger): 52 | with STICKERS_FILTER_INSERTION_LOCK: 53 | stickers_filt = StickersFilters(str(chat_id), trigger) 54 | 55 | SESSION.merge(stickers_filt) # merge to avoid duplicate key issues 56 | SESSION.commit() 57 | global CHAT_STICKERS 58 | if CHAT_STICKERS.get(str(chat_id), set()) == set(): 59 | CHAT_STICKERS[str(chat_id)] = {trigger} 60 | else: 61 | CHAT_STICKERS.get(str(chat_id), set()).add(trigger) 62 | 63 | 64 | def rm_from_stickers(chat_id, trigger): 65 | with STICKERS_FILTER_INSERTION_LOCK: 66 | stickers_filt = SESSION.query(StickersFilters).get( 67 | (str(chat_id), trigger)) 68 | if stickers_filt: 69 | if trigger in CHAT_STICKERS.get(str(chat_id), 70 | set()): # sanity check 71 | CHAT_STICKERS.get(str(chat_id), set()).remove(trigger) 72 | 73 | SESSION.delete(stickers_filt) 74 | SESSION.commit() 75 | return True 76 | 77 | SESSION.close() 78 | return False 79 | 80 | 81 | def get_chat_stickers(chat_id): 82 | return CHAT_STICKERS.get(str(chat_id), set()) 83 | 84 | 85 | def num_stickers_filters(): 86 | try: 87 | return SESSION.query(StickersFilters).count() 88 | finally: 89 | SESSION.close() 90 | 91 | 92 | def num_stickers_chat_filters(chat_id): 93 | try: 94 | return SESSION.query(StickersFilters.chat_id).filter( 95 | StickersFilters.chat_id == str(chat_id)).count() 96 | finally: 97 | SESSION.close() 98 | 99 | 100 | def num_stickers_filter_chats(): 101 | try: 102 | return SESSION.query(func.count(distinct( 103 | StickersFilters.chat_id))).scalar() 104 | finally: 105 | SESSION.close() 106 | 107 | 108 | def set_blacklist_strength(chat_id, blacklist_type, value): 109 | # for blacklist_type 110 | # 0 = nothing 111 | # 1 = delete 112 | # 2 = warn 113 | # 3 = mute 114 | # 4 = kick 115 | # 5 = ban 116 | # 6 = tban 117 | # 7 = tmute 118 | with STICKSET_FILTER_INSERTION_LOCK: 119 | global CHAT_BLSTICK_BLACKLISTS 120 | curr_setting = SESSION.query(StickerSettings).get(str(chat_id)) 121 | if not curr_setting: 122 | curr_setting = StickerSettings( 123 | chat_id, blacklist_type=int(blacklist_type), value=value) 124 | 125 | curr_setting.blacklist_type = int(blacklist_type) 126 | curr_setting.value = str(value) 127 | CHAT_BLSTICK_BLACKLISTS[str(chat_id)] = { 128 | 'blacklist_type': int(blacklist_type), 129 | 'value': value 130 | } 131 | 132 | SESSION.add(curr_setting) 133 | SESSION.commit() 134 | 135 | 136 | def get_blacklist_setting(chat_id): 137 | try: 138 | setting = CHAT_BLSTICK_BLACKLISTS.get(str(chat_id)) 139 | if setting: 140 | return setting['blacklist_type'], setting['value'] 141 | else: 142 | return 1, "0" 143 | 144 | finally: 145 | SESSION.close() 146 | 147 | 148 | def __load_CHAT_STICKERS(): 149 | global CHAT_STICKERS 150 | try: 151 | chats = SESSION.query(StickersFilters.chat_id).distinct().all() 152 | for (chat_id,) in chats: # remove tuple by ( ,) 153 | CHAT_STICKERS[chat_id] = [] 154 | 155 | all_filters = SESSION.query(StickersFilters).all() 156 | for x in all_filters: 157 | CHAT_STICKERS[x.chat_id] += [x.trigger] 158 | 159 | CHAT_STICKERS = {x: set(y) for x, y in CHAT_STICKERS.items()} 160 | 161 | finally: 162 | SESSION.close() 163 | 164 | 165 | def __load_chat_stickerset_blacklists(): 166 | global CHAT_BLSTICK_BLACKLISTS 167 | try: 168 | chats_settings = SESSION.query(StickerSettings).all() 169 | for x in chats_settings: # remove tuple by ( ,) 170 | CHAT_BLSTICK_BLACKLISTS[x.chat_id] = { 171 | 'blacklist_type': x.blacklist_type, 172 | 'value': x.value 173 | } 174 | 175 | finally: 176 | SESSION.close() 177 | 178 | 179 | def migrate_chat(old_chat_id, new_chat_id): 180 | with STICKERS_FILTER_INSERTION_LOCK: 181 | chat_filters = SESSION.query(StickersFilters).filter( 182 | StickersFilters.chat_id == str(old_chat_id)).all() 183 | for filt in chat_filters: 184 | filt.chat_id = str(new_chat_id) 185 | SESSION.commit() 186 | 187 | 188 | __load_CHAT_STICKERS() 189 | __load_chat_stickerset_blacklists() 190 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/chatbot.py: -------------------------------------------------------------------------------- 1 | import html 2 | # AI module using Intellivoid's Coffeehouse API by @TheRealPhoenix 3 | from time import sleep, time 4 | 5 | import GabiBraunRobot.modules.sql.chatbot_sql as sql 6 | from coffeehouse.api import API 7 | from coffeehouse.exception import CoffeeHouseError as CFError 8 | from coffeehouse.lydia import LydiaAI 9 | from GabiBraunRobot import AI_API_KEY, OWNER_ID, SUPPORT_CHAT, dispatcher 10 | from GabiBraunRobot.modules.helper_funcs.chat_status import user_admin 11 | from GabiBraunRobot.modules.helper_funcs.filters import CustomFilters 12 | from GabiBraunRobot.modules.log_channel import gloggable 13 | from telegram import Update 14 | from telegram.error import BadRequest, RetryAfter, Unauthorized 15 | from telegram.ext import (CallbackContext, CommandHandler, Filters, 16 | MessageHandler, run_async) 17 | from telegram.utils.helpers import mention_html 18 | 19 | CoffeeHouseAPI = API(AI_API_KEY) 20 | api_client = LydiaAI(CoffeeHouseAPI) 21 | 22 | 23 | @run_async 24 | @user_admin 25 | @gloggable 26 | def add_chat(update: Update, context: CallbackContext): 27 | global api_client 28 | chat = update.effective_chat 29 | msg = update.effective_message 30 | user = update.effective_user 31 | is_chat = sql.is_chat(chat.id) 32 | if chat.type == "private": 33 | msg.reply_text("You can't enable AI in PM.") 34 | return 35 | 36 | if not is_chat: 37 | ses = api_client.create_session() 38 | ses_id = str(ses.id) 39 | expires = str(ses.expires) 40 | sql.set_ses(chat.id, ses_id, expires) 41 | msg.reply_text("AI successfully enabled for this chat!") 42 | message = ( 43 | f"{html.escape(chat.title)}:\n" 44 | f"#AI_ENABLED\n" 45 | f"Admin: {mention_html(user.id, html.escape(user.first_name))}\n" 46 | ) 47 | return message 48 | else: 49 | msg.reply_text("AI is already enabled for this chat!") 50 | return "" 51 | 52 | 53 | @run_async 54 | @user_admin 55 | @gloggable 56 | def remove_chat(update: Update, context: CallbackContext): 57 | msg = update.effective_message 58 | chat = update.effective_chat 59 | user = update.effective_user 60 | is_chat = sql.is_chat(chat.id) 61 | if not is_chat: 62 | msg.reply_text("AI isn't enabled here in the first place!") 63 | return "" 64 | else: 65 | sql.rem_chat(chat.id) 66 | msg.reply_text("AI disabled successfully!") 67 | message = ( 68 | f"{html.escape(chat.title)}:\n" 69 | f"#AI_DISABLED\n" 70 | f"Admin: {mention_html(user.id, html.escape(user.first_name))}\n" 71 | ) 72 | return message 73 | 74 | 75 | def check_message(context: CallbackContext, message): 76 | reply_msg = message.reply_to_message 77 | if message.text.lower() == "gabi": 78 | return True 79 | if reply_msg: 80 | if reply_msg.from_user.id == context.bot.get_me().id: 81 | return True 82 | else: 83 | return False 84 | 85 | 86 | @run_async 87 | def chatbot(update: Update, context: CallbackContext): 88 | global api_client 89 | msg = update.effective_message 90 | chat_id = update.effective_chat.id 91 | is_chat = sql.is_chat(chat_id) 92 | bot = context.bot 93 | if not is_chat: 94 | return 95 | if msg.text and not msg.document: 96 | if not check_message(context, msg): 97 | return 98 | sesh, exp = sql.get_ses(chat_id) 99 | query = msg.text 100 | try: 101 | if int(exp) < time(): 102 | ses = api_client.create_session() 103 | ses_id = str(ses.id) 104 | expires = str(ses.expires) 105 | sql.set_ses(chat_id, ses_id, expires) 106 | sesh, exp = sql.get_ses(chat_id) 107 | except ValueError: 108 | pass 109 | try: 110 | bot.send_chat_action(chat_id, action='typing') 111 | rep = api_client.think_thought(sesh, query) 112 | sleep(0.3) 113 | msg.reply_text(rep, timeout=60) 114 | except CFError as e: 115 | pass 116 | #bot.send_message(OWNER_ID, 117 | # f"Chatbot error: {e} occurred in {chat_id}!") 118 | 119 | 120 | @run_async 121 | def list_chatbot_chats(update: Update, context: CallbackContext): 122 | chats = sql.get_all_chats() 123 | text = "AI-Enabled Chats\n" 124 | for chat in chats: 125 | try: 126 | x = context.bot.get_chat(int(*chat)) 127 | name = x.title if x.title else x.first_name 128 | text += f"• {name}\n" 129 | except BadRequest: 130 | sql.rem_chat(*chat) 131 | except Unauthorized: 132 | sql.rem_chat(*chat) 133 | except RetryAfter as e: 134 | sleep(e.retry_after) 135 | update.effective_message.reply_text(text, parse_mode="HTML") 136 | 137 | 138 | __help__ = f""" 139 | Chatbot utilizes the CoffeeHouse API and allows Saitama to talk and provides a more interactive group chat experience. 140 | 141 | *Commands:* 142 | *Admins only:* 143 | • `/addchat`*:* Enables Chatbot mode in the chat. 144 | • `/rmchat`*:* Disables Chatbot mode in the chat. 145 | 146 | Reports bugs at @{SUPPORT_CHAT} 147 | *Powered by CoffeeHouse* (https://coffeehouse.intellivoid.net/) from @Intellivoid 148 | """ 149 | 150 | ADD_CHAT_HANDLER = CommandHandler("addchat", add_chat) 151 | REMOVE_CHAT_HANDLER = CommandHandler("rmchat", remove_chat) 152 | CHATBOT_HANDLER = MessageHandler( 153 | Filters.text & (~Filters.regex(r"^#[^\s]+") & ~Filters.regex(r"^!") 154 | & ~Filters.regex(r"^\/")), chatbot) 155 | LIST_CB_CHATS_HANDLER = CommandHandler( 156 | "listaichats", list_chatbot_chats, filters=CustomFilters.dev_filter) 157 | # Filters for ignoring #note messages, !commands and sed. 158 | 159 | dispatcher.add_handler(ADD_CHAT_HANDLER) 160 | dispatcher.add_handler(REMOVE_CHAT_HANDLER) 161 | dispatcher.add_handler(CHATBOT_HANDLER) 162 | dispatcher.add_handler(LIST_CB_CHATS_HANDLER) 163 | 164 | __mod_name__ = "Chatbot" 165 | __command_list__ = ["addchat", "rmchat", "listaichats"] 166 | __handlers__ = [ 167 | ADD_CHAT_HANDLER, REMOVE_CHAT_HANDLER, CHATBOT_HANDLER, 168 | LIST_CB_CHATS_HANDLER 169 | ] 170 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/blacklist_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from sqlalchemy import func, distinct, Column, String, UnicodeText, Integer 4 | 5 | from GabiBraunRobot.modules.sql import SESSION, BASE 6 | 7 | 8 | class BlackListFilters(BASE): 9 | __tablename__ = "blacklist" 10 | chat_id = Column(String(14), primary_key=True) 11 | trigger = Column(UnicodeText, primary_key=True, nullable=False) 12 | 13 | def __init__(self, chat_id, trigger): 14 | self.chat_id = str(chat_id) # ensure string 15 | self.trigger = trigger 16 | 17 | def __repr__(self): 18 | return "" % (self.trigger, self.chat_id) 19 | 20 | def __eq__(self, other): 21 | return bool( 22 | isinstance(other, BlackListFilters) and 23 | self.chat_id == other.chat_id and self.trigger == other.trigger) 24 | 25 | 26 | class BlacklistSettings(BASE): 27 | __tablename__ = "blacklist_settings" 28 | chat_id = Column(String(14), primary_key=True) 29 | blacklist_type = Column(Integer, default=1) 30 | value = Column(UnicodeText, default="0") 31 | 32 | def __init__(self, chat_id, blacklist_type=1, value="0"): 33 | self.chat_id = str(chat_id) 34 | self.blacklist_type = blacklist_type 35 | self.value = value 36 | 37 | def __repr__(self): 38 | return "<{} will executing {} for blacklist trigger.>".format( 39 | self.chat_id, self.blacklist_type) 40 | 41 | 42 | BlackListFilters.__table__.create(checkfirst=True) 43 | BlacklistSettings.__table__.create(checkfirst=True) 44 | 45 | BLACKLIST_FILTER_INSERTION_LOCK = threading.RLock() 46 | BLACKLIST_SETTINGS_INSERTION_LOCK = threading.RLock() 47 | 48 | CHAT_BLACKLISTS = {} 49 | CHAT_SETTINGS_BLACKLISTS = {} 50 | 51 | 52 | def add_to_blacklist(chat_id, trigger): 53 | with BLACKLIST_FILTER_INSERTION_LOCK: 54 | blacklist_filt = BlackListFilters(str(chat_id), trigger) 55 | 56 | SESSION.merge(blacklist_filt) # merge to avoid duplicate key issues 57 | SESSION.commit() 58 | global CHAT_BLACKLISTS 59 | if CHAT_BLACKLISTS.get(str(chat_id), set()) == set(): 60 | CHAT_BLACKLISTS[str(chat_id)] = {trigger} 61 | else: 62 | CHAT_BLACKLISTS.get(str(chat_id), set()).add(trigger) 63 | 64 | 65 | def rm_from_blacklist(chat_id, trigger): 66 | with BLACKLIST_FILTER_INSERTION_LOCK: 67 | blacklist_filt = SESSION.query(BlackListFilters).get( 68 | (str(chat_id), trigger)) 69 | if blacklist_filt: 70 | if trigger in CHAT_BLACKLISTS.get(str(chat_id), 71 | set()): # sanity check 72 | CHAT_BLACKLISTS.get(str(chat_id), set()).remove(trigger) 73 | 74 | SESSION.delete(blacklist_filt) 75 | SESSION.commit() 76 | return True 77 | 78 | SESSION.close() 79 | return False 80 | 81 | 82 | def get_chat_blacklist(chat_id): 83 | return CHAT_BLACKLISTS.get(str(chat_id), set()) 84 | 85 | 86 | def num_blacklist_filters(): 87 | try: 88 | return SESSION.query(BlackListFilters).count() 89 | finally: 90 | SESSION.close() 91 | 92 | 93 | def num_blacklist_chat_filters(chat_id): 94 | try: 95 | return (SESSION.query(BlackListFilters.chat_id).filter( 96 | BlackListFilters.chat_id == str(chat_id)).count()) 97 | finally: 98 | SESSION.close() 99 | 100 | 101 | def num_blacklist_filter_chats(): 102 | try: 103 | return SESSION.query(func.count(distinct( 104 | BlackListFilters.chat_id))).scalar() 105 | finally: 106 | SESSION.close() 107 | 108 | 109 | def set_blacklist_strength(chat_id, blacklist_type, value): 110 | # for blacklist_type 111 | # 0 = nothing 112 | # 1 = delete 113 | # 2 = warn 114 | # 3 = mute 115 | # 4 = kick 116 | # 5 = ban 117 | # 6 = tban 118 | # 7 = tmute 119 | with BLACKLIST_SETTINGS_INSERTION_LOCK: 120 | global CHAT_SETTINGS_BLACKLISTS 121 | curr_setting = SESSION.query(BlacklistSettings).get(str(chat_id)) 122 | if not curr_setting: 123 | curr_setting = BlacklistSettings( 124 | chat_id, blacklist_type=int(blacklist_type), value=value) 125 | 126 | curr_setting.blacklist_type = int(blacklist_type) 127 | curr_setting.value = str(value) 128 | CHAT_SETTINGS_BLACKLISTS[str(chat_id)] = { 129 | "blacklist_type": int(blacklist_type), 130 | "value": value, 131 | } 132 | 133 | SESSION.add(curr_setting) 134 | SESSION.commit() 135 | 136 | 137 | def get_blacklist_setting(chat_id): 138 | try: 139 | setting = CHAT_SETTINGS_BLACKLISTS.get(str(chat_id)) 140 | if setting: 141 | return setting["blacklist_type"], setting["value"] 142 | else: 143 | return 1, "0" 144 | 145 | finally: 146 | SESSION.close() 147 | 148 | 149 | def __load_chat_blacklists(): 150 | global CHAT_BLACKLISTS 151 | try: 152 | chats = SESSION.query(BlackListFilters.chat_id).distinct().all() 153 | for (chat_id,) in chats: # remove tuple by ( ,) 154 | CHAT_BLACKLISTS[chat_id] = [] 155 | 156 | all_filters = SESSION.query(BlackListFilters).all() 157 | for x in all_filters: 158 | CHAT_BLACKLISTS[x.chat_id] += [x.trigger] 159 | 160 | CHAT_BLACKLISTS = {x: set(y) for x, y in CHAT_BLACKLISTS.items()} 161 | 162 | finally: 163 | SESSION.close() 164 | 165 | 166 | def __load_chat_settings_blacklists(): 167 | global CHAT_SETTINGS_BLACKLISTS 168 | try: 169 | chats_settings = SESSION.query(BlacklistSettings).all() 170 | for x in chats_settings: # remove tuple by ( ,) 171 | CHAT_SETTINGS_BLACKLISTS[x.chat_id] = { 172 | "blacklist_type": x.blacklist_type, 173 | "value": x.value, 174 | } 175 | 176 | finally: 177 | SESSION.close() 178 | 179 | 180 | def migrate_chat(old_chat_id, new_chat_id): 181 | with BLACKLIST_FILTER_INSERTION_LOCK: 182 | chat_filters = ( 183 | SESSION.query(BlackListFilters).filter( 184 | BlackListFilters.chat_id == str(old_chat_id)).all()) 185 | for filt in chat_filters: 186 | filt.chat_id = str(new_chat_id) 187 | SESSION.commit() 188 | 189 | 190 | __load_chat_blacklists() 191 | __load_chat_settings_blacklists() 192 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/sql/users_sql.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from GabiBraunRobot import dispatcher 4 | from GabiBraunRobot.modules.sql import BASE, SESSION 5 | from sqlalchemy import (Column, ForeignKey, Integer, String, UnicodeText, 6 | UniqueConstraint, func) 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( 40 | String(14), 41 | ForeignKey("chats.chat_id", onupdate="CASCADE", ondelete="CASCADE"), 42 | nullable=False) 43 | user = Column( 44 | Integer, 45 | ForeignKey("users.user_id", onupdate="CASCADE", ondelete="CASCADE"), 46 | nullable=False) 47 | __table_args__ = (UniqueConstraint('chat', 'user', 48 | name='_chat_members_uc'),) 49 | 50 | def __init__(self, chat, user): 51 | self.chat = chat 52 | self.user = user 53 | 54 | def __repr__(self): 55 | return "".format( 56 | self.user.username, self.user.user_id, self.chat.chat_name, 57 | 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( 98 | ChatMembers.chat == chat.chat_id, 99 | ChatMembers.user == user.user_id).first() 100 | if not member: 101 | chat_member = ChatMembers(chat.chat_id, user.user_id) 102 | SESSION.add(chat_member) 103 | 104 | SESSION.commit() 105 | 106 | 107 | def get_userid_by_name(username): 108 | try: 109 | return SESSION.query(Users).filter( 110 | func.lower(Users.username) == username.lower()).all() 111 | finally: 112 | SESSION.close() 113 | 114 | 115 | def get_name_by_userid(user_id): 116 | try: 117 | return SESSION.query(Users).get(Users.user_id == int(user_id)).first() 118 | finally: 119 | SESSION.close() 120 | 121 | 122 | def get_chat_members(chat_id): 123 | try: 124 | return SESSION.query(ChatMembers).filter( 125 | ChatMembers.chat == str(chat_id)).all() 126 | finally: 127 | SESSION.close() 128 | 129 | 130 | def get_all_chats(): 131 | try: 132 | return SESSION.query(Chats).all() 133 | finally: 134 | SESSION.close() 135 | 136 | 137 | def get_all_users(): 138 | try: 139 | return SESSION.query(Users).all() 140 | finally: 141 | SESSION.close() 142 | 143 | 144 | def get_user_num_chats(user_id): 145 | try: 146 | return SESSION.query(ChatMembers).filter( 147 | ChatMembers.user == int(user_id)).count() 148 | finally: 149 | SESSION.close() 150 | 151 | 152 | def get_user_com_chats(user_id): 153 | try: 154 | chat_members = SESSION.query(ChatMembers).filter( 155 | ChatMembers.user == int(user_id)).all() 156 | return [i.chat for i in chat_members] 157 | finally: 158 | SESSION.close() 159 | 160 | 161 | def num_chats(): 162 | try: 163 | return SESSION.query(Chats).count() 164 | finally: 165 | SESSION.close() 166 | 167 | 168 | def num_users(): 169 | try: 170 | return SESSION.query(Users).count() 171 | finally: 172 | SESSION.close() 173 | 174 | 175 | def migrate_chat(old_chat_id, new_chat_id): 176 | with INSERTION_LOCK: 177 | chat = SESSION.query(Chats).get(str(old_chat_id)) 178 | if chat: 179 | chat.chat_id = str(new_chat_id) 180 | SESSION.commit() 181 | 182 | chat_members = SESSION.query(ChatMembers).filter( 183 | ChatMembers.chat == str(old_chat_id)).all() 184 | for member in chat_members: 185 | member.chat = str(new_chat_id) 186 | SESSION.commit() 187 | 188 | 189 | ensure_bot_in_db() 190 | 191 | 192 | def del_user(user_id): 193 | with INSERTION_LOCK: 194 | curr = SESSION.query(Users).get(user_id) 195 | if curr: 196 | SESSION.delete(curr) 197 | SESSION.commit() 198 | return True 199 | 200 | ChatMembers.query.filter(ChatMembers.user == user_id).delete() 201 | SESSION.commit() 202 | SESSION.close() 203 | return False 204 | 205 | 206 | def rem_chat(chat_id): 207 | with INSERTION_LOCK: 208 | chat = SESSION.query(Chats).get(str(chat_id)) 209 | if chat: 210 | SESSION.delete(chat) 211 | SESSION.commit() 212 | else: 213 | SESSION.close() 214 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/afk.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import time 3 | 4 | from telegram import Message, User 5 | from telegram import MessageEntity, ParseMode 6 | from telegram.error import BadRequest 7 | from telegram.ext import Filters, MessageHandler, run_async 8 | 9 | from GabiBraunRobot import dispatcher 10 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler, DisableAbleMessageHandler 11 | from GabiBraunRobot.modules.sql.afk_redis import start_afk, end_afk, is_user_afk, afk_reason 12 | from GabiBraunRobot import REDIS 13 | from GabiBraunRobot.modules.users import get_user_id 14 | 15 | from GabiBraunRobot.modules.helper_funcs.alternate import send_message 16 | from GabiBraunRobot.modules.helper_funcs.readable_time import get_readable_time 17 | 18 | AFK_GROUP = 7 19 | AFK_REPLY_GROUP = 8 20 | 21 | @run_async 22 | def afk(update, context): 23 | args = update.effective_message.text.split(None, 1) 24 | user = update.effective_user 25 | if not user: # ignore channels 26 | return 27 | 28 | if user.id == 777000: 29 | return 30 | start_afk_time = time.time() 31 | if len(args) >= 2: 32 | reason = args[1] 33 | else: 34 | reason = "none" 35 | start_afk(update.effective_user.id, reason) 36 | REDIS.set(f'afk_time_{update.effective_user.id}', start_afk_time) 37 | fname = update.effective_user.first_name 38 | try: 39 | update.effective_message.reply_text( 40 | "{} is now Away!".format(fname)) 41 | except BadRequest: 42 | pass 43 | 44 | @run_async 45 | def no_longer_afk(update, context): 46 | user = update.effective_user 47 | message = update.effective_message 48 | if not user: # ignore channels 49 | return 50 | 51 | if not is_user_afk(user.id): #Check if user is afk or not 52 | return 53 | end_afk_time = get_readable_time((time.time() - float(REDIS.get(f'afk_time_{user.id}')))) 54 | REDIS.delete(f'afk_time_{user.id}') 55 | res = end_afk(user.id) 56 | if res: 57 | if message.new_chat_members: #dont say msg 58 | return 59 | firstname = update.effective_user.first_name 60 | try: 61 | message.reply_text( 62 | "{} is no longer AFK!\nTime you were AFK for: {}".format(firstname, end_afk_time)) 63 | except Exception: 64 | return 65 | 66 | 67 | @run_async 68 | def reply_afk(update, context): 69 | message = update.effective_message 70 | userc = update.effective_user 71 | userc_id = userc.id 72 | if message.entities and message.parse_entities( 73 | [MessageEntity.TEXT_MENTION, MessageEntity.MENTION]): 74 | entities = message.parse_entities( 75 | [MessageEntity.TEXT_MENTION, MessageEntity.MENTION]) 76 | 77 | chk_users = [] 78 | for ent in entities: 79 | if ent.type == MessageEntity.TEXT_MENTION: 80 | user_id = ent.user.id 81 | fst_name = ent.user.first_name 82 | 83 | if user_id in chk_users: 84 | return 85 | chk_users.append(user_id) 86 | 87 | elif ent.type == MessageEntity.MENTION: 88 | user_id = get_user_id(message.text[ent.offset:ent.offset + 89 | ent.length]) 90 | if not user_id: 91 | # Should never happen, since for a user to become AFK they must have spoken. Maybe changed username? 92 | return 93 | 94 | if user_id in chk_users: 95 | return 96 | chk_users.append(user_id) 97 | 98 | try: 99 | chat = context.bot.get_chat(user_id) 100 | except BadRequest: 101 | print("Error: Could not fetch userid {} for AFK module". 102 | format(user_id)) 103 | return 104 | fst_name = chat.first_name 105 | 106 | else: 107 | return 108 | 109 | check_afk(update, context, user_id, fst_name, userc_id) 110 | 111 | elif message.reply_to_message: 112 | user_id = message.reply_to_message.from_user.id 113 | fst_name = message.reply_to_message.from_user.first_name 114 | check_afk(update, context, user_id, fst_name, userc_id) 115 | 116 | 117 | def check_afk(update, context, user_id, fst_name, userc_id): 118 | if is_user_afk(user_id): 119 | reason = afk_reason(user_id) 120 | since_afk = get_readable_time((time.time() - float(REDIS.get(f'afk_time_{user_id}')))) 121 | if reason == "none": 122 | if int(userc_id) == int(user_id): 123 | return 124 | res = "{} is AFK!\nSince: {}".format(fst_name, since_afk) 125 | update.effective_message.reply_text(res) 126 | else: 127 | if int(userc_id) == int(user_id): 128 | return 129 | res = "{} is AFK! Says it's because of:\n{}\nSince: {}".format(fst_name, reason, since_afk) 130 | update.effective_message.reply_text(res) 131 | 132 | 133 | def __user_info__(user_id): 134 | is_afk = is_user_afk(user_id) 135 | text = "" 136 | if is_afk: 137 | since_afk = get_readable_time((time.time() - float(REDIS.get(f'afk_time_{user_id}')))) 138 | text = "This user is currently afk (away from keyboard)." 139 | text += f"\nSince: {since_afk}" 140 | 141 | else: 142 | text = "This user is currently isn't afk (away from keyboard)." 143 | return text 144 | 145 | 146 | def __gdpr__(user_id): 147 | end_afk(user_id) 148 | 149 | 150 | 151 | __mod_name__ = "AFK" 152 | 153 | 154 | __help__ = """ 155 | - /afk : Mark yourself as AFK. 156 | - brb : Same as the afk command, but not a command.\n 157 | When marked as AFK, any mentions will be replied to with a message stating that you're not available! 158 | """ 159 | 160 | 161 | AFK_HANDLER = DisableAbleCommandHandler("afk", afk) 162 | AFK_REGEX_HANDLER = MessageHandler(Filters.regex("(?i)brb"), afk) 163 | NO_AFK_HANDLER = MessageHandler(Filters.all & Filters.group, no_longer_afk) 164 | AFK_REPLY_HANDLER = MessageHandler(Filters.all & Filters.group, reply_afk) 165 | 166 | dispatcher.add_handler(AFK_HANDLER, AFK_GROUP) 167 | dispatcher.add_handler(AFK_REGEX_HANDLER, AFK_GROUP) 168 | dispatcher.add_handler(NO_AFK_HANDLER, AFK_GROUP) 169 | dispatcher.add_handler(AFK_REPLY_HANDLER, AFK_REPLY_GROUP) 170 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/math.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import pynewtonmath as newton 4 | from GabiBraunRobot import dispatcher 5 | from GabiBraunRobot.modules.disable import DisableAbleCommandHandler 6 | from telegram import Update 7 | from telegram.ext import CallbackContext, run_async 8 | 9 | 10 | @run_async 11 | def simplify(update: Update, context: CallbackContext): 12 | args = context.args 13 | message = update.effective_message 14 | message.reply_text(newton.simplify('{}'.format(args[0]))) 15 | 16 | 17 | @run_async 18 | def factor(update: Update, context: CallbackContext): 19 | args = context.args 20 | message = update.effective_message 21 | message.reply_text(newton.factor('{}'.format(args[0]))) 22 | 23 | 24 | @run_async 25 | def derive(update: Update, context: CallbackContext): 26 | args = context.args 27 | message = update.effective_message 28 | message.reply_text(newton.derive('{}'.format(args[0]))) 29 | 30 | 31 | @run_async 32 | def integrate(update: Update, context: CallbackContext): 33 | args = context.args 34 | message = update.effective_message 35 | message.reply_text(newton.integrate('{}'.format(args[0]))) 36 | 37 | 38 | @run_async 39 | def zeroes(update: Update, context: CallbackContext): 40 | args = context.args 41 | message = update.effective_message 42 | message.reply_text(newton.zeroes('{}'.format(args[0]))) 43 | 44 | 45 | @run_async 46 | def tangent(update: Update, context: CallbackContext): 47 | args = context.args 48 | message = update.effective_message 49 | message.reply_text(newton.tangent('{}'.format(args[0]))) 50 | 51 | 52 | @run_async 53 | def area(update: Update, context: CallbackContext): 54 | args = context.args 55 | message = update.effective_message 56 | message.reply_text(newton.area('{}'.format(args[0]))) 57 | 58 | 59 | @run_async 60 | def cos(update: Update, context: CallbackContext): 61 | args = context.args 62 | message = update.effective_message 63 | message.reply_text(math.cos(int(args[0]))) 64 | 65 | 66 | @run_async 67 | def sin(update: Update, context: CallbackContext): 68 | args = context.args 69 | message = update.effective_message 70 | message.reply_text(math.sin(int(args[0]))) 71 | 72 | 73 | @run_async 74 | def tan(update: Update, context: CallbackContext): 75 | args = context.args 76 | message = update.effective_message 77 | message.reply_text(math.tan(int(args[0]))) 78 | 79 | 80 | @run_async 81 | def arccos(update: Update, context: CallbackContext): 82 | args = context.args 83 | message = update.effective_message 84 | message.reply_text(math.acos(int(args[0]))) 85 | 86 | 87 | @run_async 88 | def arcsin(update: Update, context: CallbackContext): 89 | args = context.args 90 | message = update.effective_message 91 | message.reply_text(math.asin(int(args[0]))) 92 | 93 | 94 | @run_async 95 | def arctan(update: Update, context: CallbackContext): 96 | args = context.args 97 | message = update.effective_message 98 | message.reply_text(math.atan(int(args[0]))) 99 | 100 | 101 | @run_async 102 | def abs(update: Update, context: CallbackContext): 103 | args = context.args 104 | message = update.effective_message 105 | message.reply_text(math.fabs(int(args[0]))) 106 | 107 | 108 | @run_async 109 | def log(update: Update, context: CallbackContext): 110 | args = context.args 111 | message = update.effective_message 112 | message.reply_text(math.log(int(args[0]))) 113 | 114 | 115 | __help__ = """ 116 | Solves complex math problems using https://newton.now.sh 117 | • `/math`*:* Math `/math 2^2+2(2)` 118 | • `/factor`*:* Factor `/factor x^2 + 2x` 119 | • `/derive`*:* Derive `/derive x^2+2x` 120 | • `/integrate`*:* Integrate `/integrate x^2+2x` 121 | • `/zeroes`*:* Find 0's `/zeroes x^2+2x` 122 | • `/tangent`*:* Find Tangent `/tangent 2lx^3` 123 | • `/area`*:* Area Under Curve `/area 2:4lx^3` 124 | • `/cos`*:* Cosine `/cos pi` 125 | • `/sin`*:* Sine `/sin 0` 126 | • `/tan`*:* Tangent `/tan 0` 127 | • `/arccos`*:* Inverse Cosine `/arccos 1` 128 | • `/arcsin`*:* Inverse Sine `/arcsin 0` 129 | • `/arctan`*:* Inverse Tangent `/arctan 0` 130 | • `/abs`*:* Absolute Value `/abs -1` 131 | • `/log`*:* Logarithm `/log 2l8` 132 | 133 | _Keep in mind_: To find the tangent line of a function at a certain x value, send the request as c|f(x) where c is the given x value and f(x) is the function expression, the separator is a vertical bar '|'. See the table above for an example request. 134 | To find the area under a function, send the request as c:d|f(x) where c is the starting x value, d is the ending x value, and f(x) is the function under which you want the curve between the two x values. 135 | To compute fractions, enter expressions as numerator(over)denominator. For example, to process 2/4 you must send in your expression as 2(over)4. The result expression will be in standard math notation (1/2, 3/4). 136 | """ 137 | 138 | __mod_name__ = "Math" 139 | 140 | SIMPLIFY_HANDLER = DisableAbleCommandHandler("math", simplify) 141 | FACTOR_HANDLER = DisableAbleCommandHandler("factor", factor) 142 | DERIVE_HANDLER = DisableAbleCommandHandler("derive", derive) 143 | INTEGRATE_HANDLER = DisableAbleCommandHandler("integrate", integrate) 144 | ZEROES_HANDLER = DisableAbleCommandHandler("zeroes", zeroes) 145 | TANGENT_HANDLER = DisableAbleCommandHandler("tangent", tangent) 146 | AREA_HANDLER = DisableAbleCommandHandler("area", area) 147 | COS_HANDLER = DisableAbleCommandHandler("cos", cos) 148 | SIN_HANDLER = DisableAbleCommandHandler("sin", sin) 149 | TAN_HANDLER = DisableAbleCommandHandler("tan", tan) 150 | ARCCOS_HANDLER = DisableAbleCommandHandler("arccos", arccos) 151 | ARCSIN_HANDLER = DisableAbleCommandHandler("arcsin", arcsin) 152 | ARCTAN_HANDLER = DisableAbleCommandHandler("arctan", arctan) 153 | ABS_HANDLER = DisableAbleCommandHandler("abs", abs) 154 | LOG_HANDLER = DisableAbleCommandHandler("log", log) 155 | 156 | dispatcher.add_handler(SIMPLIFY_HANDLER) 157 | dispatcher.add_handler(FACTOR_HANDLER) 158 | dispatcher.add_handler(DERIVE_HANDLER) 159 | dispatcher.add_handler(INTEGRATE_HANDLER) 160 | dispatcher.add_handler(ZEROES_HANDLER) 161 | dispatcher.add_handler(TANGENT_HANDLER) 162 | dispatcher.add_handler(AREA_HANDLER) 163 | dispatcher.add_handler(COS_HANDLER) 164 | dispatcher.add_handler(SIN_HANDLER) 165 | dispatcher.add_handler(TAN_HANDLER) 166 | dispatcher.add_handler(ARCCOS_HANDLER) 167 | dispatcher.add_handler(ARCSIN_HANDLER) 168 | dispatcher.add_handler(ARCTAN_HANDLER) 169 | dispatcher.add_handler(ABS_HANDLER) 170 | dispatcher.add_handler(LOG_HANDLER) 171 | -------------------------------------------------------------------------------- /GabiBraunRobot/modules/users.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from time import sleep 3 | 4 | from telegram import TelegramError, Update 5 | from telegram.error import BadRequest, Unauthorized 6 | from telegram.ext import (CallbackContext, CommandHandler, Filters, 7 | MessageHandler, run_async) 8 | 9 | import GabiBraunRobot.modules.sql.users_sql as sql 10 | from GabiBraunRobot import DEV_USERS, LOGGER, OWNER_ID, dispatcher 11 | from GabiBraunRobot.modules.helper_funcs.chat_status import dev_plus, sudo_plus 12 | from GabiBraunRobot.modules.sql.users_sql import get_all_users 13 | 14 | USERS_GROUP = 4 15 | CHAT_GROUP = 5 16 | DEV_AND_MORE = DEV_USERS.append(int(OWNER_ID)) 17 | 18 | 19 | def get_user_id(username): 20 | # ensure valid userid 21 | if len(username) <= 5: 22 | return None 23 | 24 | if username.startswith('@'): 25 | username = username[1:] 26 | 27 | users = sql.get_userid_by_name(username) 28 | 29 | if not users: 30 | return None 31 | 32 | elif len(users) == 1: 33 | return users[0].user_id 34 | 35 | else: 36 | for user_obj in users: 37 | try: 38 | userdat = dispatcher.bot.get_chat(user_obj.user_id) 39 | if userdat.username == username: 40 | return userdat.id 41 | 42 | except BadRequest as excp: 43 | if excp.message == 'Chat not found': 44 | pass 45 | else: 46 | LOGGER.exception("Error extracting user ID") 47 | 48 | return None 49 | 50 | 51 | @run_async 52 | @dev_plus 53 | def broadcast(update: Update, context: CallbackContext): 54 | to_send = update.effective_message.text.split(None, 1) 55 | 56 | if len(to_send) >= 2: 57 | to_group = False 58 | to_user = False 59 | if to_send[0] == '/broadcastgroups': 60 | to_group = True 61 | if to_send[0] == '/broadcastusers': 62 | to_user = True 63 | else: 64 | to_group = to_user = True 65 | chats = sql.get_all_chats() or [] 66 | users = get_all_users() 67 | failed = 0 68 | failed_user = 0 69 | if to_group: 70 | for chat in chats: 71 | try: 72 | context.bot.sendMessage( 73 | int(chat.chat_id), 74 | to_send[1], 75 | parse_mode="MARKDOWN", 76 | disable_web_page_preview=True) 77 | sleep(0.1) 78 | except TelegramError: 79 | failed += 1 80 | if to_user: 81 | for user in users: 82 | try: 83 | context.bot.sendMessage( 84 | int(user.user_id), 85 | to_send[1], 86 | parse_mode="MARKDOWN", 87 | disable_web_page_preview=True) 88 | sleep(0.1) 89 | except TelegramError: 90 | failed_user += 1 91 | update.effective_message.reply_text( 92 | f"Broadcast complete.\nGroups failed: {failed}.\nUsers failed: {failed_user}." 93 | ) 94 | 95 | 96 | @run_async 97 | def log_user(update: Update, context: CallbackContext): 98 | chat = update.effective_chat 99 | msg = update.effective_message 100 | 101 | sql.update_user(msg.from_user.id, msg.from_user.username, chat.id, 102 | chat.title) 103 | 104 | if msg.reply_to_message: 105 | sql.update_user(msg.reply_to_message.from_user.id, 106 | msg.reply_to_message.from_user.username, chat.id, 107 | chat.title) 108 | 109 | if msg.forward_from: 110 | sql.update_user(msg.forward_from.id, msg.forward_from.username) 111 | 112 | 113 | @run_async 114 | @sudo_plus 115 | def chats(update: Update, context: CallbackContext): 116 | all_chats = sql.get_all_chats() or [] 117 | chatfile = 'List of chats.\n0. Chat name | Chat ID | Members count\n' 118 | P = 1 119 | for chat in all_chats: 120 | try: 121 | curr_chat = context.bot.getChat(chat.chat_id) 122 | bot_member = curr_chat.get_member(context.bot.id) 123 | chat_members = curr_chat.get_members_count(context.bot.id) 124 | chatfile += "{}. {} | {} | {}\n".format(P, chat.chat_name, 125 | chat.chat_id, chat_members) 126 | P = P + 1 127 | except: 128 | pass 129 | 130 | with BytesIO(str.encode(chatfile)) as output: 131 | output.name = "groups_list.txt" 132 | update.effective_message.reply_document( 133 | document=output, 134 | filename="groups_list.txt", 135 | caption="Here be the list of groups in my database.") 136 | 137 | 138 | @run_async 139 | def chat_checker(update: Update, context: CallbackContext): 140 | bot = context.bot 141 | try: 142 | if update.effective_message.chat.get_member( 143 | bot.id).can_send_messages is False: 144 | bot.leaveChat(update.effective_message.chat.id) 145 | except Unauthorized: 146 | pass 147 | 148 | 149 | def __user_info__(user_id): 150 | if user_id in [777000, 1087968824]: 151 | return """╘══「 Groups count: ??? 」""" 152 | if user_id == dispatcher.bot.id: 153 | return """╘══「 Groups count: ??? 」""" 154 | num_chats = sql.get_user_num_chats(user_id) 155 | return f"""╘══「 Groups count: {num_chats} 」""" 156 | 157 | 158 | def __stats__(): 159 | return f"• {sql.num_users()} users, across {sql.num_chats()} chats" 160 | 161 | 162 | def __migrate__(old_chat_id, new_chat_id): 163 | sql.migrate_chat(old_chat_id, new_chat_id) 164 | 165 | 166 | __help__ = "" # no help string 167 | 168 | BROADCAST_HANDLER = CommandHandler( 169 | ["broadcastall", "broadcastusers", "broadcastgroups"], broadcast) 170 | USER_HANDLER = MessageHandler(Filters.all & Filters.group, log_user) 171 | CHAT_CHECKER_HANDLER = MessageHandler(Filters.all & Filters.group, chat_checker) 172 | CHATLIST_HANDLER = CommandHandler("groups", chats) 173 | 174 | dispatcher.add_handler(USER_HANDLER, USERS_GROUP) 175 | dispatcher.add_handler(BROADCAST_HANDLER) 176 | dispatcher.add_handler(CHATLIST_HANDLER) 177 | dispatcher.add_handler(CHAT_CHECKER_HANDLER, CHAT_GROUP) 178 | 179 | __mod_name__ = "Users" 180 | __handlers__ = [(USER_HANDLER, USERS_GROUP), BROADCAST_HANDLER, 181 | CHATLIST_HANDLER] 182 | --------------------------------------------------------------------------------