├── .config └── fontconfig │ └── fonts.conf ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── ci.yml │ └── stale.yml ├── .gitignore ├── Aptfile ├── Dockerfile ├── Dockerfile.lite ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── assets └── getter_botlogs.png ├── docker-compose.yml ├── docs ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── getter ├── __init__.py ├── __main__.py ├── config.py ├── core │ ├── __init__.py │ ├── base_client.py │ ├── constants.py │ ├── db │ │ ├── __init__.py │ │ ├── afk_db.py │ │ ├── collections_db.py │ │ ├── engine.py │ │ ├── gban_db.py │ │ ├── gdel_db.py │ │ ├── globals_db.py │ │ ├── gmute_db.py │ │ └── pmpermit_db.py │ ├── decorators.py │ ├── functions.py │ ├── helper.py │ ├── patched │ │ ├── __init__.py │ │ ├── client.py │ │ ├── conversation.py │ │ └── message.py │ ├── patcher.py │ ├── property.py │ ├── startup.py │ ├── tools.py │ └── utils.py ├── logger.py └── plugins │ ├── __init__.py │ ├── _watcher.py │ ├── admintools.py │ ├── afk.py │ ├── beautify.py │ ├── bot.py │ ├── chat.py │ ├── core.py │ ├── custom │ └── __init__.py │ ├── deepfry.py │ ├── delayspam.py │ ├── dev.py │ ├── downloader.py │ ├── fake.py │ ├── fakeaction.py │ ├── fun.py │ ├── games.py │ ├── globaltools.py │ ├── help.py │ ├── info.py │ ├── loader.py │ ├── mention.py │ ├── mutual.py │ ├── pmpermit.py │ ├── profiles.py │ ├── randoms.py │ ├── screenshot.py │ ├── sudo.py │ ├── supertools.py │ ├── text.py │ ├── translate.py │ ├── updater.py │ ├── usage.py │ ├── utility.py │ ├── vctools.py │ └── webtools.py ├── heroku.yml ├── lite-compose.yml ├── local-compose.yml ├── manifest.json ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── run.py ├── runtime.txt ├── sample_config.env ├── scripts ├── __init__.py ├── autoreload.py └── prettyjson.py ├── strgen.py └── version.py /.config/fontconfig/fonts.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sans-serif 6 | 7 | Roboto 8 | Noto Color Emoji 9 | 10 | 11 | 12 | serif 13 | 14 | Roboto Condensed 15 | Noto Color Emoji 16 | 17 | 18 | 19 | monospace 20 | 21 | Hack 22 | Noto Color Emoji 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [{*.json,*.py,*.sh}] 13 | indent_size = 4 14 | 15 | [*.json] 16 | insert_final_newline = ignore 17 | 18 | [*.md] 19 | max_line_length = 0 20 | trim_trailing_whitespace = false 21 | 22 | [LICENSE] 23 | insert_final_newline = ignore 24 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [illvart] 2 | custom: ["https://linktr.ee/illvart"] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Python Version** 24 | Provide exact python version used 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 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Additional context** 14 | Add any other context or screenshots about the feature request here. 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request, workflow_dispatch] 3 | jobs: 4 | linter: 5 | name: Run linting and format code 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest] 10 | python-version: ["3.10", "3.11", "3.12"] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: styfle/cancel-workflow-action@0.12.0 14 | with: 15 | all_but_latest: true 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - uses: actions/cache@v4 21 | if: startsWith(runner.os, 'Linux') 22 | with: 23 | path: ~/.cache/pip 24 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }} 25 | restore-keys: | 26 | ${{ runner.os }}-pip- 27 | - name: Install dependencies 28 | run: | 29 | python3 -m pip install -U pip 30 | pip3 install -r requirements.txt 31 | if [ -f requirements-dev.txt ]; then pip3 install -r requirements-dev.txt; fi 32 | - name: Linting and format code 33 | run: python3 -m run --lint 34 | - uses: illvart/beautysh-action@latest 35 | with: 36 | args: "*.sh --indent-size 2 &>/dev/null" 37 | - uses: stefanzweifel/git-auto-commit-action@v5 38 | with: 39 | commit_message: "[action] ci: auto-fixes" 40 | commit_options: "--no-verify" 41 | commit_user_name: kastaid 42 | commit_user_email: ${{ secrets.EMAIL }} 43 | commit_author: kastaid <${{ secrets.EMAIL }}> 44 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.' 13 | days-before-stale: 30 14 | days-before-close: 7 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .progress 132 | .vscode/* 133 | 134 | config.env 135 | *.db 136 | *.db-* 137 | *.session* 138 | 139 | logs 140 | backup 141 | *s_list.csv 142 | 143 | */plugins/custom/* 144 | !*/plugins/custom/__init__.py 145 | 146 | .ruff* 147 | -------------------------------------------------------------------------------- /Aptfile: -------------------------------------------------------------------------------- 1 | tree 2 | neofetch 3 | fonts-roboto 4 | fonts-hack-ttf 5 | fonts-noto-color-emoji 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | FROM python:3.12-slim-bookworm 9 | 10 | ENV TZ=Asia/Jakarta \ 11 | DEBIAN_FRONTEND=noninteractive \ 12 | VIRTUAL_ENV=/opt/venv \ 13 | PATH=/opt/venv/bin:/app/bin:$PATH \ 14 | CHROME_BIN=/usr/bin/google-chrome \ 15 | DISPLAY=:99 16 | ARG LANG=en_US 17 | ARG CHROME_VERSION=124.0.6367.207 18 | 19 | WORKDIR /app 20 | COPY requirements.txt /tmp/ 21 | COPY .config /app/.config 22 | 23 | RUN set -eux && \ 24 | apt-get -qqy update && \ 25 | apt-get -qqy install --no-install-recommends \ 26 | tini \ 27 | gnupg2 \ 28 | git \ 29 | curl \ 30 | wget \ 31 | tree \ 32 | neofetch \ 33 | fonts-roboto \ 34 | fonts-hack-ttf \ 35 | fonts-noto-color-emoji \ 36 | locales \ 37 | tzdata \ 38 | ffmpeg \ 39 | cairosvg \ 40 | libjpeg-dev \ 41 | libpng-dev \ 42 | libnss3 \ 43 | libatk1.0-0 \ 44 | libatk-bridge2.0-0 \ 45 | libcups2 \ 46 | libxcomposite1 \ 47 | libxdamage1 \ 48 | libxrandr2 \ 49 | libxcb1 \ 50 | libxext6 \ 51 | libxfixes3 \ 52 | libasound2 \ 53 | libgtk-3-0 \ 54 | xdg-utils \ 55 | ca-certificates \ 56 | unzip \ 57 | build-essential && \ 58 | localedef --quiet -i ${LANG} -c -f UTF-8 -A /usr/share/locale/locale.alias ${LANG}.UTF-8 && \ 59 | cp /usr/share/zoneinfo/${TZ} /etc/localtime && \ 60 | echo "${TZ}" > /etc/timezone && \ 61 | dpkg-reconfigure --force -f noninteractive tzdata >/dev/null 2>&1 && \ 62 | curl -sS -o /tmp/chrome.zip https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip && \ 63 | unzip -qq /tmp/chrome.zip -d /opt/ && \ 64 | mv /opt/chrome-linux64 /opt/chrome && \ 65 | ln -s /opt/chrome/chrome $CHROME_BIN && \ 66 | chmod +x $CHROME_BIN && \ 67 | rm -f /tmp/chrome.zip && \ 68 | curl -sS -o /tmp/chromedriver.zip https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chromedriver-linux64.zip && \ 69 | unzip -qq /tmp/chromedriver.zip -d /opt/ && \ 70 | mv /opt/chromedriver-linux64/chromedriver /usr/bin/chromedriver && \ 71 | chmod +x /usr/bin/chromedriver && \ 72 | rm -f /tmp/chromedriver.zip && \ 73 | command -v chromedriver && \ 74 | $(command -v chromedriver) --version && \ 75 | cp -rf .config ~/ && \ 76 | python -m venv $VIRTUAL_ENV && \ 77 | $VIRTUAL_ENV/bin/pip install --upgrade pip && \ 78 | $VIRTUAL_ENV/bin/pip install --no-cache-dir --disable-pip-version-check --default-timeout=100 -r /tmp/requirements.txt && \ 79 | apt-get -qqy purge --auto-remove \ 80 | locales \ 81 | unzip \ 82 | build-essential && \ 83 | apt-get -qqy clean && \ 84 | rm -rf -- /var/lib/apt/lists/* /var/cache/apt/archives/* /usr/share/man/* /usr/share/doc/* /tmp/* /var/tmp/* 85 | 86 | COPY . . 87 | 88 | ENTRYPOINT ["/usr/bin/tini", "--"] 89 | CMD ["python", "-m", "getter"] 90 | -------------------------------------------------------------------------------- /Dockerfile.lite: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | FROM python:3.12-slim-bookworm 9 | 10 | ENV TZ=Asia/Jakarta \ 11 | DEBIAN_FRONTEND=noninteractive \ 12 | VIRTUAL_ENV=/opt/venv \ 13 | PATH=/opt/venv/bin:/app/bin:$PATH 14 | ARG LANG=en_US 15 | 16 | WORKDIR /app 17 | COPY requirements.txt /tmp/ 18 | 19 | RUN set -eux && \ 20 | apt-get -qqy update && \ 21 | apt-get -qqy install --no-install-recommends \ 22 | tini \ 23 | gnupg2 \ 24 | git \ 25 | locales \ 26 | tzdata \ 27 | build-essential && \ 28 | localedef --quiet -i ${LANG} -c -f UTF-8 -A /usr/share/locale/locale.alias ${LANG}.UTF-8 && \ 29 | cp /usr/share/zoneinfo/${TZ} /etc/localtime && \ 30 | echo "${TZ}" > /etc/timezone && \ 31 | dpkg-reconfigure --force -f noninteractive tzdata >/dev/null 2>&1 && \ 32 | python -m venv $VIRTUAL_ENV && \ 33 | $VIRTUAL_ENV/bin/pip install --upgrade pip && \ 34 | $VIRTUAL_ENV/bin/pip install --no-cache-dir --disable-pip-version-check --default-timeout=100 -r /tmp/requirements.txt && \ 35 | apt-get -qqy purge --auto-remove \ 36 | locales \ 37 | build-essential && \ 38 | apt-get -qqy clean && \ 39 | rm -rf -- /var/lib/apt/lists/* /var/cache/apt/archives/* /etc/apt/sources.list.d/* /usr/share/man/* /usr/share/doc/* /tmp/* /var/tmp/* 40 | 41 | COPY . . 42 | 43 | ENTRYPOINT ["/usr/bin/tini", "--"] 44 | CMD ["python", "-m", "getter"] 45 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 -m getter 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `getter` 2 | 3 | > Get and put users (scraping) to the target **group/channel** efficiently, correctly and safety. 4 | 5 |

6 | CI 7 | Python 8 | Version 9 | LICENSE 10 | Telegram 11 |

12 | 13 | ``` 14 | #include 15 | /* 16 | * Your Telegram account may get banned. 17 | * We are not responsible for any improper use of this userbot. 18 | * This userbot is specific for scraping members with some helpfull commands. 19 | * 20 | * If you ended up spamming groups, getting reported left and right, 21 | * and you ended up in being fight with Telegram 22 | * and at the end Telegram Team deleted your account. DON'T BLAME US. 23 | * 24 | * No personal support will be provided / We won't spoon feed you. 25 | * If you need help ask in our support group 26 | * and we or our friends will try to help you. 27 | */ 28 | ``` 29 | 30 | ## Table of Contents 31 | 32 |
33 | Details 34 | 35 | - [Requirements](#requirements) 36 | - [STRING_SESSION](#string_session) 37 | - [Deploy](#deploy) 38 | - [Locally](#locally) 39 | - [Config](#config) 40 | - [Run](#run) 41 | - [Example Plugin](#example-plugin) 42 | - [Supports](#sparkling_heart-supports) 43 | - [Credits and Thanks](#credits-and-thanks) 44 | - [Contributing](#contributing) 45 | - [License](#license) 46 | 47 |
48 | 49 | ## Requirements 50 | 51 | - Python 3.11.x 52 | - Linux (Recommend Debian/Ubuntu) 53 | - Telegram `API_ID` and `API_HASH` from [API development tools](https://my.telegram.org) 54 | 55 | ### STRING_SESSION 56 | 57 | Generate `STRING_SESSION` using [@strgen_bot](https://telegram.me/strgen_bot) or [replit](https://replit.com/@notudope/strgen) or run locally `python3 strgen.py` 58 | 59 | ### Deploy 60 | 61 | To deploy please visit our channel at [@kastaid](https://telegram.me/kastaid). 62 | 63 | ### Locally 64 | 65 | #### Config 66 | 67 | Create and save `config.env` file at main directory and fill with the example config file at [sample_config.env](https://github.com/kastaid/getter/blob/main/sample_config.env). 68 | 69 | #### Run 70 | 71 | ```sh 72 | # Production 73 | pip3 install -r requirements.txt 74 | python3 -m getter 75 | 76 | # Development 77 | pip3 install -r requirements.txt 78 | pip3 install -r requirements-dev.txt 79 | python3 -m run --watch 80 | ``` 81 | 82 | More commands `python3 -m run -h` 83 | 84 | ### Example Plugin 85 | 86 | Clone the repo, then create and save plugin at `./getter/plugins/plugin_name.py`. 87 | 88 | This Example Works Everywhere. (e.g. Groups, Personal Chats) 89 | ```python 90 | from . import kasta_cmd 91 | @kasta_cmd(pattern="hi") 92 | async def _(event): 93 | await event.eor("Hello **World**.") 94 | ``` 95 | 96 | This Example Works Only In Personal Chats. 97 | ```python 98 | from . import kasta_cmd 99 | @kasta_cmd(pattern="hi", func=lambda e: e.is_private) 100 | async def _(event): 101 | await event.eor("Hello **World**.") 102 | ``` 103 | 104 | This Example Works Only In Channels. 105 | ```python 106 | from . import kasta_cmd 107 | @kasta_cmd(pattern="hi", func=lambda e: e.is_channel and e.chat.broadcast) 108 | async def _(event): 109 | await event.eor("Hello **World**.") 110 | ``` 111 | 112 | This Example Works Only In Groups. 113 | ```python 114 | from . import kasta_cmd 115 | @kasta_cmd(pattern="hi", func=lambda e: e.is_group) 116 | async def _(event): 117 | await event.eor("Hello **World**.") 118 | ``` 119 | 120 | This Example Works Only In Groups or Channels. 121 | ```python 122 | from . import kasta_cmd 123 | @kasta_cmd(pattern="hi", func=lambda e: not e.is_private) 124 | async def _(event): 125 | await event.eor("Hello **World**.") 126 | ``` 127 | 128 | ## :sparkling_heart: Supports 129 | 130 | This project is open source and free to use under the [license](#license). However, if you are using this project and happy with it or just want to encourage me to continue creating stuff please donate! 131 | 132 | ## Credits and Thanks 133 | 134 | * [LonamiWebs](https://github.com/LonamiWebs/Telethon) - Telethon 135 | * [MarshalX](https://github.com/MarshalX/tgcalls) - pytgcalls 136 | * [TeamUltroid](https://github.com/TeamUltroid) - Team Ultroid 137 | * [UsergeTeam](https://github.com/UsergeTeam) - UsergeTeam 138 | * [Dragon-Userbot](https://github.com/Dragon-Userbot) - Dragon Userbot 139 | * [TgCatUB](https://github.com/TgCatUB) - CatUserbot 140 | * [userbotindo](https://github.com/userbotindo) - Userbot Indonesia Community 141 | * [illvart](https://github.com/illvart) - Core Developer 142 | * [notudope](https://github.com/notudope) - Core Developer 143 | 144 | and [everyone](https://github.com/kastaid/getter/graphs/contributors) 🦄 145 | 146 | ## Contributing 147 | 148 | If you would like to help out with some code, check the [details](https://github.com/kastaid/getter/blob/main/docs/CONTRIBUTING.md). 149 | 150 | ## License 151 | 152 | This project is licensed under the **GNU Affero General Public License v3.0**. See the [LICENSE](https://github.com/kastaid/getter/blob/main/LICENSE) file for details. 153 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "API_ID": { 4 | "description": "Get this value from API development tools - \"my.telegram.org/apps\" at App api_id:", 5 | "required": true 6 | }, 7 | "API_HASH": { 8 | "description": "Get this value from API development tools - \"my.telegram.org/apps\" at App api_hash:", 9 | "required": true 10 | }, 11 | "STRING_SESSION": { 12 | "description": "Telethon Session from \"t.me/strgen_bot\" or \"replit.com/@notudope/strgen\"", 13 | "required": true 14 | }, 15 | "BOTLOGS": { 16 | "description": "Your BOTLOGS group id e.g: -100xxx or skip this to use autopilot instantly. YOU NOT UNDERSTAND? THEN DO NOT CHANGE 0 !!", 17 | "required": false, 18 | "value": "0" 19 | }, 20 | "HANDLER": { 21 | "description": "Initial command handler (prefix), supported characters \"github.com/kastaid/getter/blob/main/getter/config.py\". Default: [ . ]", 22 | "required": false, 23 | "value": "." 24 | }, 25 | "NO_HANDLER": { 26 | "description": "Initial command without handler (prefix)", 27 | "required": false, 28 | "value": "False" 29 | }, 30 | "TZ": { 31 | "description": "Set local timezone: Continent/Country. Get list from \"gist.github.com/notudope/9c3b8a5389293d9fe34c6c1f2484eeb3#file-timezones-txt\". Default: Asia/Jakarta", 32 | "required": false, 33 | "value": "Asia/Jakarta" 34 | }, 35 | "HEROKU_APP_NAME": { 36 | "description": "Input this App name at the top ↑", 37 | "required": true 38 | }, 39 | "HEROKU_API": { 40 | "description": "Get the API Key from \"dashboard.heroku.com/account\"", 41 | "required": true 42 | } 43 | }, 44 | "success_url": "https://t.me/kastaot", 45 | "buildpacks": [ 46 | { 47 | "url": "heroku/python" 48 | }, 49 | { 50 | "url": "https://github.com/heroku/heroku-buildpack-apt" 51 | }, 52 | { 53 | "url": "https://github.com/heroku/heroku-buildpack-google-chrome" 54 | }, 55 | { 56 | "url": "https://github.com/heroku/heroku-buildpack-chromedriver" 57 | }, 58 | { 59 | "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest" 60 | } 61 | ], 62 | "stack": "heroku-22", 63 | "addons": [ 64 | { 65 | "plan": "heroku-postgresql", 66 | "options": { 67 | "version": "14" 68 | } 69 | } 70 | ] 71 | } -------------------------------------------------------------------------------- /assets/getter_botlogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kastaid/getter/6e246595698e7243cd398f3269f2bd29a20a8ae1/assets/getter_botlogs.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | worker: 3 | build: . 4 | environment: 5 | API_ID: $API_ID 6 | API_HASH: $API_HASH 7 | STRING_SESSION: $STRING_SESSION 8 | DATABASE_URL: $DATABASE_URL 9 | BOTLOGS: $BOTLOGS 10 | HANDLER: $HANDLER 11 | NO_HANDLER: $NO_HANDLER 12 | TZ: $TZ 13 | LANG_CODE: $LANG_CODE 14 | restart: on-failure 15 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Fork it! 4 | 2. Clone your repository 5 | 3. Create your feature branch: `git checkout -b my-new-feature` 6 | 4. Commit your changes: `git commit -m 'Add some feature'` 7 | 5. Push to the branch: `git push origin my-new-feature` 8 | 6. Submit a pull request 9 | -------------------------------------------------------------------------------- /getter/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F401 2 | # getter < https://t.me/kastaid > 3 | # Copyright (C) 2022-present kastaid 4 | # 5 | # This file is a part of < https://github.com/kastaid/getter/ > 6 | # Please read the GNU Affero General Public License in 7 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 8 | 9 | import sys 10 | from asyncio import set_event_loop 11 | from concurrent.futures import ThreadPoolExecutor 12 | from multiprocessing import cpu_count 13 | from pathlib import Path 14 | from platform import python_version 15 | from shutil import rmtree 16 | from time import time 17 | import uvloop 18 | from telethon.tl.alltlobjects import LAYER as __layer__ 19 | from telethon.version import __version__ as __tlversion__ 20 | from version import __version__ 21 | 22 | StartTime = time() 23 | __license__ = "GNU Affero General Public License v3.0" 24 | __copyright__ = "Getter Copyright (C) 2022-present kastaid" 25 | __pyversion__ = python_version() 26 | 27 | if not sys.platform.startswith("linux"): 28 | print(f"You must use Linux platform, currently {sys.platform}. Quitting...") 29 | sys.exit(1) 30 | if "/com.termux" in sys.executable: 31 | print("You are detected using Termux, maybe the functionality will not work normally.") 32 | 33 | Root: Path = Path(__file__).parent.parent 34 | LOOP = uvloop.new_event_loop() 35 | set_event_loop(LOOP) 36 | WORKERS = min(32, (cpu_count() or 1) + 4) 37 | EXECUTOR = ThreadPoolExecutor(max_workers=WORKERS) 38 | 39 | for d in ( 40 | "logs/", 41 | "downloads/", 42 | ): 43 | if not (Root / d).exists(): 44 | (Root / d).mkdir(parents=True, exist_ok=True) 45 | else: 46 | for _ in (Root / d).rglob("*"): 47 | if _.is_dir(): 48 | rmtree(_, ignore_errors=True) 49 | else: 50 | _.unlink(missing_ok=True) 51 | [_.unlink(missing_ok=True) for _ in Root.rglob("*s_list.csv")] 52 | 53 | del sys, set_event_loop, Path, ThreadPoolExecutor, cpu_count, python_version, rmtree, time, uvloop 54 | -------------------------------------------------------------------------------- /getter/__main__.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | import sys 10 | from importlib import import_module 11 | from time import monotonic 12 | from requests.packages import urllib3 13 | import getter.core.patched # noqa 14 | from . import ( 15 | __license__, 16 | __copyright__, 17 | __version__, 18 | __tlversion__, 19 | __layer__, 20 | __pyversion__, 21 | ) 22 | from .config import Var, hl 23 | from .core.base_client import getter_app 24 | from .core.db import db_connect 25 | from .core.helper import plugins_help, jdata 26 | from .core.property import do_not_remove_credit 27 | from .core.startup import ( 28 | trap, 29 | migrations, 30 | autopilot, 31 | verify, 32 | autous, 33 | finishing, 34 | ) 35 | from .core.utils import time_formatter 36 | from .logger import LOG 37 | 38 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 39 | 40 | success_msg = ">> Visit @kastaid for Updates !!" 41 | if Var.DEV_MODE: 42 | trap() 43 | print( 44 | "\nDEV_MODE config enabled.\n" 45 | + "Some codes and functionality will not work normally.\n" 46 | + "If you need to run in Production then comment DEV_MODE or set value to False or remove them!\n" 47 | ) 48 | 49 | 50 | async def main() -> None: 51 | await db_connect() 52 | await jdata.sudo_users() 53 | migrations() 54 | await autopilot() 55 | await verify() 56 | LOG.info(">> Load Plugins...") 57 | load = monotonic() 58 | plugins = getter_app.all_plugins 59 | for p in plugins: 60 | try: 61 | plugin = ( 62 | "".join(("getter.plugins.", p["path"])) 63 | if p["path"].startswith("custom") 64 | else "".join(("getter.", p["path"])) 65 | ) 66 | import_module(plugin) 67 | LOG.success("[+] " + p["name"]) 68 | except Exception as err: 69 | LOG.exception(f"[-] {p['name']} : {err}") 70 | from .plugins.afk import handle_afk 71 | from .plugins.pmpermit import handle_pmpermit 72 | 73 | await asyncio.gather(*[handle_afk(), handle_pmpermit()]) 74 | loaded_time = time_formatter((monotonic() - load) * 1000) 75 | loaded_msg = ">> Loaded Plugins: {} , Commands: {} (took {}) : {}".format( 76 | plugins_help.count, 77 | plugins_help.total, 78 | loaded_time, 79 | tuple(_["name"] for _ in plugins), 80 | ) 81 | LOG.info(loaded_msg) 82 | do_not_remove_credit() 83 | python_msg = f">> Python Version - {__pyversion__}" 84 | telethon_msg = f">> Telethon Version - {__tlversion__} [Layer: {__layer__}]" 85 | launch_msg = f">> 🚀 Getter v{__version__} launch ({getter_app.full_name} - {getter_app.uid}) in {getter_app.uptime} with handler [ {hl}ping ]" 86 | LOG.info(python_msg) 87 | LOG.info(telethon_msg) 88 | LOG.info(launch_msg) 89 | LOG.info(__license__) 90 | LOG.info(__copyright__) 91 | await autous(getter_app.uid) 92 | await finishing(launch_msg) 93 | LOG.success(success_msg) 94 | 95 | 96 | if __name__ == "__main__": 97 | try: 98 | getter_app.run_in_loop(main()) 99 | getter_app.run() 100 | except ( 101 | KeyboardInterrupt, 102 | SystemExit, 103 | ): 104 | pass 105 | except Exception as err: 106 | LOG.exception(f"[MAIN_ERROR] : {err}") 107 | finally: 108 | LOG.warning("[MAIN] - Getter Stopped...") 109 | sys.exit(0) 110 | -------------------------------------------------------------------------------- /getter/config.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from base64 import b64decode 9 | from os import getenv 10 | from string import ascii_lowercase 11 | from typing import Any 12 | from zoneinfo import ZoneInfo 13 | from dotenv import load_dotenv, find_dotenv 14 | 15 | load_dotenv(find_dotenv("config.env")) 16 | 17 | 18 | def tobool(val: str) -> int | None: 19 | """ 20 | Convert a string representation of truth to true (1) or false (0). 21 | https://github.com/python/cpython/blob/main/Lib/distutils/util.py 22 | """ 23 | val = val.lower() 24 | if val in {"y", "yes", "t", "true", "on", "1"}: 25 | return 1 26 | if val in {"n", "no", "f", "false", "off", "0"}: 27 | return 0 28 | raise ValueError(f"invalid truth value {val!r}") 29 | 30 | 31 | class Var: 32 | DEV_MODE: bool = tobool(getenv("DEV_MODE", "false").strip()) 33 | API_ID: int = int(getenv("API_ID", "0").strip()) 34 | API_HASH: str = getenv("API_HASH", "").strip() 35 | STRING_SESSION: str = getenv("STRING_SESSION", "").strip() 36 | DATABASE_URL: str = ( 37 | lambda c: ( 38 | c.replace(c.split("://")[0], "postgresql+asyncpg") if c.startswith(("postgres:", "postgresql:")) else c 39 | ) 40 | )(getenv("DATABASE_URL", "sqlite+aiosqlite:///./getter.db").strip()) 41 | BOTLOGS: int = int(getenv("BOTLOGS", "0").strip()) 42 | HANDLER: str = getenv("HANDLER", ".").strip() 43 | NO_HANDLER: bool = tobool(getenv("NO_HANDLER", "false").strip()) 44 | TZ: str = getenv("TZ", "Asia/Jakarta").strip() 45 | LANG_CODE: str = getenv("LANG_CODE", "id").lower().strip() 46 | HEROKU_APP_NAME: str = getenv("HEROKU_APP_NAME", "").strip() 47 | HEROKU_API: str = getenv("HEROKU_API", "").strip() 48 | 49 | 50 | try: 51 | tz = ZoneInfo(Var.TZ) 52 | except BaseException: 53 | _ = "Asia/Jakarta" 54 | print("An error or unknown TZ :", Var.TZ) 55 | print("Set default TZ as", _) 56 | tz = ZoneInfo(_) 57 | 58 | if not ( 59 | Var.HANDLER.lower().startswith( 60 | ( 61 | "/", 62 | ".", 63 | "!", 64 | "+", 65 | "-", 66 | "_", 67 | ";", 68 | "~", 69 | "^", 70 | "%", 71 | "&", 72 | ">", 73 | "<", 74 | *tuple(ascii_lowercase), 75 | ), 76 | ) 77 | ): 78 | hl = "." 79 | print(f"Your HANDLER [ {Var.HANDLER} ] is not supported.") 80 | print("Set default HANDLER as dot [ .command ]") 81 | else: 82 | hl = "".join(Var.HANDLER.split()) 83 | 84 | BOTLOGS_CACHE: list[int] = [] 85 | DEV_CMDS: dict[str, list[str]] = {} 86 | SUDO_CMDS: dict[str, list[str]] = {} 87 | INVITE_WORKER: dict[str, Any] = {} 88 | CALLS: dict[int, Any] = {} 89 | TESTER = {5215824623} 90 | # va, vn, en, xl 91 | DEVS = { 92 | *{int(_) for _ in b64decode("MjAwMzM2MTQxMCAxOTk4OTE4MDI0IDE3OTI0ODYxNTAgMTQ0ODQ3NzUwMQ==").split()}, 93 | *TESTER, 94 | } 95 | NOCHATS = { 96 | -1001699144606, 97 | -1001700971911, 98 | } 99 | del Any, b64decode, ascii_lowercase, ZoneInfo, load_dotenv, find_dotenv 100 | -------------------------------------------------------------------------------- /getter/core/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F401, F403 2 | # getter < https://t.me/kastaid > 3 | # Copyright (C) 2022-present kastaid 4 | # 5 | # This file is a part of < https://github.com/kastaid/getter/ > 6 | # Please read the GNU Affero General Public License in 7 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 8 | 9 | from .base_client import getter_app 10 | from .constants import * 11 | from .db import * 12 | from .decorators import kasta_cmd, sendlog 13 | from .functions import ( 14 | TELEGRAM_LINK_RE, 15 | USERNAME_RE, 16 | MSG_ID_RE, 17 | is_telegram_link, 18 | get_username, 19 | get_msg_id, 20 | mentionuser, 21 | display_name, 22 | normalize_chat_id, 23 | get_chat_id, 24 | get_text, 25 | get_user_status, 26 | get_user, 27 | is_admin, 28 | admin_check, 29 | to_privilege, 30 | parse_pre, 31 | get_media_type, 32 | ) 33 | from .helper import ( 34 | plugins_help, 35 | from_key, 36 | hk, 37 | jdata, 38 | get_botlogs, 39 | formatx_send, 40 | ) 41 | from .patched import * 42 | from .patcher import patch, patchable 43 | from .property import get_blacklisted, do_not_remove_credit 44 | from .tools import ( 45 | is_termux, 46 | aioify, 47 | import_lib, 48 | Runner, 49 | Fetch, 50 | Carbon, 51 | Screenshot, 52 | MyIp, 53 | Pinger, 54 | Telegraph, 55 | ) 56 | from .utils import ( 57 | humanbool, 58 | replace_all, 59 | md_to_html, 60 | strip_format, 61 | strip_emoji, 62 | strip_ascii, 63 | humanbytes, 64 | time_formatter, 65 | until_time, 66 | get_random_hex, 67 | get_random_alpha, 68 | mask_email, 69 | chunk, 70 | sort_dict, 71 | deep_get, 72 | to_dict, 73 | camel, 74 | snake, 75 | kebab, 76 | normalize, 77 | get_full_class_name, 78 | ) 79 | 80 | if hk.stack == "container" or not hk.is_heroku: 81 | CHROME_BIN = "/usr/bin/google-chrome" 82 | CHROME_DRIVER = "/usr/bin/chromedriver" 83 | else: 84 | CHROME_BIN = "/app/.apt/usr/bin/google-chrome" 85 | CHROME_DRIVER = "/app/.chromedriver/bin/chromedriver" 86 | -------------------------------------------------------------------------------- /getter/core/db/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F401 2 | # getter < https://t.me/kastaid > 3 | # Copyright (C) 2022-present kastaid 4 | # 5 | # This file is a part of < https://github.com/kastaid/getter/ > 6 | # Please read the GNU Affero General Public License in 7 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 8 | 9 | from .afk_db import ( 10 | is_afk, 11 | add_afk, 12 | del_afk, 13 | set_last_afk, 14 | ) 15 | from .collections_db import ( 16 | get_cols, 17 | col_list, 18 | get_col, 19 | set_col, 20 | del_col, 21 | ) 22 | from .engine import ( 23 | db_connect, 24 | db_disconnect, 25 | db_size, 26 | Model, 27 | Session, 28 | ) 29 | from .gban_db import ( 30 | all_gban, 31 | gban_list, 32 | is_gban, 33 | add_gban, 34 | del_gban, 35 | set_gban_reason, 36 | ) 37 | from .gdel_db import ( 38 | all_gdel, 39 | gdel_list, 40 | is_gdel, 41 | add_gdel, 42 | del_gdel, 43 | set_gdel_reason, 44 | ) 45 | from .globals_db import ( 46 | all_gvar, 47 | gvar_list, 48 | gvar, 49 | sgvar, 50 | dgvar, 51 | ) 52 | from .gmute_db import ( 53 | all_gmute, 54 | gmute_list, 55 | is_gmute, 56 | add_gmute, 57 | del_gmute, 58 | set_gmute_reason, 59 | ) 60 | from .pmpermit_db import ( 61 | all_allow, 62 | is_allow, 63 | allow_user, 64 | deny_user, 65 | deny_all, 66 | ) 67 | -------------------------------------------------------------------------------- /getter/core/db/afk_db.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from typing import Any 9 | from sqlalchemy import ( 10 | Column, 11 | Boolean, 12 | UnicodeText, 13 | Float, 14 | delete, 15 | insert, 16 | select, 17 | ) 18 | from sqlalchemy_json import MutableJson 19 | from .engine import Model, Session 20 | 21 | 22 | class GoAFK(Model): 23 | __tablename__ = "afk" 24 | state = Column(Boolean, primary_key=True) 25 | reason = Column(UnicodeText) 26 | start = Column(Float) 27 | last = Column(MutableJson) 28 | 29 | 30 | async def is_afk() -> GoAFK | None: 31 | async with Session() as s: 32 | try: 33 | data = (await s.execute(select(GoAFK).filter(GoAFK.state == True))).scalar_one_or_none() 34 | if data: 35 | await s.refresh(data) 36 | return data 37 | except BaseException: 38 | pass 39 | return None 40 | 41 | 42 | async def add_afk( 43 | reason: str, 44 | start: float, 45 | ) -> None: 46 | await del_afk() 47 | async with Session(True) as s: 48 | await s.execute( 49 | insert(GoAFK).values( 50 | state=True, 51 | reason=reason, 52 | start=start, 53 | last={}, 54 | ) 55 | ) 56 | 57 | 58 | async def del_afk(): 59 | if not await is_afk(): 60 | return 61 | async with Session(True) as s: 62 | await s.execute(delete(GoAFK).where(GoAFK.state == True)) 63 | 64 | 65 | async def set_last_afk( 66 | chat_id: str, 67 | msg_id: int, 68 | ) -> dict[str, Any]: 69 | afk = await is_afk() 70 | if not afk: 71 | return {} 72 | async with Session(True) as s: 73 | old_last = afk.last 74 | afk.last[chat_id] = msg_id 75 | await s.merge(afk) 76 | return old_last 77 | -------------------------------------------------------------------------------- /getter/core/db/collections_db.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from typing import Any 9 | from sqlalchemy import ( 10 | Column, 11 | String, 12 | delete, 13 | exists, 14 | insert, 15 | select, 16 | update, 17 | ) 18 | from sqlalchemy_json import MutableJson, NestedMutableJson 19 | from .engine import Model, Session 20 | 21 | 22 | class Collections(Model): 23 | __tablename__ = "collections" 24 | keyword = Column(String, primary_key=True) 25 | json = Column(MutableJson) 26 | njson = Column(NestedMutableJson) 27 | 28 | 29 | async def get_cols() -> list[Collections]: 30 | async with Session() as s: 31 | try: 32 | return (await s.execute(select(Collections).order_by(Collections.keyword.asc()))).scalars().all() 33 | except BaseException: 34 | return [] 35 | 36 | 37 | async def col_list() -> list[dict[str, Any]]: 38 | return [i.to_dict() for i in await get_cols()] 39 | 40 | 41 | async def get_col(keyword: str) -> Collections: 42 | async with Session() as s: 43 | try: 44 | data = (await s.execute(select(Collections).filter(Collections.keyword == keyword))).scalar_one_or_none() 45 | if data: 46 | await s.refresh(data) 47 | return data 48 | except BaseException: 49 | pass 50 | return {} 51 | 52 | 53 | async def set_col( 54 | keyword: str, 55 | json: Any, 56 | njson: Any = None, 57 | ) -> None: 58 | njson = njson or {} 59 | async with Session(True) as s: 60 | data = await s.execute(select(exists().where(Collections.keyword == keyword))) 61 | if data.scalar(): 62 | stmt = ( 63 | update(Collections) 64 | .where(Collections.keyword == keyword) 65 | .values( 66 | json=json, 67 | njson=njson, 68 | ) 69 | ) 70 | else: 71 | stmt = insert(Collections).values( 72 | keyword=keyword, 73 | json=json, 74 | njson=njson, 75 | ) 76 | await s.execute(stmt) 77 | 78 | 79 | async def del_col(keyword: str) -> None: 80 | async with Session(True) as s: 81 | await s.execute(delete(Collections).where(Collections.keyword == keyword)) 82 | -------------------------------------------------------------------------------- /getter/core/db/engine.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import sys 9 | from collections.abc import AsyncIterator 10 | from contextlib import asynccontextmanager 11 | import orjson 12 | from sqlalchemy.ext.asyncio import ( 13 | async_sessionmaker, 14 | AsyncSession, 15 | create_async_engine, 16 | AsyncEngine, 17 | ) 18 | from sqlalchemy.orm import DeclarativeBase 19 | from sqlalchemy.sql.expression import text 20 | from getter.config import Var 21 | from getter.logger import LOG 22 | 23 | engine = None 24 | 25 | 26 | class Model(DeclarativeBase): 27 | """ 28 | Model is an abstract base class for all SQLAlchemy ORM models , 29 | providing common columns and functionality. 30 | 31 | Methods: 32 | to_dict: Converts the current object to a dictionary. 33 | to_json: Converts the current object to a JSON string. 34 | from_json: Creates a new object of the class using the provided JSON data. 35 | __repr__: Returns a string representation of the current object. 36 | """ 37 | 38 | __abstract__ = True 39 | 40 | def to_dict(self): 41 | return {c.name: getattr(self, c.name) for c in self.__table__.columns} 42 | 43 | def to_json(self): 44 | return orjson.dumps(self.to_dict()).decode() 45 | 46 | @classmethod 47 | def from_json(cls, json_data): 48 | return cls(**orjson.loads(json_data)) 49 | 50 | def __repr__(self): 51 | return f"{self.__class__.__name__} ({self.to_dict()})" 52 | 53 | 54 | async def db_connect() -> AsyncEngine: 55 | global engine 56 | if engine is not None: 57 | return engine 58 | db_url = ( 59 | Var.DATABASE_URL.replace("sqlite:", "sqlite+aiosqlite:") 60 | if Var.DATABASE_URL.startswith("sqlite:") 61 | else Var.DATABASE_URL 62 | ) 63 | engine = create_async_engine( 64 | db_url, 65 | echo=False, 66 | json_deserializer=orjson.loads, 67 | json_serializer=lambda x: orjson.dumps(x).decode(), 68 | ) 69 | try: 70 | async with engine.connect() as conn: 71 | LOG.success("Database connected.") 72 | async with engine.begin() as conn: 73 | await conn.run_sync(Model.metadata.create_all, checkfirst=True) 74 | LOG.success("Tables created.") 75 | except Exception as err: 76 | LOG.exception(f"Unable to connect the database : {err}") 77 | await engine.dispose() 78 | sys.exit(1) 79 | return engine 80 | 81 | 82 | async def db_disconnect() -> None: 83 | db = await db_connect() 84 | await db.dispose() 85 | 86 | 87 | async def db_size() -> int: 88 | db = await db_connect() 89 | url = str(db.url) 90 | async with db.connect() as conn: 91 | if "postgresql" in url: 92 | d = url.split("/")[-1].split("?")[0] 93 | q = f"SELECT pg_database_size({d!r})" 94 | else: 95 | q = "SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()" 96 | return (await conn.execute(text(q))).scalar() 97 | 98 | 99 | @asynccontextmanager 100 | async def Session(commit: bool = False) -> AsyncIterator[AsyncSession]: 101 | if not hasattr(Session, "cached_session"): 102 | Session.cached_session = async_sessionmaker( 103 | await db_connect(), 104 | expire_on_commit=True, 105 | autocommit=False, 106 | ) 107 | async with Session.cached_session() as session: 108 | if commit: 109 | async with session.begin(): 110 | yield session 111 | else: 112 | yield session 113 | -------------------------------------------------------------------------------- /getter/core/db/gban_db.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from typing import Any 9 | from cachetools import TTLCache 10 | from sqlalchemy import ( 11 | Column, 12 | String, 13 | Float, 14 | UnicodeText, 15 | delete, 16 | insert, 17 | select, 18 | ) 19 | from .engine import Model, Session 20 | 21 | _GBAN_CACHE = TTLCache(maxsize=100, ttl=60) # 1 mins 22 | 23 | 24 | class GBan(Model): 25 | __tablename__ = "gban" 26 | user_id = Column(String, primary_key=True) 27 | date = Column(Float) 28 | reason = Column(UnicodeText) 29 | 30 | 31 | async def all_gban() -> list[GBan]: 32 | async with Session() as s: 33 | try: 34 | return (await s.execute(select(GBan).order_by(GBan.date.asc()))).scalars().all() 35 | except BaseException: 36 | return [] 37 | 38 | 39 | async def gban_list() -> list[dict[str, Any]]: 40 | return [i.to_dict() for i in await all_gban()] 41 | 42 | 43 | async def is_gban( 44 | user_id: int, 45 | use_cache: bool = False, 46 | ) -> GBan | None: 47 | user_id, value = str(user_id), None 48 | if use_cache and user_id in _GBAN_CACHE: 49 | return _GBAN_CACHE.get(user_id) 50 | async with Session() as s: 51 | try: 52 | data = (await s.execute(select(GBan).filter(GBan.user_id == user_id))).scalar_one_or_none() 53 | if data: 54 | await s.refresh(data) 55 | value = data 56 | if use_cache and not _GBAN_CACHE.get(user_id): 57 | _GBAN_CACHE[user_id] = value 58 | return value 59 | except BaseException: 60 | pass 61 | return value 62 | 63 | 64 | async def add_gban( 65 | user_id: int, 66 | date: float, 67 | reason: str = "", 68 | ) -> None: 69 | async with Session(True) as s: 70 | await s.execute( 71 | insert(GBan).values( 72 | user_id=str(user_id), 73 | date=date, 74 | reason=reason, 75 | ), 76 | ) 77 | 78 | 79 | async def del_gban(user_id: int) -> None: 80 | async with Session(True) as s: 81 | user_id = str(user_id) 82 | if user_id in _GBAN_CACHE: 83 | del _GBAN_CACHE[user_id] 84 | await s.execute(delete(GBan).where(GBan.user_id == user_id)) 85 | 86 | 87 | async def set_gban_reason( 88 | user_id: int, 89 | reason: str = "", 90 | ) -> str: 91 | gban = await is_gban(user_id) 92 | if not gban: 93 | return "" 94 | async with Session(True) as s: 95 | prev_reason = gban.reason 96 | gban.reason = reason 97 | await s.merge(gban) 98 | return prev_reason 99 | -------------------------------------------------------------------------------- /getter/core/db/gdel_db.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from typing import Any 9 | from cachetools import TTLCache 10 | from sqlalchemy import ( 11 | Column, 12 | String, 13 | Float, 14 | UnicodeText, 15 | delete, 16 | insert, 17 | select, 18 | ) 19 | from .engine import Model, Session 20 | 21 | _GDEL_CACHE = TTLCache(maxsize=100, ttl=30) # 30 sec 22 | 23 | 24 | class GDel(Model): 25 | __tablename__ = "gdel" 26 | user_id = Column(String, primary_key=True) 27 | date = Column(Float) 28 | reason = Column(UnicodeText) 29 | 30 | 31 | async def all_gdel() -> list[GDel]: 32 | async with Session() as s: 33 | try: 34 | return (await s.execute(select(GDel).order_by(GDel.date.asc()))).scalars().all() 35 | except BaseException: 36 | return [] 37 | 38 | 39 | async def gdel_list() -> list[dict[str, Any]]: 40 | return [i.to_dict() for i in await all_gdel()] 41 | 42 | 43 | async def is_gdel( 44 | user_id: int, 45 | use_cache: bool = False, 46 | ) -> GDel | None: 47 | user_id, value = str(user_id), None 48 | if use_cache and user_id in _GDEL_CACHE: 49 | return _GDEL_CACHE.get(user_id) 50 | async with Session() as s: 51 | try: 52 | data = (await s.execute(select(GDel).filter(GDel.user_id == user_id))).scalar_one_or_none() 53 | if data: 54 | await s.refresh(data) 55 | value = data 56 | if use_cache and not _GDEL_CACHE.get(user_id): 57 | _GDEL_CACHE[user_id] = value 58 | return value 59 | except BaseException: 60 | pass 61 | return value 62 | 63 | 64 | async def add_gdel( 65 | user_id: int, 66 | date: float, 67 | reason: str = "", 68 | ) -> None: 69 | async with Session(True) as s: 70 | await s.execute( 71 | insert(GDel).values( 72 | user_id=str(user_id), 73 | date=date, 74 | reason=reason, 75 | ), 76 | ) 77 | 78 | 79 | async def del_gdel(user_id: int) -> None: 80 | async with Session(True) as s: 81 | user_id = str(user_id) 82 | if user_id in _GDEL_CACHE: 83 | del _GDEL_CACHE[user_id] 84 | await s.execute(delete(GDel).where(GDel.user_id == user_id)) 85 | 86 | 87 | async def set_gdel_reason( 88 | user_id: int, 89 | reason: str = "", 90 | ) -> str: 91 | gdel = await is_gdel(user_id) 92 | if not gdel: 93 | return "" 94 | async with Session(True) as s: 95 | prev_reason = gdel.reason 96 | gdel.reason = reason 97 | await s.merge(gdel) 98 | return prev_reason 99 | -------------------------------------------------------------------------------- /getter/core/db/globals_db.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from typing import Any 9 | from cachetools import LRUCache 10 | from sqlalchemy import ( 11 | Column, 12 | String, 13 | UnicodeText, 14 | delete, 15 | exists, 16 | insert, 17 | select, 18 | update, 19 | ) 20 | from .engine import Model, Session 21 | 22 | _GVAR_CACHE = LRUCache(maxsize=float("inf")) 23 | 24 | 25 | class Globals(Model): 26 | __tablename__ = "globals" 27 | var = Column(String, primary_key=True) 28 | value = Column(UnicodeText) 29 | 30 | 31 | async def all_gvar() -> list[Globals]: 32 | async with Session() as s: 33 | try: 34 | return (await s.execute(select(Globals).order_by(Globals.var.asc()))).scalars().all() 35 | except BaseException: 36 | return [] 37 | 38 | 39 | async def gvar_list() -> list[dict[str, Any]]: 40 | return [i.to_dict() for i in await all_gvar()] 41 | 42 | 43 | async def gvar( 44 | var: str, 45 | use_cache: bool = False, 46 | ) -> str | None: 47 | value = None 48 | if use_cache and var in _GVAR_CACHE: 49 | return _GVAR_CACHE.get(var) 50 | async with Session() as s: 51 | try: 52 | data = (await s.execute(select(Globals).filter(Globals.var == var))).scalar_one_or_none() 53 | if data: 54 | await s.refresh(data) 55 | value = data.value 56 | if use_cache and not _GVAR_CACHE.get(var): 57 | _GVAR_CACHE[var] = value 58 | except BaseException: 59 | pass 60 | return value 61 | 62 | 63 | async def sgvar( 64 | var: str, 65 | value: str, 66 | ) -> None: 67 | value = str(value) 68 | async with Session(True) as s: 69 | data = await s.execute(select(exists().where(Globals.var == var))) 70 | if data.scalar(): 71 | if var in _GVAR_CACHE: 72 | del _GVAR_CACHE[var] 73 | stmt = update(Globals).where(Globals.var == var).values(value=value) 74 | else: 75 | stmt = insert(Globals).values(var=var, value=value) 76 | await s.execute(stmt) 77 | 78 | 79 | async def dgvar(var: str) -> None: 80 | async with Session(True) as s: 81 | if var in _GVAR_CACHE: 82 | del _GVAR_CACHE[var] 83 | await s.execute(delete(Globals).where(Globals.var == var)) 84 | -------------------------------------------------------------------------------- /getter/core/db/gmute_db.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from typing import Any 9 | from cachetools import TTLCache 10 | from sqlalchemy import ( 11 | Column, 12 | String, 13 | Float, 14 | UnicodeText, 15 | delete, 16 | insert, 17 | select, 18 | ) 19 | from .engine import Model, Session 20 | 21 | _GMUTE_CACHE = TTLCache(maxsize=100, ttl=60) # 1 mins 22 | 23 | 24 | class GMute(Model): 25 | __tablename__ = "gmute" 26 | user_id = Column(String, primary_key=True) 27 | date = Column(Float) 28 | reason = Column(UnicodeText) 29 | 30 | 31 | async def all_gmute() -> list[GMute]: 32 | async with Session() as s: 33 | try: 34 | return (await s.execute(select(GMute).order_by(GMute.date.asc()))).scalars().all() 35 | except BaseException: 36 | return [] 37 | 38 | 39 | async def gmute_list() -> list[dict[str, Any]]: 40 | return [i.to_dict() for i in await all_gmute()] 41 | 42 | 43 | async def is_gmute( 44 | user_id: int, 45 | use_cache: bool = False, 46 | ) -> GMute | None: 47 | user_id, value = str(user_id), None 48 | if use_cache and user_id in _GMUTE_CACHE: 49 | return _GMUTE_CACHE.get(user_id) 50 | async with Session() as s: 51 | try: 52 | data = (await s.execute(select(GMute).filter(GMute.user_id == user_id))).scalar_one_or_none() 53 | if data: 54 | await s.refresh(data) 55 | value = data 56 | if use_cache and not _GMUTE_CACHE.get(user_id): 57 | _GMUTE_CACHE[user_id] = value 58 | return value 59 | except BaseException: 60 | pass 61 | return value 62 | 63 | 64 | async def add_gmute( 65 | user_id: int, 66 | date: float, 67 | reason: str = "", 68 | ) -> None: 69 | async with Session(True) as s: 70 | await s.execute( 71 | insert(GMute).values( 72 | user_id=str(user_id), 73 | date=date, 74 | reason=reason, 75 | ), 76 | ) 77 | 78 | 79 | async def del_gmute(user_id: int) -> None: 80 | async with Session(True) as s: 81 | user_id = str(user_id) 82 | if user_id in _GMUTE_CACHE: 83 | del _GMUTE_CACHE[user_id] 84 | await s.execute(delete(GMute).where(GMute.user_id == user_id)) 85 | 86 | 87 | async def set_gmute_reason( 88 | user_id: int, 89 | reason: str = "", 90 | ) -> str: 91 | gmute = await is_gmute(user_id) 92 | if not gmute: 93 | return "" 94 | async with Session(True) as s: 95 | prev_reason = gmute.reason 96 | gmute.reason = reason 97 | await s.merge(gmute) 98 | return prev_reason 99 | -------------------------------------------------------------------------------- /getter/core/db/pmpermit_db.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from cachetools import LRUCache 9 | from sqlalchemy import ( 10 | Column, 11 | String, 12 | Float, 13 | UnicodeText, 14 | delete, 15 | insert, 16 | select, 17 | ) 18 | from .engine import Model, Session 19 | 20 | _PMPERMIT_CACHE = LRUCache(maxsize=100) 21 | 22 | 23 | class PMPermit(Model): 24 | __tablename__ = "pmpermit" 25 | user_id = Column(String, primary_key=True) 26 | date = Column(Float) 27 | reason = Column(UnicodeText) 28 | 29 | 30 | async def all_allow() -> list[PMPermit]: 31 | async with Session() as s: 32 | try: 33 | return (await s.execute(select(PMPermit).order_by(PMPermit.date.asc()))).scalars().all() 34 | except BaseException: 35 | return [] 36 | 37 | 38 | async def is_allow( 39 | user_id: int, 40 | use_cache: bool = False, 41 | ) -> PMPermit | None: 42 | user_id, value = str(user_id), None 43 | if use_cache and user_id in _PMPERMIT_CACHE: 44 | return _PMPERMIT_CACHE.get(user_id) 45 | async with Session() as s: 46 | try: 47 | data = (await s.execute(select(PMPermit).filter(PMPermit.user_id == user_id))).scalar_one_or_none() 48 | if data: 49 | await s.refresh(data) 50 | value = data 51 | if use_cache and not _PMPERMIT_CACHE.get(user_id): 52 | _PMPERMIT_CACHE[user_id] = value 53 | return value 54 | except BaseException: 55 | pass 56 | return value 57 | 58 | 59 | async def allow_user( 60 | user_id: int, 61 | date: float, 62 | reason: str = "", 63 | ) -> None: 64 | async with Session(True) as s: 65 | await s.execute( 66 | insert(PMPermit).values( 67 | user_id=str(user_id), 68 | date=date, 69 | reason=reason, 70 | ), 71 | ) 72 | 73 | 74 | async def deny_user(user_id: int) -> None: 75 | async with Session(True) as s: 76 | user_id = str(user_id) 77 | if user_id in _PMPERMIT_CACHE: 78 | del _PMPERMIT_CACHE[user_id] 79 | await s.execute(delete(PMPermit).where(PMPermit.user_id == user_id)) 80 | 81 | 82 | async def deny_all() -> None: 83 | async with Session(True) as s: 84 | await s.execute(delete(PMPermit)) 85 | -------------------------------------------------------------------------------- /getter/core/helper.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from collections import UserDict 9 | from html import escape 10 | from typing import Any 11 | from cachetools import cached, LRUCache 12 | from heroku3 import from_key 13 | from getter.config import Var, BOTLOGS_CACHE 14 | from getter.logger import LOG 15 | from .db import gvar, get_col 16 | from .utils import get_full_class_name 17 | 18 | 19 | class PluginsHelp(UserDict): 20 | def append(self, obj: dict) -> None: 21 | plug = next(iter(obj.keys())) 22 | cmds = {} 23 | for i in obj[plug]: 24 | name = next(iter(i.keys())) 25 | desc = i[name] 26 | cmds[name] = desc 27 | self[plug] = cmds 28 | 29 | @property 30 | def count(self) -> int: 31 | return len(self) 32 | 33 | @property 34 | def total(self) -> int: 35 | return sum(len(i) for i in self.values()) 36 | 37 | 38 | class JSONData: 39 | def __init__(self) -> None: 40 | self.CACHE_DATA = LRUCache(maxsize=float("inf")) 41 | 42 | async def sudos(self) -> dict[str, Any]: 43 | return getattr(await get_col("sudos"), "json", {}) 44 | 45 | async def pmwarns(self) -> dict[str, int]: 46 | return getattr(await get_col("pmwarns"), "json", {}) 47 | 48 | async def pmlasts(self) -> dict[str, int]: 49 | return getattr(await get_col("pmwarns"), "njson", {}) 50 | 51 | async def gblack(self) -> dict[str, Any]: 52 | return getattr(await get_col("gblack"), "json", {}) 53 | 54 | async def gblacklist(self) -> set[int]: 55 | result = await self.gblack() 56 | return {int(i) for i in result} 57 | 58 | async def sudo_users(self) -> list[int]: 59 | if "sudo" in self.CACHE_DATA: 60 | return self.CACHE_DATA.get("sudo", []) 61 | result = await self.sudos() 62 | users = [int(i) for i in result] 63 | if "sudo" not in self.CACHE_DATA: 64 | self.CACHE_DATA["sudo"] = users 65 | return users 66 | 67 | 68 | class Heroku: 69 | def __init__(self) -> None: 70 | self.name: str = Var.HEROKU_APP_NAME 71 | self.api: str = Var.HEROKU_API 72 | 73 | def heroku(self) -> Any: 74 | conn = None 75 | try: 76 | if self.is_heroku: 77 | conn = from_key(self.api) 78 | except BaseException as err: 79 | LOG.exception(err) 80 | return conn 81 | 82 | @property 83 | @cached(LRUCache(maxsize=512)) 84 | def stack(self) -> str: 85 | try: 86 | app = self.heroku().app(self.name) 87 | stack = app.info.stack.name 88 | except BaseException: 89 | stack = "none" 90 | return stack 91 | 92 | @property 93 | def is_heroku(self) -> bool: 94 | return bool(self.api and self.name) 95 | 96 | 97 | async def get_botlogs() -> int: 98 | if BOTLOGS_CACHE: 99 | return next(reversed(BOTLOGS_CACHE), 0) 100 | b = await gvar("BOTLOGS", use_cache=True) 101 | i = int(Var.BOTLOGS or b or 0) 102 | BOTLOGS_CACHE.append(i) 103 | return i 104 | 105 | 106 | def formatx_send(err: Exception) -> str: 107 | text = r"\\#Getter_Error//" 108 | text += f"\n
{get_full_class_name(err)}: {escape(str(err))}
" 109 | return text 110 | 111 | 112 | plugins_help = PluginsHelp() 113 | jdata = JSONData() 114 | hk = Heroku() 115 | -------------------------------------------------------------------------------- /getter/core/patched/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F401 2 | # getter < https://t.me/kastaid > 3 | # Copyright (C) 2022-present kastaid 4 | # 5 | # This file is a part of < https://github.com/kastaid/getter/ > 6 | # Please read the GNU Affero General Public License in 7 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 8 | 9 | from .client import TelegramClient 10 | from .conversation import Conversation 11 | from .message import Message 12 | -------------------------------------------------------------------------------- /getter/core/patched/client.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from random import randrange 10 | import telethon.client.telegramclient 11 | from telethon import hints, utils 12 | from telethon.tl import functions as fun, types as typ 13 | from getter.core.constants import MAX_MESSAGE_LEN 14 | from getter.core.functions import get_chat_id, get_text, get_user 15 | from getter.core.patcher import patch, patchable 16 | 17 | delattr(fun.account, "DeleteAccountRequest") 18 | 19 | 20 | @patch(telethon.client.telegramclient.TelegramClient) 21 | class TelegramClient: 22 | @patchable() 23 | async def get_id(self, entity: hints.EntityLike) -> int: 24 | try: 25 | entity = int(entity) 26 | except ValueError: 27 | pass 28 | return await self.get_peer_id(entity) 29 | 30 | @patchable() 31 | async def get_chat_id(self, *args, **kwargs) -> int | None: 32 | return await get_chat_id(*args, **kwargs) 33 | 34 | @patchable() 35 | async def get_text(self, *args, **kwargs) -> str: 36 | return await get_text(*args, **kwargs) 37 | 38 | @patchable() 39 | async def get_user(self, *args, **kwargs) -> tuple[typ.User, str] | None: 40 | return await get_user(*args, **kwargs) 41 | 42 | @patchable() 43 | async def read_chat(self, *args, **kwargs) -> bool: 44 | try: 45 | return await self.send_read_acknowledge(*args, **kwargs) 46 | except BaseException: 47 | return False 48 | 49 | @patchable() 50 | async def block(self, entity: hints.EntityLike) -> bool: 51 | try: 52 | entity = await self.get_input_entity(entity) 53 | return await self(fun.contacts.BlockRequest(entity)) 54 | except BaseException: 55 | return False 56 | 57 | @patchable() 58 | async def unblock(self, entity: hints.EntityLike) -> bool: 59 | try: 60 | entity = await self.get_input_entity(entity) 61 | return await self(fun.contacts.UnblockRequest(entity)) 62 | except BaseException: 63 | return False 64 | 65 | @patchable() 66 | async def archive(self, entity: hints.EntityLike) -> typ.Updates | None: 67 | try: 68 | return await self.edit_folder(entity, folder=1) 69 | except BaseException: 70 | return None 71 | 72 | @patchable() 73 | async def unarchive(self, entity: hints.EntityLike) -> typ.Updates | None: 74 | try: 75 | return await self.edit_folder(entity, folder=0) 76 | except BaseException: 77 | return None 78 | 79 | @patchable() 80 | async def delete_chat( 81 | self, 82 | entity: hints.EntityLike, 83 | revoke: bool = False, 84 | ) -> typ.Updates | None: 85 | try: 86 | return await self.delete_dialog(entity, revoke=revoke) 87 | except BaseException: 88 | return None 89 | 90 | @patchable() 91 | async def report_spam(self, entity: hints.EntityLike) -> bool: 92 | try: 93 | entity = await self.get_input_entity(entity) 94 | return await self(fun.messages.ReportSpamRequest(entity)) 95 | except BaseException: 96 | return False 97 | 98 | @patchable() 99 | async def send_reaction( 100 | self, 101 | entity: hints.EntityLike, 102 | message: hints.MessageIDLike, 103 | big: bool = False, 104 | add_to_recent: bool = False, 105 | reaction: str | None = None, 106 | ) -> typ.Updates | None: 107 | try: 108 | message = utils.get_message_id(message) or 0 109 | entity = await self.get_input_entity(entity) 110 | return await self( 111 | fun.messages.SendReactionRequest( 112 | big=big, 113 | add_to_recent=add_to_recent, 114 | peer=entity, 115 | msg_id=message, 116 | reaction=[typ.ReactionEmoji(emoticon=reaction)], 117 | ) 118 | ) 119 | except BaseException: 120 | return None 121 | 122 | @patchable() 123 | async def join_to(self, entity: hints.EntityLike) -> typ.Updates | None: 124 | try: 125 | entity = await self.get_input_entity(entity) 126 | return await self(fun.channels.JoinChannelRequest(entity)) 127 | except BaseException: 128 | return None 129 | 130 | @patchable() 131 | async def mute_chat(self, entity: hints.EntityLike) -> bool: 132 | try: 133 | entity = await self.get_input_entity(entity) 134 | return await self( 135 | fun.account.UpdateNotifySettingsRequest( 136 | entity, 137 | settings=typ.InputPeerNotifySettings( 138 | show_previews=False, 139 | silent=True, 140 | mute_until=2**31 - 1, 141 | sound=None, 142 | ), 143 | ) 144 | ) 145 | except BaseException: 146 | return False 147 | 148 | @patchable() 149 | async def create_group( 150 | self, 151 | title: str = "Getter", 152 | about: str = "", 153 | users: list[str | int] | None = None, 154 | photo: str | None = None, 155 | ) -> tuple[str | None, int | None]: 156 | users = users or [] 157 | try: 158 | created = await self( 159 | fun.channels.CreateChannelRequest( 160 | title=title, 161 | about=about, 162 | megagroup=True, 163 | ) 164 | ) 165 | chat_id = created.chats[0].id 166 | await asyncio.sleep(6) 167 | link = await self(fun.messages.ExportChatInviteRequest(chat_id)) 168 | if users: 169 | await asyncio.sleep(6) 170 | await self( 171 | fun.channels.InviteToChannelRequest( 172 | chat_id, 173 | users=users, 174 | ) 175 | ) 176 | if photo: 177 | await asyncio.sleep(6) 178 | await self( 179 | fun.channels.EditPhotoRequest( 180 | chat_id, 181 | photo=typ.InputChatUploadedPhoto(photo), 182 | ), 183 | ) 184 | except Exception as err: 185 | self.log.critical(err) 186 | return None, None 187 | if not str(chat_id).startswith("-100"): 188 | chat_id = int("-100" + str(chat_id)) 189 | return link, chat_id 190 | 191 | @patchable() 192 | async def send_message_parts( 193 | self, 194 | entity: hints.EntityLike, 195 | text: str, 196 | **kwargs, 197 | ) -> typ.Message: 198 | if len(text) > MAX_MESSAGE_LEN: 199 | parts = [] 200 | while len(text): 201 | splits = text[:MAX_MESSAGE_LEN].rfind("\n") 202 | if splits != -1: 203 | parts.append(text[:splits]) 204 | text = text[splits + 1 :] 205 | else: 206 | splits = text[:MAX_MESSAGE_LEN].rfind(". ") 207 | if splits != -1: 208 | parts.append(text[: splits + 1]) 209 | text = text[splits + 2 :] 210 | else: 211 | parts.append(text[:MAX_MESSAGE_LEN]) 212 | text = text[MAX_MESSAGE_LEN:] 213 | msg = None 214 | for part in parts[:-1]: 215 | msg = await self.send_message(entity, part, **kwargs) 216 | await asyncio.sleep(randrange(1, 3)) 217 | return msg 218 | return await self.send_message(entity, text, **kwargs) 219 | -------------------------------------------------------------------------------- /getter/core/patched/conversation.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import telethon.tl.custom.conversation 9 | from telethon.tl.custom.conversation import _checks_cancelled 10 | from getter.core.patcher import patch, patchable 11 | 12 | 13 | @patch(telethon.tl.custom.conversation.Conversation) 14 | class Conversation: 15 | @patchable() 16 | @_checks_cancelled 17 | async def read( 18 | self, 19 | message: int | None = None, 20 | **args, 21 | ): 22 | if message is None: 23 | message = self._incoming[-1].id if self._incoming else 0 24 | elif not isinstance(message, int): 25 | message = message.id 26 | return await self._client.read_chat( 27 | entity=self._input_chat, 28 | max_id=message, 29 | **args, 30 | ) 31 | -------------------------------------------------------------------------------- /getter/core/patcher.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from collections.abc import Callable 9 | from functools import wraps 10 | from typing import Any, T 11 | 12 | 13 | def patch(target: Any): 14 | def is_patchable(item: tuple[str, Any]) -> bool: 15 | return getattr(item[1], "patchable", False) 16 | 17 | @wraps(target) 18 | def wrapper(container: type[T]) -> T: 19 | for name, func in filter(is_patchable, container.__dict__.items()): 20 | old = getattr(target, name, None) 21 | if old is not None: 22 | setattr(target, f"old_{name}", old) 23 | if getattr(func, "is_property", False): 24 | func = property(func) 25 | setattr(target, name, func) 26 | return container 27 | 28 | return wrapper 29 | 30 | 31 | def patchable(is_property: bool = False) -> Callable: 32 | def wrapper(func: Callable) -> Callable: 33 | func.patchable = True 34 | func.is_property = is_property 35 | return func 36 | 37 | return wrapper 38 | -------------------------------------------------------------------------------- /getter/core/property.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | import sys 10 | from base64 import b64decode 11 | from re import findall 12 | from asyncache import cached 13 | from cachetools import TTLCache 14 | from getter import __license__, __copyright__ 15 | from getter.logger import LOG 16 | from .tools import Fetch 17 | 18 | _c, _u, _g = ( 19 | b64decode("a2FzdGFpZA==").decode("utf-8"), 20 | b64decode("a2FzdGF1cA==").decode("utf-8"), 21 | b64decode("a2FzdGFvdA==").decode("utf-8"), 22 | ) 23 | 24 | 25 | def do_not_remove_credit() -> None: 26 | if _c not in __copyright__: 27 | LOG.warning(__copyright__) 28 | LOG.warning("PLEASE RESPECT US, DO NOT REMOVE THE ORIGINAL CREDITS AND LICENSE !!") 29 | LOG.warning(__license__) 30 | sys.exit(1) 31 | 32 | 33 | @cached(TTLCache(maxsize=1000, ttl=(120 * 30))) # 1 hours 34 | async def get_blacklisted( 35 | url: str, 36 | is_json: bool = False, 37 | attempts: int = 3, 38 | fallbacks: tuple[int | str] | None = None, 39 | ) -> set[int | str]: 40 | count = 0 41 | is_content = not is_json 42 | while count < attempts: 43 | res = await Fetch( 44 | url, 45 | re_json=is_json, 46 | re_content=is_content, 47 | ) 48 | count += 1 49 | if not res: 50 | if count != attempts: 51 | await asyncio.sleep(1) 52 | continue 53 | ids = fallbacks or [] 54 | break 55 | if is_content: 56 | reg = r"[^\s#,\[\]\{\}]+" 57 | data = findall(reg, res.decode("utf-8")) 58 | ids = [int(x) for x in data if x.isdecimal() or (x.startswith("-") and x[1:].isdecimal())] 59 | else: 60 | ids = list(res) 61 | break 62 | return set(ids) 63 | -------------------------------------------------------------------------------- /getter/core/tools.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | import subprocess 10 | import sys 11 | from functools import partial 12 | from io import BytesIO 13 | from re import sub 14 | from typing import Any 15 | import aiofiles.os 16 | import aiohttp 17 | import telegraph.aio 18 | from getter import __version__, LOOP, EXECUTOR 19 | from getter.logger import LOG 20 | from .db import gvar, sgvar 21 | from .utils import get_random_hex 22 | 23 | _TGH: list[telegraph.aio.Telegraph] = [] 24 | 25 | 26 | def is_termux() -> bool: 27 | return "/com.termux" in sys.executable 28 | 29 | 30 | async def aioify(func, *args, **kwargs): 31 | return await LOOP.run_in_executor(executor=EXECUTOR, func=partial(func, *args, **kwargs)) 32 | 33 | 34 | def import_lib( 35 | lib_name: str, 36 | pkg_name: str | None = None, 37 | ) -> Any: 38 | from importlib import import_module 39 | 40 | if pkg_name is None: 41 | pkg_name = lib_name 42 | lib_name = sub(r"(=|>|<|~).*", "", lib_name) 43 | try: 44 | return import_module(lib_name) 45 | except ImportError: 46 | done = subprocess.run(["python3", "-m", "pip", "install", "-U", pkg_name]) 47 | if done.returncode != 0: 48 | raise AssertionError(f"Failed to install library {pkg_name} (pip exited with code {done.returncode})") 49 | return import_module(lib_name) 50 | 51 | 52 | async def Runner(cmd: str) -> tuple[str, str, int, int]: 53 | proc = await asyncio.create_subprocess_shell( 54 | cmd, 55 | stdout=asyncio.subprocess.PIPE, 56 | stderr=asyncio.subprocess.PIPE, 57 | ) 58 | try: 59 | stdout, stderr = await proc.communicate() 60 | except BaseException: 61 | stdout, stderr = "", "" 62 | return ( 63 | stdout.decode().strip(), 64 | stderr.decode().strip(), 65 | proc.returncode, 66 | proc.pid, 67 | ) 68 | 69 | 70 | async def Fetch( 71 | url: str, 72 | post: bool | None = None, 73 | headers: dict | None = None, 74 | params: dict | None = None, 75 | json: dict | None = None, 76 | data: dict | None = None, 77 | ssl: Any = None, 78 | re_json: bool = False, 79 | re_content: bool = False, 80 | real: bool = False, 81 | statuses: set[int] | None = None, 82 | **args, 83 | ) -> Any: 84 | statuses = statuses or {} 85 | if not headers: 86 | headers = { 87 | "User-Agent": "Python/{0[0]}.{0[1]} aiohttp/{1} getter/{2}".format( # noqa: UP032 88 | sys.version_info, 89 | aiohttp.__version__, 90 | __version__, 91 | ) 92 | } 93 | async with aiohttp.ClientSession(headers=headers) as session: 94 | try: 95 | if post: 96 | resp = await session.post( 97 | url=url, 98 | json=json, 99 | data=data, 100 | ssl=ssl, 101 | raise_for_status=False, 102 | **args, 103 | ) 104 | else: 105 | resp = await session.get( 106 | url=url, 107 | params=params, 108 | ssl=ssl, 109 | raise_for_status=False, 110 | **args, 111 | ) 112 | except BaseException: 113 | return None 114 | if resp.status not in {*{200, 201}, *statuses}: 115 | return None 116 | if re_json: 117 | return await resp.json(content_type=None) 118 | if re_content: 119 | return await resp.read() 120 | if real: 121 | return resp 122 | return await resp.text() 123 | 124 | 125 | async def Carbon( 126 | code: str, 127 | url: str = "carbon/api/cook", 128 | file_name: str = "carbon", 129 | download: bool = False, 130 | rayso: bool = False, 131 | **kwargs: Any | None, 132 | ) -> Any: 133 | kwargs["code"] = code 134 | if rayso: 135 | url = "rayso/api" 136 | kwargs["title"] = kwargs.get("title", "getter") 137 | kwargs["theme"] = kwargs.get("theme", "raindrop") 138 | kwargs["darkMode"] = kwargs.get("darkMode", True) 139 | kwargs["background"] = kwargs.get("background", True) 140 | res = await Fetch( 141 | url, 142 | post=True, 143 | json=kwargs, 144 | re_content=True, 145 | ) 146 | if not res: 147 | return None 148 | file_name = f"{file_name}_{get_random_hex()}.jpg" 149 | if not download: 150 | file = BytesIO(res) 151 | file.name = file_name 152 | else: 153 | file = "downloads/" + file_name 154 | async with aiofiles.open(file, mode="wb") as f: 155 | await f.write(res) 156 | return file 157 | 158 | 159 | async def Screenshot( 160 | video: str, 161 | duration: int, 162 | output: str = "", 163 | ) -> str | None: 164 | ttl = duration // 2 165 | cmd = f"ffmpeg -v quiet -ss {ttl} -i {video} -vframes 1 {output}" 166 | await Runner(cmd) 167 | return output if await aiofiles.os.path.isfile(output) else None 168 | 169 | 170 | async def MyIp() -> str: 171 | ips = ( 172 | "https://ipinfo.io/ip", 173 | "https://ip.seeip.org", 174 | "http://ip-api.com/line/?fields=query", 175 | "https://checkip.amazonaws.com", 176 | "https://api.ipify.org", 177 | "https://ipaddr.site", 178 | "https://icanhazip.com", 179 | "https://ident.me", 180 | "https://curlmyip.net", 181 | "https://ipecho.net/plain", 182 | ) 183 | statuses = { 184 | 405, # api.ipify.org 185 | } 186 | for url in ips: 187 | res = await Fetch(url, re_content=True, statuses=statuses) 188 | if res: 189 | return res.decode("utf-8").strip() 190 | continue 191 | return "null" 192 | 193 | 194 | def Pinger(addr: str) -> str: 195 | try: 196 | import icmplib 197 | except ImportError: 198 | icmplib = import_lib( 199 | lib_name="icmplib", 200 | pkg_name="icmplib==3.0.3", 201 | ) 202 | try: 203 | res = icmplib.ping( 204 | addr, 205 | count=1, 206 | interval=0.1, 207 | timeout=2, 208 | privileged=False, 209 | ) 210 | return f"{res.avg_rtt}ms" 211 | except BaseException: 212 | try: 213 | out = subprocess.check_output(["ping", "-c", "1", addr]).decode() 214 | out = out.split("\n") 215 | rtt_line = "" 216 | for _ in out: 217 | if "min/avg/max" in _: 218 | rtt_line = _ 219 | break 220 | rtt_line = rtt_line.replace(" ", "") 221 | rtt_line = rtt_line.split("=")[-1] 222 | rtt_line = rtt_line.split("/")[0] 223 | return f"{rtt_line}ms" 224 | except Exception as err: 225 | LOG.warning(err) 226 | return "--ms" 227 | 228 | 229 | async def Telegraph( 230 | author: str | None = None, 231 | ) -> telegraph.aio.Telegraph: 232 | if _TGH: 233 | return next(reversed(_TGH), None) 234 | token = await gvar("_TELEGRAPH_TOKEN") 235 | api = telegraph.aio.Telegraph(token) 236 | if token: 237 | _TGH.append(api) 238 | return api 239 | if author is None: 240 | return api 241 | try: 242 | await api.create_account( 243 | short_name="getteruser", 244 | author_name=author[:128], 245 | author_url="https://t.me/kastaid", 246 | ) 247 | except BaseException: 248 | return None 249 | await sgvar("_TELEGRAPH_TOKEN", api.get_access_token()) 250 | _TGH.append(api) 251 | return api 252 | -------------------------------------------------------------------------------- /getter/core/utils.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from functools import reduce 9 | from math import ceil 10 | from random import choice 11 | from re import sub, IGNORECASE 12 | from string import ascii_letters 13 | from time import time 14 | from typing import Any 15 | from uuid import uuid4 16 | from bs4 import BeautifulSoup 17 | from cachetools import cached 18 | from emoji import replace_emoji 19 | from markdown.core import markdown 20 | from unidecode import unidecode 21 | 22 | 23 | def humanbool(b: Any, toggle: bool = False) -> str: 24 | return ("off" if toggle else "no") if str(b).lower() in {"false", "none", "0", ""} else ("on" if toggle else "yes") 25 | 26 | 27 | def replace_all( 28 | text: str, 29 | repls: dict, 30 | regex: bool = False, 31 | ) -> str: 32 | if regex: 33 | return reduce(lambda a, kv: sub(*kv, a, flags=IGNORECASE), repls.items(), text) 34 | return reduce(lambda a, kv: a.replace(*kv), repls.items(), text) 35 | 36 | 37 | def md_to_html(text: str) -> str: 38 | repls = { 39 | "

(.*)

": "\\1", 40 | r"\~\~(.*)\~\~": "\\1", 41 | r"\-\-(.*)\-\-": "\\1", 42 | r"\_\_(.*)\_\_": "\\1", 43 | r"\|\|(.*)\|\|": "\\1", 44 | } 45 | return replace_all(markdown(text), repls, regex=True) 46 | 47 | 48 | def strip_format(text: str) -> str: 49 | repls = { 50 | "~~": "", 51 | "--": "", 52 | "__": "", 53 | "||": "", 54 | } 55 | return replace_all(BeautifulSoup(markdown(text), features="html.parser").get_text(), repls).strip() 56 | 57 | 58 | def strip_emoji(text: str) -> str: 59 | return replace_emoji(text, "").strip() 60 | 61 | 62 | def strip_ascii(text: str) -> str: 63 | return text.encode("ascii", "ignore").decode("ascii") 64 | 65 | 66 | def humanbytes(size: int | float) -> str: 67 | if not size: 68 | return "0 B" 69 | power = 1024 70 | pos = 0 71 | power_dict = {0: "", 1: "K", 2: "M", 3: "G", 4: "T", 5: "P", 6: "E", 7: "Z", 8: "Y"} 72 | while size > power: 73 | size /= power 74 | pos += 1 75 | return f"{size:.2f}{power_dict[pos]}B" 76 | 77 | 78 | def time_formatter(ms: int | float) -> str: 79 | minutes, seconds = divmod(int(ms / 1000), 60) 80 | hours, minutes = divmod(minutes, 60) 81 | days, hours = divmod(hours, 24) 82 | weeks, days = divmod(days, 7) 83 | time_units = ( 84 | f"{weeks}w, " if weeks else "", 85 | f"{days}d, " if days else "", 86 | f"{hours}h, " if hours else "", 87 | f"{minutes}m, " if minutes else "", 88 | f"{seconds}s, " if seconds else "", 89 | ) 90 | return "".join(time_units)[:-2] or "0s" 91 | 92 | 93 | def until_time( 94 | timing: str | int, 95 | unit: str = "m", 96 | ) -> tuple[float, str]: 97 | if unit.lower() not in { 98 | "s", 99 | "m", 100 | "h", 101 | "d", 102 | "w", 103 | }: 104 | unit = "m" 105 | if not str(timing).isdecimal(): 106 | raise TypeError("'timing' must be integers or str digits") 107 | if unit == "s": 108 | until = int(time() + int(timing) * 1) 109 | dur = "seconds" 110 | elif unit == "m": 111 | until = int(time() + int(timing) * 60) 112 | dur = "minutes" 113 | elif unit == "h": 114 | until = int(time() + int(timing) * 60 * 60) 115 | dur = "hours" 116 | elif unit == "d": 117 | until = int(time() + int(timing) * 24 * 60 * 60) 118 | dur = "days" 119 | else: 120 | until = int(time() + int(timing) * 7 * 24 * 60 * 60) 121 | dur = "weeks" 122 | return until, dur 123 | 124 | 125 | def get_random_hex(length: int = 12) -> str: 126 | return uuid4().hex[:length] 127 | 128 | 129 | def get_random_alpha(length: int = 12) -> str: 130 | return "".join(choice(ascii_letters) for _ in range(length)) 131 | 132 | 133 | def mask_email(email: str) -> str: 134 | at = email.find("@") 135 | return email[0] + "*" * int(at - 2) + email[at - 1 :] 136 | 137 | 138 | def chunk(lst: list, size: int = 2) -> list: 139 | return [lst[_ * size : _ * size + size] for _ in list(range(ceil(len(lst) / size)))] 140 | 141 | 142 | def sort_dict(dct: dict, reverse: bool = False) -> dict: 143 | return dict(sorted(dct.items(), reverse=reverse)) 144 | 145 | 146 | def deep_get( 147 | dct: dict, 148 | keys: str, 149 | default: Any = None, 150 | ) -> Any: 151 | return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dct) 152 | 153 | 154 | def to_dict( 155 | obj: Any, 156 | classkey: str | None = None, 157 | ) -> Any: 158 | if isinstance(obj, dict): 159 | data = {} 160 | for k, v in obj.items(): 161 | data[k] = to_dict(v, classkey) 162 | return data 163 | if hasattr(obj, "_ast"): 164 | return to_dict(obj._ast()) 165 | if hasattr(obj, "__iter__") and not isinstance(obj, str): 166 | return [to_dict(i, classkey) for i in obj] 167 | if hasattr(obj, "__dict__"): 168 | data = {k: to_dict(v, classkey) for k, v in obj.__dict__.items() if not callable(v) and not k.startswith("_")} 169 | if classkey and hasattr(obj, "__class__"): 170 | data[classkey] = obj.__class__.__name__ 171 | return data 172 | return obj 173 | 174 | 175 | def camel(text: str) -> str: 176 | text = sub(r"(_|-)+", " ", text).title().replace(" ", "") 177 | return "".join([text[0].lower(), text[1:]]) 178 | 179 | 180 | def snake(text: str) -> str: 181 | return "_".join(sub(r"([A-Z][a-z]+)", r" \1", sub(r"([A-Z]+)", r" \1", text.replace("-", " "))).split()).lower() 182 | 183 | 184 | def kebab(text: str) -> str: 185 | return "-".join( 186 | sub( 187 | r"(\s|_|-)+", 188 | " ", 189 | sub( 190 | r"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+", 191 | lambda x: " " + x.group(0).lower(), 192 | text, 193 | ), 194 | ).split() 195 | ) 196 | 197 | 198 | @cached(cache={}) 199 | def normalize(text: str) -> str: 200 | return unidecode(text) 201 | 202 | 203 | def get_full_class_name(obj: Any) -> str: 204 | module = obj.__class__.__module__ 205 | if module is None or module == str.__class__.__module__: 206 | return obj.__class__.__name__ 207 | return module + "." + obj.__class__.__name__ 208 | -------------------------------------------------------------------------------- /getter/logger.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import logging 9 | import sys 10 | from datetime import date 11 | from loguru import logger as LOG 12 | 13 | LOG.remove(0) 14 | LOG.add( 15 | "logs/getter-{}.log".format(date.today().strftime("%Y-%m-%d")), 16 | format="{time:YY/MM/DD HH:mm:ss} | {level: <8} | {name: ^15} | {function: ^15} | {line: >3} : {message}", 17 | rotation="1 MB", 18 | enqueue=True, 19 | ) 20 | LOG.add( 21 | sys.stderr, 22 | format="{time:YY/MM/DD HH:mm:ss} | {level} | {name}:{function}:{line} | {message}", 23 | level="INFO", 24 | colorize=False, 25 | ) 26 | 27 | 28 | class InterceptHandler(logging.Handler): 29 | def emit(self, record): 30 | try: 31 | level = LOG.level(record.levelname).name 32 | except ValueError: 33 | level = record.levelno 34 | frame, depth = sys._getframe(6), 6 35 | while frame and frame.f_code.co_filename == logging.__file__: 36 | frame = frame.f_back 37 | depth += 1 38 | LOG.opt( 39 | exception=record.exc_info, 40 | lazy=True, 41 | depth=depth, 42 | ).log(level, record.getMessage()) 43 | 44 | 45 | logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO) 46 | logging.disable(logging.DEBUG) 47 | logging.getLogger("asyncio").setLevel(logging.ERROR) 48 | logging.getLogger("urllib3").disabled = True 49 | logging.getLogger("urllib3.connectionpool").disabled = True 50 | logging.getLogger("PIL").disabled = True 51 | logging.getLogger("webdriver_manager").disabled = True 52 | logging.getLogger("pytgcalls").setLevel(logging.ERROR) 53 | TelethonLogger = logging.getLogger("telethon") 54 | TelethonLogger.setLevel(logging.ERROR) 55 | -------------------------------------------------------------------------------- /getter/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F401, F403 2 | # getter < https://t.me/kastaid > 3 | # Copyright (C) 2022-present kastaid 4 | # 5 | # This file is a part of < https://github.com/kastaid/getter/ > 6 | # Please read the GNU Affero General Public License in 7 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 8 | 9 | from validators.url import url as is_url 10 | from getter import ( 11 | __layer__, 12 | __tlversion__, 13 | __version__, 14 | StartTime, 15 | __license__, 16 | __copyright__, 17 | __pyversion__, 18 | Root, 19 | ) 20 | from getter.config import * 21 | from getter.core import * 22 | from getter.logger import LOG 23 | -------------------------------------------------------------------------------- /getter/plugins/_watcher.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from telethon import events 9 | from telethon.tl import types as typ 10 | from . import ( 11 | getter_app, 12 | sendlog, 13 | mentionuser, 14 | display_name, 15 | humanbool, 16 | is_gban, 17 | is_gmute, 18 | is_gdel, 19 | is_allow, 20 | ) 21 | 22 | gbanned_text = r""" 23 | \\#GBanned_Watch// User {} joined and quickly banned! 24 | Reported: {} 25 | Reason: {} 26 | """ 27 | gmuted_text = r""" 28 | \\#GMuted_Watch// User {} joined and quickly muted! 29 | Reason: {} 30 | """ 31 | 32 | 33 | @getter_app.on( 34 | events.NewMessage( 35 | incoming=True, 36 | func=lambda e: e.is_private or e.is_group, 37 | ) 38 | ) 39 | async def OnNewMessageFunc(kst): 40 | try: 41 | await DeletedUserHandler(kst) 42 | except ConnectionError: 43 | pass 44 | except Exception as err: 45 | kst.client.log.exception(err) 46 | 47 | 48 | @getter_app.on( 49 | events.ChatAction( 50 | func=lambda e: e.user_joined or e.user_added, 51 | ) 52 | ) 53 | async def OnChatActionFunc(kst): 54 | try: 55 | await JoinedHandler(kst) 56 | except ConnectionError: 57 | pass 58 | except Exception as err: 59 | kst.client.log.exception(err) 60 | 61 | 62 | async def DeletedUserHandler(kst): 63 | user = await kst.get_sender() 64 | if not isinstance(user, typ.User): 65 | return 66 | if kst.is_private and await is_allow(user.id, use_cache=True): 67 | return 68 | if await is_gdel(user.id, use_cache=True): 69 | try: 70 | await kst.delete() 71 | except BaseException: 72 | pass 73 | 74 | 75 | async def JoinedHandler(kst): 76 | ga = kst.client 77 | chat = await kst.get_chat() 78 | if not (chat.admin_rights or chat.creator): 79 | return 80 | user = await kst.get_user() 81 | gban = await is_gban(user.id, use_cache=True) 82 | if gban: 83 | mention = mentionuser(user.id, display_name(user), width=15, html=True) 84 | is_reported = await ga.report_spam(user.id) 85 | logs_text = r"\\#GBanned_Watch//" 86 | logs_text += f"\nUser {mention} [{user.id}] detected joining chat in {chat.title} - {chat.id} and quickly banned!\n" 87 | if kst.is_group: 88 | await kst.reply( 89 | gbanned_text.format( 90 | mention, 91 | humanbool(is_reported), 92 | f"
{gban.reason}
" if gban.reason else "None given.", 93 | ), 94 | parse_mode="html", 95 | silent=True, 96 | ) 97 | try: 98 | await ga.edit_permissions(chat.id, user.id, view_messages=False) 99 | except BaseException: 100 | pass 101 | logs_text += f"Reported: {humanbool(is_reported)}\n" 102 | logs_text += "Reason: {}\n".format(f"
{gban.reason}
" if gban.reason else "None given.") 103 | await sendlog(logs_text) 104 | 105 | gmute = await is_gmute(user.id, use_cache=True) 106 | if gmute and kst.is_group: 107 | mention = mentionuser(user.id, display_name(user), width=15, html=True) 108 | logs_text = r"\\#GMuted_Watch//" 109 | logs_text += f"\nUser {mention} [{user.id}] detected joining chat in {chat.title} - {chat.id} and quickly muted!\n" 110 | await kst.reply( 111 | gmuted_text.format( 112 | mention, 113 | f"
{gmute.reason}
" if gmute.reason else "None given.", 114 | ), 115 | parse_mode="html", 116 | silent=True, 117 | ) 118 | try: 119 | await ga.edit_permissions(chat.id, user.id, send_messages=False) 120 | except BaseException: 121 | pass 122 | logs_text += "Reason: {}\n".format(f"
{gban.reason}
" if gban.reason else "None given.") 123 | await sendlog(logs_text) 124 | -------------------------------------------------------------------------------- /getter/plugins/afk.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from datetime import datetime 9 | from html import escape 10 | from random import choice 11 | from telethon import events 12 | from . import ( 13 | DEVS, 14 | NOCHATS, 15 | getter_app, 16 | kasta_cmd, 17 | plugins_help, 18 | DEFAULT_GUCAST_BLACKLIST, 19 | get_blacklisted, 20 | time_formatter, 21 | OUTS_AFK, 22 | is_afk, 23 | add_afk, 24 | del_afk, 25 | set_last_afk, 26 | gvar, 27 | is_allow, 28 | ) 29 | 30 | _ON_STOP = ( 31 | "afk", 32 | "brb", 33 | "getter", 34 | "#anti_pm", 35 | "#blocked", 36 | "#archived", 37 | "#new_message", 38 | "#gbanned_watch", 39 | "#gmuted_watch", 40 | ) 41 | 42 | 43 | @kasta_cmd( 44 | pattern=r"afk(?: |$)([\s\S]*)", 45 | ) 46 | @kasta_cmd( 47 | pattern=r"brb(?: |$)([\s\S]*)", 48 | no_handler=True, 49 | ) 50 | async def _(kst): 51 | if await is_afk(): 52 | return 53 | yy = await kst.eor("`Go To AFK...!!`") 54 | start = datetime.now().timestamp() 55 | reason = await kst.client.get_text(kst, plain=False) 56 | text = "I`m Going AFK ツ" 57 | if reason: 58 | reason = escape(reason) 59 | text += f"\nReason:
{reason}
" 60 | await add_afk(reason, start) 61 | getter_app.add_handler( 62 | StopAFK, 63 | event=events.NewMessage( 64 | outgoing=True, 65 | forwards=False, 66 | ), 67 | ) 68 | getter_app.add_handler( 69 | OnAFK, 70 | event=events.NewMessage( 71 | incoming=True, 72 | func=lambda e: bool(e.mentioned or e.is_private), 73 | forwards=False, 74 | ), 75 | ) 76 | await yy.eod(text, parse_mode="html") 77 | 78 | 79 | async def StopAFK(kst): 80 | if any(_ in kst.raw_text.lower() for _ in _ON_STOP): 81 | return 82 | if kst.chat_id in NOCHATS and kst.client.uid not in DEVS: 83 | return 84 | afk = await is_afk() 85 | if afk: 86 | start = datetime.fromtimestamp(afk.start) 87 | end = datetime.now().replace(microsecond=0) 88 | afk_time = time_formatter((end - start).seconds * 1000) 89 | try: 90 | for x, y in afk.last.items(): 91 | await kst.client.delete_messages(int(x), [y]) 92 | except BaseException: 93 | pass 94 | await del_afk() 95 | myself = escape(kst.client.full_name) 96 | text = f"{myself}\n" 97 | text += f"{choice(OUTS_AFK)}\n" 98 | text += f"Was away for ~ {afk_time}" 99 | await kst.eod(text, parse_mode="html") 100 | 101 | 102 | async def OnAFK(kst): 103 | if any(_ in kst.raw_text.lower() for _ in ("afk", "brb")): 104 | return 105 | if not await is_afk(): 106 | return 107 | user = await kst.get_sender() 108 | if getattr(user, "bot", False) or getattr(user, "support", False) or getattr(user, "verified", False): 109 | return 110 | if kst.chat_id in NOCHATS and user.id not in DEVS: 111 | return 112 | if kst.is_private and await gvar("_pmguard", use_cache=True) and not await is_allow(user.id, use_cache=True): 113 | return 114 | GUCAST_BLACKLIST = await get_blacklisted( 115 | url="https://raw.githubusercontent.com/kastaid/resources/main/gucastblacklist.py", 116 | attempts=1, 117 | fallbacks=DEFAULT_GUCAST_BLACKLIST, 118 | ) 119 | if user.id in {*DEVS, *GUCAST_BLACKLIST}: 120 | return 121 | afk = await is_afk() 122 | if afk: 123 | start = datetime.fromtimestamp(afk.start) 124 | end = datetime.now().replace(microsecond=0) 125 | afk_time = time_formatter((end - start).seconds * 1000) 126 | text = "I`m Now AFK ツ\n" 127 | text += f"Last seen {afk_time} ago." 128 | reason = f"
{afk.reason}
" if afk.reason else "No reason." 129 | text += f"\nReason: {reason}" 130 | chat_id = str(kst.chat_id) 131 | if chat_id in afk.last: 132 | try: 133 | await kst.client.delete_messages(int(chat_id), [afk.last[chat_id]]) 134 | except BaseException: 135 | pass 136 | last = await kst.reply( 137 | text, 138 | link_preview=False, 139 | parse_mode="html", 140 | ) 141 | await set_last_afk(chat_id, last.id) 142 | 143 | 144 | async def handle_afk() -> None: 145 | if await is_afk(): 146 | getter_app.add_handler( 147 | StopAFK, 148 | event=events.NewMessage( 149 | outgoing=True, 150 | forwards=False, 151 | ), 152 | ) 153 | getter_app.add_handler( 154 | OnAFK, 155 | event=events.NewMessage( 156 | incoming=True, 157 | func=lambda e: bool(e.mentioned or e.is_private), 158 | forwards=False, 159 | ), 160 | ) 161 | 162 | 163 | plugins_help["afk"] = { 164 | "{i}afk [reason]/[reply]": "When you are in AFK if anyone tags you then will notify them if you're AFK unless if 'afk' or 'brb' words is exists!", 165 | "brb": """Alias for afk command, without handler! 166 | 167 | **Note:** 168 | - AFK is abbreviation for “Away From Keyboard”. 169 | - BRB also abbreviation for “Be Right Back”. 170 | - To stopping AFK just typing at anywhere. 171 | - To continue AFK put the 'afk' or 'brb' word in message. 172 | - AFK Ignored user that not allowed to PM at pmpermit. 173 | - When AFK not stopped, this will continue even if the bot restarted. 174 | """, 175 | } 176 | -------------------------------------------------------------------------------- /getter/plugins/beautify.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from random import choice 9 | from . import ( 10 | Root, 11 | kasta_cmd, 12 | plugins_help, 13 | mentionuser, 14 | get_media_type, 15 | Carbon, 16 | CARBON_PRESETS, 17 | RAYSO_THEMES, 18 | ) 19 | 20 | 21 | @kasta_cmd( 22 | pattern=r"carbon(?: |$)([\s\S]*)", 23 | ) 24 | async def _(kst): 25 | ga = kst.client 26 | match = kst.pattern_match.group(1) 27 | args = match.split(" ") 28 | if args[0] in CARBON_PRESETS: 29 | is_theme, theme = True, args[0] 30 | else: 31 | is_theme, theme = False, choice(tuple(CARBON_PRESETS)) 32 | if kst.is_reply: 33 | reply = await kst.get_reply_message() 34 | if reply.media and get_media_type(reply.media) == "text": 35 | file = await reply.download_media() 36 | code = (Root / file).read_text() 37 | (Root / file).unlink(missing_ok=True) 38 | else: 39 | code = reply.message 40 | if is_theme: 41 | try: 42 | code = match.split(maxsplit=1)[1] 43 | except BaseException: 44 | pass 45 | else: 46 | code = match 47 | if is_theme: 48 | try: 49 | code = match.split(maxsplit=1)[1] 50 | except BaseException: 51 | pass 52 | if not code: 53 | return await kst.eod("`Reply to text message or readable file.`") 54 | yy = await kst.eor("`Processing...`") 55 | backgroundColor = CARBON_PRESETS[theme] 56 | windowTheme = choice(("none", "sharp", "bw")) 57 | carbon = await Carbon( 58 | code.strip(), 59 | file_name="carbon", 60 | download=True, 61 | fontFamily="Fira Code", 62 | theme=theme, 63 | backgroundColor=backgroundColor, 64 | dropShadow=windowTheme != "bw", 65 | windowTheme=windowTheme, 66 | ) 67 | if not carbon: 68 | return await yy.eod("`Carbon API not responding.`") 69 | await yy.eor( 70 | f"Carboniz by {mentionuser(ga.uid, ga.full_name, html=True)}", 71 | file=carbon, 72 | parse_mode="html", 73 | force_document=False, 74 | ) 75 | (Root / carbon).unlink(missing_ok=True) 76 | 77 | 78 | @kasta_cmd( 79 | pattern=r"rayso(?: |$)([\s\S]*)", 80 | ) 81 | async def _(kst): 82 | ga = kst.client 83 | match = kst.pattern_match.group(1) 84 | args = match.split(" ") 85 | if args[0] in RAYSO_THEMES: 86 | is_theme, theme = True, args[0] 87 | else: 88 | is_theme, theme = False, choice(RAYSO_THEMES) 89 | if kst.is_reply: 90 | reply = await kst.get_reply_message() 91 | if reply.media and get_media_type(reply.media) == "text": 92 | file = await reply.download_media() 93 | code = (Root / file).read_text() 94 | (Root / file).unlink(missing_ok=True) 95 | else: 96 | code = reply.message 97 | if is_theme: 98 | try: 99 | code = match.split(maxsplit=1)[1] 100 | except BaseException: 101 | pass 102 | else: 103 | code = match 104 | if is_theme: 105 | try: 106 | code = match.split(maxsplit=1)[1] 107 | except BaseException: 108 | pass 109 | if not code: 110 | return await kst.eod("`Reply to text message or readable file.`") 111 | yy = await kst.eor("`Processing...`") 112 | darkMode = choice((True, False)) 113 | rayso = await Carbon( 114 | code, 115 | file_name="rayso", 116 | download=True, 117 | rayso=True, 118 | theme=theme, 119 | darkMode=darkMode, 120 | ) 121 | if not rayso: 122 | return await yy.eod("`Rayso API not responding.`") 123 | await yy.eor( 124 | f"Raysoniz by {mentionuser(ga.uid, ga.full_name, html=True)}", 125 | file=rayso, 126 | parse_mode="html", 127 | force_document=False, 128 | ) 129 | (Root / rayso).unlink(missing_ok=True) 130 | 131 | 132 | @kasta_cmd( 133 | pattern="theme$", 134 | ) 135 | async def _(kst): 136 | carbon = f"**{len(CARBON_PRESETS)} Carbon Themes:**\n" + "\n".join([f"- `{_}`" for _ in CARBON_PRESETS]) 137 | rayso = f"**{len(RAYSO_THEMES)} Rayso Themes:**\n" + "\n".join([f"- `{_}`" for _ in RAYSO_THEMES]) 138 | await kst.sod(carbon) 139 | await kst.sod(rayso) 140 | 141 | 142 | plugins_help["beautify"] = { 143 | "{i}carbon [theme] [text]/[reply (text or readable file)]": "Carboniz the text with choosing theme or random themes.", 144 | "{i}rayso [theme] [text]/[reply (text or readable file)]": "Raysoniz the text with choosing theme or random themes.", 145 | "{i}theme": "List all themes name.", 146 | } 147 | -------------------------------------------------------------------------------- /getter/plugins/bot.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | import os 10 | from random import choice 11 | from sys import executable 12 | from time import sleep as tsleep, monotonic 13 | import aiofiles 14 | from telethon.tl import functions as fun 15 | from . import ( 16 | __version__, 17 | Root, 18 | kasta_cmd, 19 | plugins_help, 20 | sgvar, 21 | parse_pre, 22 | formatx_send, 23 | Carbon, 24 | CARBON_PRESETS, 25 | hk, 26 | ) 27 | 28 | 29 | @kasta_cmd( 30 | pattern="alive$", 31 | ) 32 | async def _(kst): 33 | await kst.eod("**Hey, I am alive !!**") 34 | 35 | 36 | @kasta_cmd( 37 | pattern="(uptime|up)$", 38 | ) 39 | async def _(kst): 40 | await kst.eod(f"**Uptime:** {kst.client.uptime}") 41 | 42 | 43 | @kasta_cmd( 44 | pattern="ping$|([p]ing)$", 45 | ignore_case=True, 46 | edited=True, 47 | ) 48 | async def _(kst): 49 | start = monotonic() 50 | await kst.client(fun.PingRequest(ping_id=0)) 51 | speedy = monotonic() - start 52 | uptime = kst.client.uptime 53 | await kst.eor( 54 | f"🏓 Pong !!\n├ Speedy{speedy:.3f}s\n├ Uptime{uptime}\n└ Version{__version__}", 55 | parse_mode="html", 56 | ) 57 | 58 | 59 | @kasta_cmd( 60 | pattern="logs?(?: |$)(heroku|carbon|open)?", 61 | ) 62 | @kasta_cmd( 63 | pattern="glogs?(?: |$)(heroku|carbon|open)?(?: |$)(.*)", 64 | dev=True, 65 | ) 66 | async def _(kst): 67 | mode = kst.pattern_match.group(1) 68 | if kst.is_dev: 69 | opt = kst.pattern_match.group(2) 70 | user_id = None 71 | try: 72 | user_id = int(opt) 73 | except ValueError: 74 | pass 75 | if user_id and user_id != kst.client.uid: 76 | return 77 | await asyncio.sleep(choice((4, 6, 8))) 78 | yy = await kst.eor("`Getting...`", silent=True) 79 | if mode == "heroku": 80 | return await heroku_logs(yy) 81 | if mode == "carbon": 82 | theme = choice(tuple(CARBON_PRESETS)) 83 | backgroundColor = CARBON_PRESETS[theme] 84 | for file in get_terminal_logs(): 85 | code = (Root / file).read_text() 86 | logs = await Carbon( 87 | code.strip()[-2500:], 88 | file_name="carbon-getter-log", 89 | download=True, 90 | fontFamily="Hack", 91 | theme=theme, 92 | backgroundColor=backgroundColor, 93 | dropShadow=True, 94 | ) 95 | if not logs: 96 | continue 97 | try: 98 | await yy.eor( 99 | r"\\**#Getter**// Carbon Terminal Logs", 100 | file=logs, 101 | force_document=True, 102 | ) 103 | except BaseException: 104 | pass 105 | (Root / logs).unlink(missing_ok=True) 106 | elif mode == "open": 107 | for file in get_terminal_logs(): 108 | logs = (Root / file).read_text() 109 | await yy.sod(logs, parts=True, parse_mode=parse_pre) 110 | else: 111 | try: 112 | for file in get_terminal_logs(): 113 | await yy.eor( 114 | r"\\**#Getter**// Terminal Logs", 115 | file=file, 116 | force_document=True, 117 | ) 118 | except BaseException: 119 | pass 120 | 121 | 122 | @kasta_cmd( 123 | pattern="restart$", 124 | ) 125 | @kasta_cmd( 126 | pattern="grestart(?: |$)(.*)", 127 | dev=True, 128 | ) 129 | async def _(kst): 130 | if kst.is_dev: 131 | opt = kst.pattern_match.group(1) 132 | user_id = None 133 | try: 134 | user_id = int(opt) 135 | except ValueError: 136 | pass 137 | if user_id and user_id != kst.client.uid: 138 | return 139 | await asyncio.sleep(choice((4, 6, 8))) 140 | yy = await kst.eor("`Restarting...`", silent=True) 141 | try: 142 | chat_id = yy.chat_id or yy.from_id 143 | await sgvar("_restart", f"{chat_id}|{yy.id}") 144 | except BaseException: 145 | pass 146 | if not hk.is_heroku: 147 | await yy.eor(r"\\**#Getter**// `Restarting as locally...`") 148 | return restart_app() 149 | try: 150 | await yy.eor(r"\\**#Getter**// `Restarting as heroku... Wait for a few minutes.`") 151 | app = hk.heroku().app(hk.name) 152 | app.restart() 153 | except Exception as err: 154 | reply = await yy.eor(formatx_send(err), parse_mode="html") 155 | await reply.reply(r"\\**#Getter**// `Restarting as locally...`", silent=True) 156 | restart_app() 157 | 158 | 159 | @kasta_cmd( 160 | pattern="sleep(?: |$)(.*)", 161 | ) 162 | async def _(kst): 163 | sec = await kst.client.get_text(kst) 164 | timer = int(sec) if sec.replace(".", "", 1).isdecimal() else 3 165 | timer = 3 if timer > 30 else timer 166 | yy = await kst.eor(f"`sleep in {timer} seconds...`") 167 | tsleep(timer) 168 | await yy.eod(f"`wake-up from {timer} seconds`") 169 | 170 | 171 | def get_terminal_logs() -> list[Root]: 172 | return sorted((Root / "logs").rglob("getter-*.log")) 173 | 174 | 175 | async def heroku_logs(kst) -> None: 176 | if not hk.api: 177 | return await kst.eod("Please set `HEROKU_API` in Config Vars.") 178 | if not hk.name: 179 | return await kst.eod("Please set `HEROKU_APP_NAME` in Config Vars.") 180 | try: 181 | app = hk.heroku().app(hk.name) 182 | logs = app.get_log(lines=100) 183 | except Exception as err: 184 | return await kst.eor(formatx_send(err), parse_mode="html") 185 | await kst.eor("`Downloading Logs...`") 186 | file = Root / "downloads/getter-heroku.log" 187 | async with aiofiles.open(file, mode="w") as f: 188 | await f.write(logs) 189 | await kst.eor( 190 | r"\\**#Getter**// Heroku Logs", 191 | file=file, 192 | force_document=True, 193 | ) 194 | (file).unlink(missing_ok=True) 195 | 196 | 197 | def restart_app() -> None: 198 | os.system("clear") 199 | try: 200 | import psutil 201 | 202 | proc = psutil.Process(os.getpid()) 203 | for p in proc.open_files() + proc.connections(): 204 | os.close(p.fd) 205 | except BaseException: 206 | pass 207 | reqs = Root / "requirements.txt" 208 | os.system(f"{executable} -m pip install --disable-pip-version-check --default-timeout=100 -U -r {reqs}") 209 | os.execl(executable, executable, "-m", "getter") 210 | 211 | 212 | plugins_help["bot"] = { 213 | "{i}alive": "Just showing alive.", 214 | "{i}uptime|{i}up": "Check current uptime.", 215 | "{i}ping|ping|Ping": "Check how long it takes to ping.", 216 | "{i}logs": "Get the full terminal logs.", 217 | "{i}logs open": "Open logs as text message.", 218 | "{i}logs carbon": "Get the carbonized terminal logs.", 219 | "{i}logs heroku": "Get the latest 100 lines of heroku logs.", 220 | "{i}restart": "Restart the bot.", 221 | "{i}sleep [seconds]/[reply]": "Sleep the bot in few seconds (max 30).", 222 | } 223 | -------------------------------------------------------------------------------- /getter/plugins/custom/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F403 2 | # getter < https://t.me/kastaid > 3 | # Copyright (C) 2022-present kastaid 4 | # 5 | # This file is a part of < https://github.com/kastaid/getter/ > 6 | # Please read the GNU Affero General Public License in 7 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 8 | 9 | from getter.plugins import * 10 | -------------------------------------------------------------------------------- /getter/plugins/deepfry.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from mimetypes import guess_extension 10 | from random import randint, uniform 11 | from PIL import Image, ImageEnhance, ImageOps 12 | from telethon import events 13 | from telethon.errors import YouBlockedUserError 14 | from telethon.tl import types as typ 15 | from . import ( 16 | Root, 17 | kasta_cmd, 18 | plugins_help, 19 | aioify, 20 | Runner, 21 | Screenshot, 22 | ) 23 | 24 | FRY_BOT = "image_deepfrybot" 25 | 26 | 27 | @kasta_cmd( 28 | pattern="fry(?: |$)([1-8])?", 29 | func=lambda e: e.is_reply, 30 | ) 31 | async def _(kst): 32 | ga = kst.client 33 | match = kst.pattern_match.group(1) 34 | level = int(match) if match else 3 35 | reply = await kst.get_reply_message() 36 | data = check_media(reply) 37 | if isinstance(data, bool): 38 | return await kst.eor("`Cannot frying that!`", time=5) 39 | yy = await kst.eor("`...`") 40 | ext = None 41 | fry_img = Root / "downloads/fry.jpeg" 42 | if isinstance(reply.media, typ.MessageMediaPhoto): 43 | file = fry_img 44 | else: 45 | mim = reply.media.document.mime_type 46 | ext = guess_extension(mim) 47 | file = Root / ("downloads/" + f"fry{ext}") 48 | await reply.download_media(file=file) 49 | if ext and ext in {".mp4", ".gif", ".webm"}: 50 | ss = await Screenshot(file, 0, fry_img) 51 | if not ss: 52 | (file).unlink(missing_ok=True) 53 | return await yy.try_delete() 54 | else: 55 | try: 56 | if ext and ext == ".tgs": 57 | fry_img = Root / "downloads/fry.png" 58 | await Runner(f"lottie_convert.py {file} {fry_img}") 59 | (file).unlink(missing_ok=True) 60 | file = fry_img 61 | img = Image.open(file) 62 | img.convert("RGB").save(fry_img, format="JPEG") 63 | except BaseException: 64 | (file).unlink(missing_ok=True) 65 | return await yy.try_delete() 66 | async with ga.conversation(FRY_BOT) as conv: 67 | resp = await conv_fry(conv, fry_img, level) 68 | if not resp: 69 | return await yy.eod("`Bot did not respond.`") 70 | if not getattr(resp.message.media, "photo", None): 71 | return await yy.eod(f"`{resp.message.message}`") 72 | await yy.eor( 73 | file=resp.message.media, 74 | force_document=False, 75 | ) 76 | (file).unlink(missing_ok=True) 77 | (fry_img).unlink(missing_ok=True) 78 | 79 | 80 | @kasta_cmd( 81 | pattern="ugly(?: |$)([1-9])?", 82 | func=lambda e: e.is_reply, 83 | ) 84 | async def _(kst): 85 | match = kst.pattern_match.group(1) 86 | level = int(match) if match else 3 87 | reply = await kst.get_reply_message() 88 | data = check_media(reply) 89 | if isinstance(data, bool): 90 | return await kst.eor("`Cannot uglying that!`", time=5) 91 | yy = await kst.eor("`...`") 92 | ext = None 93 | ugly_img = Root / "downloads/ugly.jpeg" 94 | if isinstance(reply.media, typ.MessageMediaPhoto): 95 | file = ugly_img 96 | else: 97 | mim = reply.media.document.mime_type 98 | ext = guess_extension(mim) 99 | file = Root / ("downloads/" + f"ugly{ext}") 100 | await reply.download_media(file=file) 101 | if ext and ext in {".mp4", ".gif", ".webm"}: 102 | to_ugly = ugly_img 103 | ss = await Screenshot(file, 0, ugly_img) 104 | if not ss: 105 | (file).unlink(missing_ok=True) 106 | return await yy.try_delete() 107 | else: 108 | if ext and ext == ".tgs": 109 | ugly_img = Root / "downloads/ugly.png" 110 | await Runner(f"lottie_convert.py {file} {ugly_img}") 111 | (file).unlink(missing_ok=True) 112 | file = ugly_img 113 | to_ugly = file 114 | try: 115 | for _ in range(level): 116 | img = await aioify(uglying, to_ugly) 117 | img.save(ugly_img, format="JPEG") 118 | except BaseException: 119 | (to_ugly).unlink(missing_ok=True) 120 | return await yy.try_delete() 121 | await yy.eor( 122 | file=ugly_img, 123 | force_document=False, 124 | ) 125 | (file).unlink(missing_ok=True) 126 | (ugly_img).unlink(missing_ok=True) 127 | 128 | 129 | async def conv_fry(conv, image, level): 130 | try: 131 | resp = conv.wait_event(events.NewMessage(incoming=True, from_users=conv.chat_id)) 132 | media = await conv.send_file( 133 | image, 134 | force_document=False, 135 | ) 136 | resp = await resp 137 | await resp.try_delete() 138 | yy = await conv.send_message(f"/deepfry {level}", reply_to=media.id) 139 | resp = conv.wait_event(events.NewMessage(incoming=True, from_users=conv.chat_id)) 140 | resp = await resp 141 | await yy.try_delete() 142 | await resp.read( 143 | clear_mentions=True, 144 | clear_reactions=True, 145 | ) 146 | return resp 147 | except asyncio.exceptions.TimeoutError: 148 | return None 149 | except YouBlockedUserError: 150 | await conv._client.unblock(conv.chat_id) 151 | return await conv_fry(conv, image, level) 152 | 153 | 154 | def uglying(img: Image) -> Image: 155 | img = Image.open(img) 156 | colours = ( 157 | (randint(50, 200), randint(40, 170), randint(40, 190)), 158 | (randint(190, 255), randint(170, 240), randint(180, 250)), 159 | ) 160 | img = img.copy().convert("RGB") 161 | img = img.convert("RGB") 162 | width, height = img.width, img.height 163 | img = img.resize( 164 | (int(width ** uniform(0.8, 0.9)), int(height ** uniform(0.8, 0.9))), 165 | resample=Image.LANCZOS, 166 | ) 167 | img = img.resize( 168 | (int(width ** uniform(0.85, 0.95)), int(height ** uniform(0.85, 0.95))), 169 | resample=Image.BILINEAR, 170 | ) 171 | img = img.resize( 172 | (int(width ** uniform(0.89, 0.98)), int(height ** uniform(0.89, 0.98))), 173 | resample=Image.BICUBIC, 174 | ) 175 | img = img.resize((width, height), resample=Image.BICUBIC) 176 | img = ImageOps.posterize(img, randint(3, 7)) 177 | overlay = img.split()[0] 178 | overlay = ImageEnhance.Contrast(overlay).enhance(uniform(1.0, 2.0)) 179 | overlay = ImageEnhance.Brightness(overlay).enhance(uniform(1.0, 2.0)) 180 | overlay = ImageOps.colorize(overlay, colours[0], colours[1]) 181 | img = Image.blend(img, overlay, uniform(0.1, 0.4)) 182 | return ImageEnhance.Sharpness(img).enhance(randint(5, 300)) 183 | 184 | 185 | def check_media(reply): 186 | data = False 187 | if reply and reply.media: 188 | if reply.photo: 189 | data = reply.photo 190 | elif reply.document: 191 | if reply.audio or reply.voice: 192 | return False 193 | data = reply.media.document 194 | else: 195 | return False 196 | if not data or data is None: 197 | return False 198 | return data 199 | 200 | 201 | plugins_help["deepfry"] = { 202 | "{i}fry [1-8] [reply]": "Frying any image/sticker/animation/gif/video use image_deepfrybot (default level 3).", 203 | "{i}ugly [1-9] [reply]": "Uglying any image/sticker/animation/gif/video and make it look ugly (default level 1).", 204 | } 205 | -------------------------------------------------------------------------------- /getter/plugins/delayspam.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from telethon.errors import RPCError, FloodWaitError, SlowModeWaitError 10 | from . import ( 11 | hl, 12 | kasta_cmd, 13 | plugins_help, 14 | normalize_chat_id, 15 | ) 16 | 17 | DS_TASKS: dict[int, dict[int, asyncio.Task]] = {i: {} for i in range(10)} 18 | 19 | 20 | def get_task_store(ds: int) -> dict[int, asyncio.Task]: 21 | return DS_TASKS.get(ds) 22 | 23 | 24 | @kasta_cmd( 25 | pattern=r"ds(1|2|3|4|5|6|7|8|9|)(?: |$)([\s\S]*)", 26 | ) 27 | async def _(kst): 28 | chat_id = normalize_chat_id(kst.chat_id) 29 | ds = int(kst.pattern_match.group(1) or 0) 30 | task_store = get_task_store(ds) 31 | if chat_id in task_store: 32 | return await kst.eor(f"Please wait until previous •ds{ds}• is finished or cancel it.", time=2) 33 | if kst.is_reply: 34 | try: 35 | args = kst.text.split(" ", 2) 36 | delay = int(args[1]) 37 | count = int(args[2]) 38 | message = await kst.get_reply_message() 39 | await kst.try_delete() 40 | except BaseException: 41 | return await kst.eor(f"`{hl}ds{ds} [delay] [count] [reply]`", time=4) 42 | else: 43 | try: 44 | args = kst.text.split(" ", 3) 45 | delay = int(args[1]) 46 | count = int(args[2]) 47 | message = str(args[3]) 48 | await kst.try_delete() 49 | except BaseException: 50 | return await kst.eor(f"`{hl}ds{ds} [delay] [count] [text]`", time=4) 51 | delay = max(2, delay) 52 | task = asyncio.create_task( 53 | run_delayspam( 54 | kst, 55 | ds, 56 | chat_id, 57 | message, 58 | delay, 59 | count, 60 | ) 61 | ) 62 | DS_TASKS[ds][chat_id] = task 63 | task.add_done_callback(lambda t, k=chat_id: get_task_store(ds).pop(k, None)) 64 | 65 | 66 | @kasta_cmd( 67 | pattern="ds(1|2|3|4|5|6|7|8|9|)cancel$", 68 | ) 69 | async def _(kst): 70 | chat_id = normalize_chat_id(kst.chat_id) 71 | ds = int(kst.pattern_match.group(1) or 0) 72 | task_store = get_task_store(ds) 73 | if chat_id not in task_store: 74 | return await kst.eor(f"No running •ds{ds}• in current chat.", time=2) 75 | task = task_store.pop(chat_id) 76 | if not task.done(): 77 | task.cancel() 78 | await kst.eor(f"`canceled ds{ds} in current chat`", time=2) 79 | 80 | 81 | @kasta_cmd( 82 | pattern="ds(1|2|3|4|5|6|7|8|9|)stop$", 83 | ) 84 | async def _(kst): 85 | ds = int(kst.pattern_match.group(1) or 0) 86 | task_store = get_task_store(ds) 87 | for task in list(task_store.values()): 88 | if not task.done(): 89 | task.cancel() 90 | task_store.clear() 91 | await kst.eor(f"`stopped ds{ds} in all chats`", time=4) 92 | 93 | 94 | @kasta_cmd( 95 | pattern="dsclear$", 96 | ) 97 | async def _(kst): 98 | for store in DS_TASKS.values(): 99 | for task in list(store.values()): 100 | if not task.done(): 101 | task.cancel() 102 | store.clear() 103 | await kst.eor("`clear all ds*`", time=4) 104 | 105 | 106 | async def run_delayspam( 107 | kst, 108 | ds: int, 109 | chat_id: int, 110 | message: str, 111 | delay: int, 112 | count: int, 113 | ) -> None: 114 | for _ in range(count): 115 | if chat_id not in get_task_store(ds): 116 | break 117 | try: 118 | await kst.client.send_message( 119 | chat_id, 120 | message=message, 121 | parse_mode="markdown", 122 | silent=True, 123 | ) 124 | await asyncio.sleep(delay) 125 | except FloodWaitError as fw: 126 | await asyncio.sleep(fw.seconds) 127 | await kst.client.send_message( 128 | chat_id, 129 | message=message, 130 | parse_mode="markdown", 131 | silent=True, 132 | ) 133 | await asyncio.sleep(delay) 134 | except SlowModeWaitError: 135 | pass 136 | except RPCError: 137 | break 138 | 139 | 140 | plugins_help["delayspam"] = { 141 | "{i}ds [delay] [count] [text]/[reply]": "Spam current chat in seconds (min 2 seconds).", 142 | "{i}ds1 [delay] [count] [text]/[reply]": "Same as above, different message as 1.", 143 | "{i}ds2 [delay] [count] [text]/[reply]": "Same as above, different message as 2.", 144 | "{i}ds3 [delay] [count] [text]/[reply]": "Same as above, different message as 3.", 145 | "{i}ds4 [delay] [count] [text]/[reply]": "Same as above, different message as 4.", 146 | "{i}ds5 [delay] [count] [text]/[reply]": "Same as above, different message as 5.", 147 | "{i}ds6 [delay] [count] [text]/[reply]": "Same as above, different message as 6.", 148 | "{i}ds7 [delay] [count] [text]/[reply]": "Same as above, different message as 7.", 149 | "{i}ds8 [delay] [count] [text]/[reply]": "Same as above, different message as 8.", 150 | "{i}ds9 [delay] [count] [text]/[reply]": "Same as above, different message as 9.", 151 | "{i}dscancel": "To cancel `{i}ds` in current chat.", 152 | "{i}ds1cancel": "To cancel `{i}ds1` in current chat.", 153 | "{i}ds2cancel": "To cancel `{i}ds2` in current chat.", 154 | "{i}ds3cancel": "To cancel `{i}ds3` in current chat.", 155 | "{i}ds4cancel": "To cancel `{i}ds4` in current chat.", 156 | "{i}ds5cancel": "To cancel `{i}ds5` in current chat.", 157 | "{i}ds6cancel": "To cancel `{i}ds6` in current chat.", 158 | "{i}ds7cancel": "To cancel `{i}ds7` in current chat.", 159 | "{i}ds8cancel": "To cancel `{i}ds8` in current chat.", 160 | "{i}ds9cancel": "To cancel `{i}ds9` in current chat.", 161 | "{i}dsstop": "To stop `{i}ds` in all chats.", 162 | "{i}ds1stop": "To stop `{i}ds1` in all chats.", 163 | "{i}ds2stop": "To stop `{i}ds2` in all chats.", 164 | "{i}ds3stop": "To stop `{i}ds3` in all chats.", 165 | "{i}ds4stop": "To stop `{i}ds4` in all chats.", 166 | "{i}ds5stop": "To stop `{i}ds5` in all chats.", 167 | "{i}ds6stop": "To stop `{i}ds6` in all chats.", 168 | "{i}ds7stop": "To stop `{i}ds7` in all chats.", 169 | "{i}ds8stop": "To stop `{i}ds8` in all chats.", 170 | "{i}ds9stop": "To stop `{i}ds9` in all chats.", 171 | "{i}dsclear": "To clear and stop all ds*.", 172 | } 173 | -------------------------------------------------------------------------------- /getter/plugins/downloader.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from telethon import events 10 | from telethon.errors import YouBlockedUserError 11 | from . import kasta_cmd, plugins_help 12 | 13 | TW_BOT = "tweedlbot" 14 | TT_BOT = "downloader_tiktok_bot" 15 | 16 | 17 | @kasta_cmd( 18 | pattern="tw(?: |$)(.*)", 19 | ) 20 | async def _(kst): 21 | ga = kst.client 22 | link = await ga.get_text(kst) 23 | if not link: 24 | return await kst.eor("`Provide a valid tweet link!`", time=5) 25 | yy = await kst.eor("`Downloading...`") 26 | async with ga.conversation(TW_BOT) as conv: 27 | resp = await conv_tw(conv, link) 28 | if not resp: 29 | return await yy.eod("`Bot did not respond.`") 30 | if not getattr(resp.message.media, "document", None): 31 | return await yy.eod(f"`{resp.message.message}`") 32 | file = resp.message.media 33 | await yy.eor( 34 | f"**Link:** `{link}`", 35 | file=file, 36 | force_document=False, 37 | ) 38 | await ga.delete_chat(TW_BOT, revoke=True) 39 | 40 | 41 | @kasta_cmd( 42 | pattern="tt(?: |$)(.*)", 43 | ) 44 | async def _(kst): 45 | ga = kst.client 46 | link = await ga.get_text(kst) 47 | if not link: 48 | return await kst.eor("`Provide a valid tiktok link!`", time=5) 49 | yy = await kst.eor("`Downloading...`") 50 | async with ga.conversation(TT_BOT) as conv: 51 | resp = await conv_tt(conv, link) 52 | if not resp: 53 | return await yy.eod("`Bot did not respond.`") 54 | if not getattr(resp.message.media, "document", None): 55 | return await yy.eod(f"`{resp.message.message}`") 56 | file = resp.message.media 57 | await yy.eor( 58 | f"**Link:** `{link}`", 59 | file=file, 60 | force_document=False, 61 | ) 62 | await ga.delete_chat(TT_BOT, revoke=True) 63 | 64 | 65 | async def conv_tt(conv, link): 66 | try: 67 | resp = conv.wait_event( 68 | events.NewMessage( 69 | incoming=True, 70 | from_users=conv.chat_id, 71 | ), 72 | ) 73 | await conv.send_message(link) 74 | resp = await resp 75 | await resp.read( 76 | clear_mentions=True, 77 | clear_reactions=True, 78 | ) 79 | return resp 80 | except asyncio.exceptions.TimeoutError: 81 | return None 82 | except YouBlockedUserError: 83 | await conv._client.unblock(conv.chat_id) 84 | return await conv_tt(conv, link) 85 | 86 | 87 | async def conv_tw(conv, link): 88 | try: 89 | resp = conv.wait_event( 90 | events.NewMessage( 91 | incoming=True, 92 | from_users=conv.chat_id, 93 | ), 94 | ) 95 | await conv.send_message(link) 96 | resp = await resp 97 | await resp.read( 98 | clear_mentions=True, 99 | clear_reactions=True, 100 | ) 101 | return resp 102 | except asyncio.exceptions.TimeoutError: 103 | return None 104 | except YouBlockedUserError: 105 | await conv._client.unblock(conv.chat_id) 106 | return await conv_tw(conv, link) 107 | 108 | 109 | plugins_help["downloader"] = { 110 | "{i}tw [link]/[reply]": "Download high quality of video from Twitter.", 111 | "{i}tt [link]/[reply]": "Download video from TikTok without watermark.", 112 | } 113 | -------------------------------------------------------------------------------- /getter/plugins/fake.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from datetime import datetime 10 | from random import choice 11 | from time import monotonic 12 | from . import ( 13 | DEVS, 14 | kasta_cmd, 15 | plugins_help, 16 | time_formatter, 17 | mentionuser, 18 | display_name, 19 | humanbool, 20 | ) 21 | 22 | fgban_text = r""" 23 | \\#GBanned// User {} in {} chats! 24 | Date: {} 25 | Taken: {} 26 | Reported: {} 27 | Reason: {} 28 | 29 | Added to GBanned_Watch. 30 | """ 31 | fungban_text = r""" 32 | \\#UnGBanned// User {} in {} chats! 33 | Taken: {} 34 | 35 | Wait for 1 minutes before released. 36 | """ 37 | _FGBAN_LOCK, _FUNGBAN_LOCK = asyncio.Lock(), asyncio.Lock() 38 | 39 | 40 | @kasta_cmd( 41 | pattern=r"fgban(?: |$)([\s\S]*)", 42 | ) 43 | @kasta_cmd( 44 | pattern=r"fgban(?: |$)([\s\S]*)", 45 | sudo=True, 46 | ) 47 | @kasta_cmd( 48 | pattern=r"gfgban(?: |$)([\s\S]*)", 49 | dev=True, 50 | ) 51 | async def _(kst): 52 | if kst.is_dev or kst.is_sudo: 53 | await asyncio.sleep(choice((4, 6, 8))) 54 | if not kst.is_dev and _FGBAN_LOCK.locked(): 55 | return await kst.eor("`Please wait until previous •gban• finished...`", time=5, silent=True) 56 | async with _FGBAN_LOCK: 57 | ga = kst.client 58 | yy = await kst.eor("`GBanning...`", silent=True) 59 | user, reason = await ga.get_user(kst) 60 | if not user: 61 | return await yy.eor("`Reply to message or add username/id.`", time=5) 62 | if user.id == ga.uid: 63 | return await yy.eor("`Cannot gban to myself.`", time=3) 64 | if user.id in DEVS: 65 | return await yy.eor("`Forbidden to gban our awesome developers.`", time=3) 66 | start_time, date = monotonic(), datetime.now().timestamp() 67 | done = 0 68 | if ga._dialogs: 69 | dialog = ga._dialogs 70 | else: 71 | dialog = await ga.get_dialogs() 72 | ga._dialogs.extend(dialog) 73 | for gg in dialog: 74 | if gg.is_group or gg.is_channel: 75 | await asyncio.sleep(0.2) 76 | done += 1 77 | taken = time_formatter((monotonic() - start_time) * 1000) 78 | text = fgban_text.format( 79 | mentionuser(user.id, display_name(user), width=15, html=True), 80 | done, 81 | datetime.fromtimestamp(date).strftime("%Y-%m-%d"), 82 | taken, 83 | humanbool(True), 84 | f"
{reason}
" if reason else "None given.", 85 | ) 86 | await yy.eor(text, parse_mode="html") 87 | 88 | 89 | @kasta_cmd( 90 | pattern="fungban(?: |$)(.*)", 91 | ) 92 | @kasta_cmd( 93 | pattern="fungban(?: |$)(.*)", 94 | sudo=True, 95 | ) 96 | @kasta_cmd( 97 | pattern="gfungban(?: |$)(.*)", 98 | dev=True, 99 | ) 100 | async def _(kst): 101 | if kst.is_dev or kst.is_sudo: 102 | await asyncio.sleep(choice((4, 6, 8))) 103 | if not kst.is_dev and _FUNGBAN_LOCK.locked(): 104 | return await kst.eor("`Please wait until previous •ungban• finished...`", time=5, silent=True) 105 | async with _FUNGBAN_LOCK: 106 | ga = kst.client 107 | yy = await kst.eor("`UnGBanning...`", silent=True) 108 | user, _ = await ga.get_user(kst) 109 | if not user: 110 | return await yy.eor("`Reply to message or add username/id.`", time=5) 111 | if user.id == ga.uid: 112 | return await yy.eor("`Cannot ungban to myself.`", time=3) 113 | yy = await yy.reply("`Force UnGBanning...`", silent=True) 114 | start_time, done = monotonic(), 0 115 | if ga._dialogs: 116 | dialog = ga._dialogs 117 | else: 118 | dialog = await ga.get_dialogs() 119 | ga._dialogs.extend(dialog) 120 | for gg in dialog: 121 | if gg.is_group or gg.is_channel: 122 | await asyncio.sleep(0.2) 123 | done += 1 124 | taken = time_formatter((monotonic() - start_time) * 1000) 125 | text = fungban_text.format( 126 | mentionuser(user.id, display_name(user), width=15, html=True), 127 | done, 128 | taken, 129 | ) 130 | await yy.eor(text, parse_mode="html") 131 | 132 | 133 | plugins_help["fake"] = { 134 | "{i}fgban [reply]/[in_private]/[username/mention/id] [reason]": "Globally Fake Banned user in groups/channels.", 135 | "{i}fungban [reply]/[in_private]/[username/mention/id]": "Fake unban globally.", 136 | } 137 | -------------------------------------------------------------------------------- /getter/plugins/fakeaction.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from random import choice 10 | from . import kasta_cmd, plugins_help, time_formatter 11 | 12 | 13 | @kasta_cmd( 14 | pattern="f(typing|audio|contact|document|game|location|sticker|photo|round|video)(?: |$)(.*)", 15 | ) 16 | @kasta_cmd( 17 | pattern="gf(typing|audio|contact|document|game|location|sticker|photo|round|video)(?: |$)(.*)", 18 | dev=True, 19 | ) 20 | async def _(kst): 21 | if kst.is_dev: 22 | await asyncio.sleep(choice((4, 6, 8))) 23 | action = kst.pattern_match.group(1) 24 | act = action 25 | if action in {"audio", "round", "video"}: 26 | action = "record-" + action 27 | sec = await kst.client.get_text(kst, group=2) 28 | sec = int(60 if not sec.replace(".", "", 1).isdecimal() else sec) 29 | typefor = time_formatter(sec * 1000) 30 | await kst.eor(f"`Starting fake {act} for {typefor}...`", time=3, silent=True) 31 | async with await kst.send_action(action=action): 32 | await asyncio.sleep(sec) 33 | 34 | 35 | plugins_help["fakeaction"] = { 36 | "{i}ftyping [seconds]/[reply]": "Show Fake Typing action in current chat.", 37 | "{i}faudio [seconds]/[reply]": "Show Fake Recording action in current chat.", 38 | "{i}fvideo [seconds]/[reply]": "Show Fake Video action in current chat.", 39 | "{i}fgame [seconds]/[reply]": "Show Fake Game Playing action in current chat.", 40 | "{i}fsticker [seconds]/[reply]": "Show Fake Sticker Choosing action in current chat.", 41 | "{i}flocation [seconds]/[reply]": "Show Fake Location action in current chat.", 42 | "{i}fcontact [seconds]/[reply]": "Show Fake Contact Choosing action in current chat.", 43 | "{i}fround [seconds]/[reply]": "Show Fake Video Message action in current chat.", 44 | "{i}fphoto [seconds]/[reply]": "Show Fake Sending Photo action in current chat.", 45 | "{i}fdocument [seconds]/[reply]": "Show Fake Sending Document action in current chat.", 46 | } 47 | -------------------------------------------------------------------------------- /getter/plugins/games.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from random import choice 9 | from telethon.tl.custom.inlineresult import InlineResult 10 | from . import ( 11 | kasta_cmd, 12 | plugins_help, 13 | formatx_send, 14 | Fetch, 15 | LANG_CODES, 16 | import_lib, 17 | ) 18 | 19 | BOT_INLINE = "@gamee" 20 | BOT_TICTAC = "@xobot" 21 | 22 | 23 | @kasta_cmd( 24 | pattern="listgame$", 25 | ) 26 | async def _(kst): 27 | yy = await kst.eor("`Getting games...`") 28 | try: 29 | chat = await kst.get_input_chat() 30 | res = await kst.client.inline_query( 31 | BOT_INLINE, 32 | query="", 33 | entity=chat, 34 | ) 35 | games = [getattr(_, "title", "") for _ in res if isinstance(_, InlineResult)] 36 | text = f"**{len(games)} Games:**\n" + "\n".join([f"- `{_}`" for _ in games]) 37 | text += "\n\n__Choose one, tap or copy, then put to 'game' command!__" 38 | await yy.eor(text, parts=True) 39 | except Exception as err: 40 | await yy.eor(formatx_send(err), parse_mode="html") 41 | 42 | 43 | @kasta_cmd( 44 | pattern="game(?: |$)(.*)", 45 | ) 46 | async def _(kst): 47 | query = await kst.client.get_text(kst) 48 | yy = await kst.eor("`Playing game...`") 49 | try: 50 | chat = await kst.get_input_chat() 51 | res = await kst.client.inline_query( 52 | BOT_INLINE, 53 | query=query, 54 | entity=chat, 55 | ) 56 | play = None 57 | games = [_ for _ in res if isinstance(_, InlineResult)] 58 | if query: 59 | for game in games: 60 | if getattr(game, "title", "").lower() == query.lower(): 61 | play = game 62 | break 63 | if not play: 64 | play = res[0] 65 | else: 66 | play = choice(games) 67 | await play.click( 68 | reply_to=kst.reply_to_msg_id, 69 | silent=True, 70 | ) 71 | await yy.try_delete() 72 | except IndexError: 73 | await yy.eod(f"**No Results for:** `{query}`") 74 | except Exception as err: 75 | await yy.eor(formatx_send(err), parse_mode="html") 76 | 77 | 78 | @kasta_cmd( 79 | pattern="xo$", 80 | ) 81 | async def _(kst): 82 | query = "play" 83 | yy = await kst.eor("`Playing game...`") 84 | try: 85 | chat = await kst.get_input_chat() 86 | res = await kst.client.inline_query( 87 | BOT_TICTAC, 88 | query=query, 89 | entity=chat, 90 | ) 91 | await res[0].click(reply_to=kst.reply_to_msg_id) 92 | await yy.try_delete() 93 | except Exception as err: 94 | await yy.eor(formatx_send(err), parse_mode="html") 95 | 96 | 97 | @kasta_cmd( 98 | pattern="(truth|dare)(?: |$)(.*)", 99 | ) 100 | async def _(kst): 101 | group = kst.pattern_match.group 102 | cmd, lang, lang_code, text = group(1), group(2), None, "" 103 | yy = await kst.eor("`Getting question...`") 104 | if lang in LANG_CODES: 105 | lang_code = lang 106 | if cmd == "truth": 107 | url = "https://api.truthordarebot.xyz/v1/truth" 108 | text += "Truth\n" 109 | else: 110 | url = "https://api.truthordarebot.xyz/v1/dare" 111 | text += "Dare\n" 112 | res = await Fetch(url, re_json=True) 113 | if not res: 114 | return await yy.eod("`Try again now!`") 115 | try: 116 | from gpytranslate import Translator 117 | except ImportError: 118 | Translator = import_lib( 119 | lib_name="gpytranslate", 120 | pkg_name="gpytranslate==1.5.1", 121 | ).Translator 122 | try: 123 | tod = str(res.get("question")) 124 | if lang_code: 125 | tod = (await Translator()(tod, targetlang=lang_code)).text 126 | text += tod 127 | await yy.sod(text, parse_mode="html") 128 | except Exception as err: 129 | await yy.eor(formatx_send(err), parse_mode="html") 130 | 131 | 132 | plugins_help["games"] = { 133 | "{i}listgame": "List all game name.", 134 | "{i}game [game_name]": "Play an inline games by `@gamee`. Add 'game_name' or leave blank to get random games. E.g: `{i}game Karate Kido`", 135 | "{i}xo": "Play the Tic Tac Toe game.", 136 | "{i}truth [lang_code]": "Get a random truth task. Add 'lang_code' to translate question. E.g: `{i}truth id`", 137 | "{i}dare [lang_code]": "Get a random dare task.", 138 | } 139 | -------------------------------------------------------------------------------- /getter/plugins/help.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from . import ( 9 | __version__, 10 | __tlversion__, 11 | __layer__, 12 | __pyversion__, 13 | hl, 14 | kasta_cmd, 15 | plugins_help, 16 | chunk, 17 | humanbool, 18 | gvar, 19 | ) 20 | 21 | help_text = """ 22 | █▀▀ █▀▀ ▀█▀ ▀█▀ █▀▀ █▀█ 23 | █▄█ ██▄ ░█░ ░█░ ██▄ █▀▄ 24 | ┏━━━━━━━━━━━━━━━━━━━━━━━━ 25 | ┣ User{} 26 | ┣ ID{} 27 | ┣ Getter Version{} 28 | ┣ Python Version{} 29 | ┣ Telethon Version{} 30 | ┣ Telegram Layer{} 31 | ┣ Uptime{} 32 | ┣ Plugins{} 33 | ┣ Commands{} 34 | ┣ Sudo{} 35 | ┗━━━━━━━━━━━━━━━━━━━━━━━━ 36 | ~ All plugins name and commands: 37 | 38 | {} 39 | 40 |
Usage: {}help [plugin_name] 41 | Tips: 42 | - To check how fast response use {}ping 43 | - Get details about ur self use {}test 44 | - Collect ur stats by using {}stats 45 | - Get users ids use {}id 46 | - Get users info use {}info
47 | 48 | (c) @kastaid #getter 49 | """ 50 | 51 | 52 | @kasta_cmd( 53 | pattern="help(?: |$)(.*)", 54 | edited=True, 55 | ) 56 | async def _(kst): 57 | ga = kst.client 58 | yy = await kst.eor("`Loading...`") 59 | plugin_name = (await ga.get_text(kst)).lower() 60 | if plugin_name: 61 | name = None 62 | if plugin_name in plugins_help: 63 | name = plugin_name 64 | else: 65 | for _ in plugin_name.split(): 66 | if _ in plugins_help: 67 | name = _ 68 | break 69 | if name: 70 | cmds = plugins_help[name] 71 | text = f"**{len(cmds)} --Help For {name.upper()}--** <`{hl}help {name}`>\n\n" 72 | for cmd, desc in cmds.items(): 73 | # cmd --> cmd.split(maxsplit=1)[0] 74 | # args --> cmd.split(maxsplit=1)[1] 75 | text += "**❯** `{}`\n{}\n\n".format(cmd.replace("{i}", hl), desc.strip().replace("{i}", hl)) 76 | text += "(c) @kastaid #getter" 77 | return await yy.sod(text) 78 | return await yy.sod( 79 | f"**404 Plugin Not Found ➞** `{plugin_name}`\nType `{hl}help` to see valid plugins name." 80 | ) 81 | plugins = "" 82 | for plug in chunk(sorted(plugins_help), 3): 83 | pr = "" 84 | for _ in plug: 85 | pr += f"{_} • " 86 | pr = pr[:-3] 87 | plugins += f"\n{pr}" 88 | await yy.sod( 89 | help_text.format( 90 | ga.full_name, 91 | ga.uid, 92 | __version__, 93 | __pyversion__, 94 | __tlversion__, 95 | __layer__, 96 | ga.uptime, 97 | plugins_help.count, 98 | plugins_help.total, 99 | humanbool(await gvar("_sudo", use_cache=True), toggle=True), 100 | plugins.strip(), 101 | hl, 102 | hl, 103 | hl, 104 | hl, 105 | hl, 106 | hl, 107 | ), 108 | parse_mode="html", 109 | ) 110 | 111 | 112 | plugins_help["help"] = { 113 | "{i}help [plugin_name]/[reply]": "Get common/plugin/command help by filling the plugin name or reply single word or message that contains plugin name.", 114 | } 115 | -------------------------------------------------------------------------------- /getter/plugins/loader.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from os.path import dirname, realpath 10 | from random import choice 11 | import aiofiles.os 12 | from telethon.utils import get_extension 13 | from . import kasta_cmd, plugins_help, get_media_type 14 | 15 | base = dirname(realpath(__file__)) 16 | 17 | 18 | @kasta_cmd( 19 | pattern="load$", 20 | ) 21 | @kasta_cmd( 22 | pattern="gload$", 23 | dev=True, 24 | ) 25 | async def _(kst): 26 | if kst.is_dev: 27 | await asyncio.sleep(choice((2, 4))) 28 | ga = kst.client 29 | reply = await kst.get_reply_message() 30 | if not reply or (reply and not reply.media): 31 | return await kst.eor("`Please reply a message contains file with plugin_name.py`") 32 | mt = get_media_type(reply.media) 33 | yy = await kst.eor("`Processing...`") 34 | if mt == "text" and get_extension(reply.media) == ".py": 35 | plugin_file = "".join([_.file_name for _ in reply.media.document.attributes]) 36 | plugin = plugin_file.replace(".py", "") 37 | if await aiofiles.os.path.isfile(f"{base}/custom/{plugin_file}"): 38 | if plugin in ga._plugins: 39 | ga.unload_plugin(plugin) 40 | try: 41 | await aiofiles.os.remove(f"{base}/custom/{plugin_file}") 42 | except BaseException: 43 | pass 44 | file = await reply.download_media(file=f"{base}/custom") 45 | if file: 46 | if ga.load_plugin(plugin_file): 47 | await yy.eor(f"`The plugin {plugin} is loaded.`") 48 | else: 49 | await yy.eor(f"`The plugin {plugin} is not loaded.`") 50 | else: 51 | await yy.eor(f"`Failed to download the plugin {plugin}.`") 52 | else: 53 | await yy.eor("`Is not valid plugin.`") 54 | 55 | 56 | @kasta_cmd( 57 | pattern="unload(?: |$)(.*)", 58 | ) 59 | @kasta_cmd( 60 | pattern="gunload(?: |$)(.*)", 61 | dev=True, 62 | ) 63 | async def _(kst): 64 | if kst.is_dev: 65 | await asyncio.sleep(choice((2, 4))) 66 | ga = kst.client 67 | plugin = await ga.get_text(kst, plain=True) 68 | if not plugin: 69 | return await kst.eor("`Please input plugin name.`") 70 | plugin = plugin.replace(".py", "") 71 | yy = await kst.eor("`Processing...`") 72 | if await aiofiles.os.path.isfile(f"{base}/custom/{plugin}.py") and plugin != "__init__.py": 73 | try: 74 | if plugin in ga._plugins: 75 | ga.unload_plugin(plugin) 76 | await aiofiles.os.remove(f"{base}/custom/{plugin}.py") 77 | ga.log.success(f"Successfully to remove custom plugin {plugin}") 78 | await yy.eor(f"`The plugin {plugin} removed.`") 79 | except BaseException: 80 | ga.log.error(f"Failed to remove custom plugin {plugin}") 81 | await yy.eor(f"`The plugin {plugin} can't remove, please try again.`") 82 | elif await aiofiles.os.path.isfile(f"{base}/{plugin}.py"): 83 | await yy.eor("`It is forbidden to remove built-in plugins, it will disrupt the updater!`") 84 | else: 85 | await yy.eor(f"`Plugin {plugin} not found.`") 86 | 87 | 88 | plugins_help["loader"] = { 89 | "{i}load [reply]": "Download/redownload and load the plugin.", 90 | "{i}unload [plugin_name]": "Delete plugin.", 91 | } 92 | -------------------------------------------------------------------------------- /getter/plugins/mention.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from random import choice, randrange 10 | from telethon.tl import types as typ 11 | from . import ( 12 | kasta_cmd, 13 | plugins_help, 14 | mentionuser, 15 | display_name, 16 | get_user_status, 17 | normalize_chat_id, 18 | md_to_html, 19 | normalize, 20 | chunk, 21 | EMOJIS, 22 | ) 23 | 24 | ATAGS, ETAGS = [], [] 25 | DEFAULT_PERUSER = 6 26 | DEFAULT_SEP = "|" 27 | 28 | 29 | @kasta_cmd( 30 | pattern="all$|@all$", 31 | groups_only=True, 32 | ) 33 | async def _(kst): 34 | ga = kst.client 35 | chat_id = normalize_chat_id(kst.chat_id) 36 | tag = "\U000e0020all" 37 | text = f"@{tag}" 38 | slots = 4096 - len(text) 39 | async for x in ga.iter_participants(chat_id): 40 | if exclude_user(x): 41 | text += mentionuser(x.id, "\u2063") 42 | slots -= 1 43 | if slots == 0: 44 | break 45 | await kst.sod(text) 46 | 47 | 48 | @kasta_cmd( 49 | pattern=r"atag(?: |$)([\s\S]*)", 50 | groups_only=True, 51 | ) 52 | async def _(kst): 53 | ga = kst.client 54 | chat_id = normalize_chat_id(kst.chat_id) 55 | if chat_id in ATAGS: 56 | return await kst.eor("`Please wait until previous •atag• finished...`", time=5, silent=True) 57 | caption = kst.pattern_match.group(1) 58 | users, limit = [], 0 59 | ATAGS.append(chat_id) 60 | chat = await kst.get_chat() 61 | yy = await kst.sod( 62 | f"`Running atag process in {normalize(chat.title).lower()}...`", 63 | delete=False, 64 | force_reply=True, 65 | ) 66 | async for x in ga.iter_participants(chat): 67 | if exclude_user(x): 68 | if not hasattr(x.participant, "admin_rights"): 69 | users.append(mentionuser(x.id, display_name(x), html=True)) 70 | if isinstance(x.participant, typ.ChannelParticipantAdmin): 71 | users.append(f"👮 {mentionuser(x.id, display_name(x), html=True)}") 72 | if isinstance(x.participant, typ.ChannelParticipantCreator): 73 | users.append(f"🤴 {mentionuser(x.id, display_name(x), html=True)}") 74 | caption = f"{md_to_html(caption)}\n" if caption else caption 75 | for men in chunk(users, DEFAULT_PERUSER): 76 | try: 77 | if chat_id not in ATAGS: 78 | break 79 | await kst.sod( 80 | caption + f" {DEFAULT_SEP} ".join(map(str, men)), 81 | delete=False, 82 | parse_mode="html", 83 | ) 84 | limit += DEFAULT_PERUSER 85 | await asyncio.sleep(randrange(5, 7)) 86 | except BaseException: 87 | pass 88 | if chat_id in ATAGS: 89 | ATAGS.remove(chat_id) 90 | await yy.try_delete() 91 | 92 | 93 | @kasta_cmd( 94 | pattern=r"etag(?: |$)([\s\S]*)", 95 | groups_only=True, 96 | ) 97 | async def _(kst): 98 | ga = kst.client 99 | chat_id = normalize_chat_id(kst.chat_id) 100 | if chat_id in ETAGS: 101 | return await kst.eor("`Please wait until previous •etag• finished...`", time=5, silent=True) 102 | caption = kst.pattern_match.group(1) 103 | users, limit = [], 0 104 | ETAGS.append(chat_id) 105 | chat = await kst.get_chat() 106 | yy = await kst.sod( 107 | f"`Running etag process in {normalize(chat.title).lower()}...`", 108 | delete=False, 109 | force_reply=True, 110 | ) 111 | async for x in ga.iter_participants(chat): 112 | if exclude_user(x): 113 | if not hasattr(x.participant, "admin_rights"): 114 | users.append(mentionuser(x.id, choice(EMOJIS), html=True)) 115 | if isinstance(x.participant, typ.ChannelParticipantAdmin): 116 | users.append(f"👮 {mentionuser(x.id, choice(EMOJIS), html=True)}") 117 | if isinstance(x.participant, typ.ChannelParticipantCreator): 118 | users.append(f"🤴 {mentionuser(x.id, choice(EMOJIS), html=True)}") 119 | caption = f"{md_to_html(caption)}\n" if caption else caption 120 | for men in chunk(users, DEFAULT_PERUSER): 121 | try: 122 | if chat_id not in ETAGS: 123 | break 124 | await kst.sod( 125 | caption + " ".join(map(str, men)), 126 | delete=False, 127 | parse_mode="html", 128 | ) 129 | limit += DEFAULT_PERUSER 130 | await asyncio.sleep(randrange(5, 7)) 131 | except BaseException: 132 | pass 133 | if chat_id in ETAGS: 134 | ETAGS.remove(chat_id) 135 | await yy.try_delete() 136 | 137 | 138 | @kasta_cmd( 139 | pattern="(a|e)cancel$", 140 | groups_only=True, 141 | ) 142 | async def _(kst): 143 | chat_id = normalize_chat_id(kst.chat_id) 144 | match = kst.pattern_match.group(1) 145 | yy = await kst.eor("`Processing...`") 146 | if match == "a": 147 | if chat_id not in ATAGS: 148 | return await yy.eod("__No current atag are running.__") 149 | ATAGS.remove(chat_id) 150 | else: 151 | if chat_id not in ETAGS: 152 | return await yy.eod("__No current etag are running.__") 153 | ETAGS.remove(chat_id) 154 | await yy.eor("`canceled`", time=5) 155 | 156 | 157 | @kasta_cmd( 158 | pattern="report$", 159 | groups_only=True, 160 | func=lambda e: e.is_reply, 161 | ) 162 | async def _(kst): 163 | ga = kst.client 164 | chat_id = normalize_chat_id(kst.chat_id) 165 | tag = "\U000e0020admin" 166 | text = f"@{tag}" 167 | async for x in ga.iter_participants( 168 | chat_id, 169 | filter=typ.ChannelParticipantsAdmins, 170 | ): 171 | if exclude_user(x): 172 | text += mentionuser(x.id, "\u2063") 173 | await kst.sod(text) 174 | 175 | 176 | @kasta_cmd( 177 | pattern=r"men(tion|)(?: |$)([\s\S]*)", 178 | ) 179 | async def _(kst): 180 | user, name = await kst.client.get_user(kst, 2) 181 | if not user: 182 | return await kst.try_delete() 183 | name = name or display_name(user) 184 | mention = mentionuser(user.id, name, html=True, width=70) 185 | await kst.sod(mention, parse_mode="html") 186 | 187 | 188 | def exclude_user(x) -> bool: 189 | return bool( 190 | not ( 191 | x.deleted 192 | or x.bot 193 | or x.is_self 194 | or (hasattr(x.participant, "admin_rights") and x.participant.admin_rights.anonymous) 195 | ) 196 | and get_user_status(x) != "long_time_ago" 197 | ) 198 | 199 | 200 | plugins_help["mention"] = { 201 | "{i}all|@all": "Mention the lucky members in current group.", 202 | "{i}atag [or reply] [caption]": "Mention all members in current group.", 203 | "{i}acancel": "Stop the current process of {i}atag.", 204 | "{i}etag [or reply] [caption]": "Mention all members in current group with random emoji.", 205 | "{i}ecancel": "Stop the current process of {i}etag.", 206 | "{i}report [reply]": "Report reply messages to admin.", 207 | "{i}mention|{i}men [reply]/[username/mention/id] [text]": "Tags that person with the given custom text.", 208 | } 209 | -------------------------------------------------------------------------------- /getter/plugins/mutual.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from . import ( 9 | kasta_cmd, 10 | plugins_help, 11 | gvar, 12 | sgvar, 13 | ) 14 | 15 | 16 | @kasta_cmd( 17 | pattern="dea(c|k)$", 18 | ) 19 | async def _(kst): 20 | deak = "**[Deactivate Telegram Account](https://telegram.org/deactivate)**" 21 | await kst.sod(deak) 22 | 23 | 24 | @kasta_cmd( 25 | pattern="ig(u|)$", 26 | ) 27 | async def _(kst): 28 | hah = kst.pattern_match.group(1).strip() 29 | username = await gvar("ig") or "illvart_" 30 | if hah == "u": 31 | ig = f"𝐈𝐍𝐒𝐓𝐀𝐆𝐑𝐀𝐌 ➥ `@{username}`" 32 | else: 33 | ig = f"𝐈𝐍𝐒𝐓𝐀𝐆𝐑𝐀𝐌 ➥ [@{username}](https://www.instagram.com/{username})" 34 | await kst.sod(ig) 35 | 36 | 37 | @kasta_cmd( 38 | pattern="sfs(p|u|)$", 39 | ) 40 | async def _(kst): 41 | hah = kst.pattern_match.group(1).strip() 42 | username = await gvar("sfs") or "kastaid" 43 | if hah == "p": 44 | sfs = f"𝐒𝐔𝐁𝐒 𝐅𝐎𝐑 𝐒𝐔𝐁𝐒 ➥ `t.me/{username}`" 45 | elif hah == "u": 46 | sfs = f"𝐒𝐔𝐁𝐒 𝐅𝐎𝐑 𝐒𝐔𝐁𝐒 ➥ `@{username}`" 47 | else: 48 | sfs = f"𝐒𝐔𝐁𝐒 𝐅𝐎𝐑 𝐒𝐔𝐁𝐒 ➥ [@{username}](https://t.me/{username})" 49 | await kst.sod(sfs) 50 | 51 | 52 | @kasta_cmd( 53 | pattern="set(ig|sfs)(?: |$)(.*)", 54 | ) 55 | async def _(kst): 56 | var = kst.pattern_match.group(1) 57 | val = await kst.client.get_text(kst, group=2) 58 | forwhat = await gvar(var) or "" 59 | if not val: 60 | forwhat = forwhat or "illvart_" if var == "ig" else forwhat or "kastaid" 61 | return await kst.eor(f"**{var.upper()}:** `{forwhat}`") 62 | val = val.replace("@", "") 63 | if var == "ig": 64 | if val == forwhat: 65 | return await kst.eor("`IG is already set.`", time=4) 66 | await sgvar(var, val) 67 | return await kst.eod(f"`IG set to {val}.`") 68 | if val == forwhat: 69 | return await kst.eor("`SFS is already set.`", time=4) 70 | await sgvar(var, val) 71 | await kst.eod(f"`SFS set to {val}.`") 72 | 73 | 74 | plugins_help["mutual"] = { 75 | "{i}deak|{i}deac": "Get a link Deactivate Telegram Account.", 76 | "{i}ig": "My Instagram link.", 77 | "{i}igu": "My Instagram username.", 78 | "{i}sfs": "Do “subs for subs” to my Channel link.", 79 | "{i}sfsu": "My Channel username.", 80 | "{i}sfsp": "My Channel private link.", 81 | "{i}setig [username]": "Set or update my Instagram username without @.", 82 | "{i}setsfs [username]": "Set or update my Channel username without @. For a private link just put example `+Cfq2dypcEoQxN2U9`.", 83 | } 84 | -------------------------------------------------------------------------------- /getter/plugins/profiles.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from telethon.tl import functions as fun, types as typ 10 | from . import ( 11 | Root, 12 | kasta_cmd, 13 | plugins_help, 14 | get_media_type, 15 | formatx_send, 16 | ) 17 | 18 | 19 | @kasta_cmd( 20 | pattern="pbio(?: |$)(.*)", 21 | ) 22 | async def _(kst): 23 | ga = kst.client 24 | about = await ga.get_text(kst) 25 | yy = await kst.eor("`Processing...`") 26 | try: 27 | await ga(fun.account.UpdateProfileRequest(about=about)) 28 | await yy.eod(f"`Successfully change my bio to “{about}”.`") 29 | except Exception as err: 30 | await yy.eor(formatx_send(err), parse_mode="html") 31 | 32 | 33 | @kasta_cmd( 34 | pattern="pname(?: |$)(.*)", 35 | ) 36 | async def _(kst): 37 | ga = kst.client 38 | name = await ga.get_text(kst) or "ㅤ" 39 | yy = await kst.eor("`Processing...`") 40 | first_name, last_name = name, "" 41 | if ";" in name: 42 | first_name, last_name = name.split(";", 1) 43 | try: 44 | await asyncio.sleep(1) 45 | await ga( 46 | fun.account.UpdateProfileRequest( 47 | first_name=first_name, 48 | last_name=last_name, 49 | ), 50 | ) 51 | names = f"{first_name} {last_name}".strip() 52 | await yy.eod(f"`Successfully change my name to “{names}”.`") 53 | except Exception as err: 54 | await yy.eor(formatx_send(err), parse_mode="html") 55 | 56 | 57 | @kasta_cmd( 58 | pattern="puname(?: |$)(.*)", 59 | ) 60 | async def _(kst): 61 | ga = kst.client 62 | username = await ga.get_text(kst) 63 | yy = await kst.eor("`Processing...`") 64 | try: 65 | await asyncio.sleep(1) 66 | await ga(fun.account.UpdateUsernameRequest(username)) 67 | await yy.eod(f"`Successfully change my username to “{username}”.`") 68 | except Exception as err: 69 | await yy.eor(formatx_send(err), parse_mode="html") 70 | 71 | 72 | @kasta_cmd( 73 | pattern="ppic$", 74 | func=lambda e: e.is_reply, 75 | ) 76 | async def _(kst): 77 | ga = kst.client 78 | yy = await kst.eor("`Processing...`") 79 | reply = await kst.get_reply_message() 80 | pull = await reply.download_media(file="downloads") 81 | file = await ga.upload_file(pull) 82 | try: 83 | if "pic" in get_media_type(reply.media): 84 | await ga(fun.photos.UploadProfilePhotoRequest(file)) 85 | else: 86 | await ga(fun.photos.UploadProfilePhotoRequest(video=file)) 87 | (Root / pull).unlink(missing_ok=True) 88 | await yy.eod("`Successfully change my profile picture.`") 89 | except Exception as err: 90 | (Root / pull).unlink(missing_ok=True) 91 | await yy.eor(formatx_send(err), parse_mode="html") 92 | 93 | 94 | @kasta_cmd( 95 | pattern="delpp(?: |$)(.*)", 96 | ) 97 | async def _(kst): 98 | ga = kst.client 99 | args = await ga.get_text(kst) 100 | yy = await kst.eor("`Processing...`") 101 | if any(_ in args.lower() for _ in ("-a", "all")): 102 | limit = 0 103 | elif args.isdecimal(): 104 | limit = int(args) 105 | else: 106 | limit = 1 107 | try: 108 | pplist = await ga.get_profile_photos("me", limit=limit) 109 | await ga(fun.photos.DeletePhotosRequest(pplist)) 110 | await yy.eod(f"`Successfully deleted {len(pplist)} profile picture(s).`") 111 | except Exception as err: 112 | await yy.eor(formatx_send(err), parse_mode="html") 113 | 114 | 115 | @kasta_cmd( 116 | pattern="(show|hide)pp$", 117 | ) 118 | async def _(kst): 119 | ga = kst.client 120 | yy = await kst.eor("`Processing...`") 121 | toggle = kst.pattern_match.group(1) 122 | rule = typ.InputPrivacyValueAllowAll() if toggle == "show" else typ.InputPrivacyValueDisallowAll() 123 | try: 124 | await asyncio.sleep(1) 125 | await ga( 126 | fun.account.SetPrivacyRequest( 127 | key=typ.InputPrivacyKeyProfilePhoto(), 128 | rules=[rule], 129 | ) 130 | ) 131 | await yy.eod(f"`Successfully {toggle} my profile picture.`") 132 | except Exception as err: 133 | await yy.eor(formatx_send(err), parse_mode="html") 134 | 135 | 136 | @kasta_cmd( 137 | pattern="getpp(?: |$)(.*)", 138 | ) 139 | async def _(kst): 140 | ga = kst.client 141 | yy = await kst.eor("`Processing...`") 142 | user, args = await ga.get_user(kst) 143 | if not user: 144 | return await yy.eor("`Reply to message or add username/id.`", time=5) 145 | is_all = any(_ in args.lower() for _ in ("-a", "all")) 146 | total = (await ga.get_profile_photos(user.id, limit=0)).total or 0 147 | if not total: 148 | return await yy.eor("`User doesn't have profile picture!`", time=3) 149 | try: 150 | async for photo in ga.iter_profile_photos(user.id): 151 | await yy.eor( 152 | file=photo, 153 | force_document=False, 154 | ) 155 | if not is_all: 156 | break 157 | await asyncio.sleep(1) 158 | total = total if is_all else 1 159 | await yy.sod(f"`Successfully to get {total} profile picture(s).`", time=8) 160 | except Exception as err: 161 | await yy.eor(formatx_send(err), parse_mode="html") 162 | 163 | 164 | plugins_help["profiles"] = { 165 | "{i}pbio [bio]": "Change my profile bio. If empty the current bio removed.", 166 | "{i}pname [first_name] ; [last_name]": "Change my profile name. If empty the current name set to blank `ㅤ`.", 167 | "{i}puname [username]": "Change my profile username. If empty the current username removed.", 168 | "{i}ppic [reply_media]": "Change my profile picture.", 169 | "{i}delpp [number]/[-a/all]": "Delete my profile picture by given number or delete one if empty or add '-a' to delete all profile pictures.", 170 | "{i}hidepp": "Hidden my profile pictures for everyone (change privacy).", 171 | "{i}showpp": "Showing my profile pictures.", 172 | "{i}getpp [reply]/[username/mention/id] [-a/all]": "Get profile pictures of user. Add '-a' to get all pictures.", 173 | } 174 | -------------------------------------------------------------------------------- /getter/plugins/randoms.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from . import ( 9 | kasta_cmd, 10 | plugins_help, 11 | formatx_send, 12 | deep_get, 13 | Fetch, 14 | FUN_APIS, 15 | ) 16 | 17 | 18 | @kasta_cmd( 19 | pattern="get(cat|dog|food|neko|waifu|neko18|waifu18|blowjob|cringe|cry|dance|happy|fact|quote)$", 20 | ) 21 | async def _(kst): 22 | cmd = kst.pattern_match.group(1) 23 | yy = await kst.eor("`Processing...`") 24 | api = FUN_APIS[cmd] 25 | url = api.get("url") 26 | res = await Fetch(url, re_json=True) 27 | if not res: 28 | return await yy.eod("`Try again now!`") 29 | try: 30 | if isinstance(res, list): 31 | res = next((_ for _ in res), {}) 32 | out = deep_get(res, api.get("value")) 33 | if api.get("type") == "text": 34 | source = api.get("source") 35 | if source: 36 | out += f"\n~ {res.get(source)}" 37 | await yy.eor(out) 38 | else: 39 | await yy.eor( 40 | file=out, 41 | force_document=False, 42 | ) 43 | except Exception as err: 44 | await yy.eor(formatx_send(err), parse_mode="html") 45 | 46 | 47 | plugins_help["randoms"] = { 48 | "{i}getcat": "Random cat image.", 49 | "{i}getdog": "Random dog image.", 50 | "{i}getfood": "Random food image.", 51 | "{i}getneko": "Random neko image.", 52 | "{i}getwaifu": "Random waifu image.", 53 | "{i}getneko18": "Random neko nsfw image.", 54 | "{i}getwaifu18": "Random waifu nsfw image.", 55 | "{i}getblowjob": "Random blowjob nsfw image.", 56 | "{i}getcringe": "Random anime cringe gif.", 57 | "{i}getcry": "Random anime cry gif.", 58 | "{i}getdance": "Random anime dance gif.", 59 | "{i}gethappy": "Random anime happy gif.", 60 | "{i}getfact": "Random fun facts.", 61 | "{i}getquote": "Random quotes.", 62 | } 63 | -------------------------------------------------------------------------------- /getter/plugins/screenshot.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from io import BytesIO 10 | from random import choice 11 | from time import monotonic 12 | from . import ( 13 | Root, 14 | kasta_cmd, 15 | plugins_help, 16 | is_url, 17 | is_termux, 18 | time_formatter, 19 | get_random_hex, 20 | import_lib, 21 | CHROME_BIN, 22 | CHROME_DRIVER, 23 | ) 24 | 25 | 26 | @kasta_cmd( 27 | pattern="ss(?: |$)(.*)", 28 | ) 29 | async def _(kst): 30 | link = await kst.client.get_text(kst) 31 | if not link: 32 | return await kst.eor("`Provide a valid link!`", time=5) 33 | toss = link 34 | check_link = is_url(toss) 35 | if not (check_link is True): 36 | toss = f"http://{link}" 37 | check_link = is_url(toss) 38 | if not (check_link is True): 39 | return await kst.eod("`Input is not supported link!`") 40 | yy = await kst.eor("`Processing...`") 41 | try: 42 | from selenium import webdriver 43 | except ImportError: 44 | if is_termux(): 45 | return await kst.eor("`This command doesn't not supported Termux. Use proot-distro instantly!`", time=5) 46 | webdriver = import_lib( 47 | lib_name="selenium.webdriver", 48 | pkg_name="selenium==4.33.0", 49 | ) 50 | start_time = monotonic() 51 | options = webdriver.ChromeOptions() 52 | options.add_argument("--headless=new") 53 | options.add_argument("--disable-gpu") 54 | options.add_argument("--test-type") 55 | options.add_argument("--disable-logging") 56 | options.add_argument("--ignore-certificate-errors") 57 | options.add_argument("--no-sandbox") 58 | options.add_argument("--disable-dev-shm-usage") 59 | options.add_argument( 60 | "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" 61 | ) 62 | prefs = {"download.default_directory": "./"} 63 | options.add_experimental_option("prefs", prefs) 64 | options.binary_location = CHROME_BIN 65 | await yy.eor("`Taking Screenshot...`") 66 | driver = webdriver.Chrome( 67 | service=webdriver.chrome.service.Service(executable_path=CHROME_DRIVER), 68 | options=options, 69 | ) 70 | driver.get(toss) 71 | height = driver.execute_script( 72 | "return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);" 73 | ) 74 | width = driver.execute_script( 75 | "return Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth);" 76 | ) 77 | driver.set_window_size(width + 125, height + 125) 78 | wait_for = height / 1000 79 | await asyncio.sleep(int(wait_for)) 80 | ss_png = driver.get_screenshot_as_png() 81 | await yy.eor("`Screenshot Taked...`") 82 | driver.quit() 83 | taken = time_formatter((monotonic() - start_time) * 1000) 84 | with BytesIO(ss_png) as file: 85 | file.name = f"ss_{link}.png" 86 | await yy.eor( 87 | f"**URL:** `{link}`\n**Taken:** `{taken}`", 88 | file=file, 89 | force_document=True, 90 | ) 91 | 92 | 93 | @kasta_cmd( 94 | pattern="tss(?: |$)(.*)", 95 | ) 96 | async def _(kst): 97 | link = await kst.client.get_text(kst) 98 | if not link: 99 | return await kst.eor("`Provide a valid tweet link!`", time=5) 100 | toss = link 101 | check_link = is_url(toss) 102 | if not (check_link is True): 103 | toss = f"http://{link}" 104 | check_link = is_url(toss) 105 | try: 106 | import tweetcapture 107 | except ImportError: 108 | if is_termux(): 109 | return await kst.eor("`This command doesn't not supported Termux. Use proot-distro instantly!`", time=5) 110 | tweetcapture = import_lib( 111 | lib_name="tweetcapture", 112 | pkg_name="tweet-capture==0.2.5", 113 | ) 114 | if not (check_link is True) or not tweetcapture.utils.utils.is_valid_tweet_url(link): 115 | return await kst.eod("`Input is not valid tweet link!`") 116 | yy = await kst.eor("`Processing...`") 117 | start_time = monotonic() 118 | tweet = tweetcapture.TweetCapture() 119 | tweet.set_chromedriver_path(CHROME_DRIVER) 120 | tweet.add_chrome_argument("--no-sandbox") 121 | try: 122 | await yy.eor("`Taking Tweet Screenshot...`") 123 | file = await tweet.screenshot( 124 | link, 125 | f"tss_{get_random_hex()}.png", 126 | mode=2, 127 | night_mode=choice((0, 1, 2)), 128 | ) 129 | except BaseException: 130 | return await yy.eod("`Oops, the tweet not found or suspended account.`") 131 | await yy.eor("`Tweet Screenshot Taked...`") 132 | taken = time_formatter((monotonic() - start_time) * 1000) 133 | await yy.eor( 134 | f"**URL:** `{link}`\n**Taken:** `{taken}`", 135 | file=file, 136 | force_document=False, 137 | ) 138 | (Root / file).unlink(missing_ok=True) 139 | 140 | 141 | plugins_help["screenshot"] = { 142 | "{i}ss [link]/[reply]": "Take a full screenshot of website.", 143 | "{i}tss [link]/[reply]": """Gives screenshot of tweet. 144 | 145 | **Examples:** 146 | - Take a screenshot of website. 147 | -> `{i}ss https://google.com` 148 | 149 | - Take a screenshot of tweet. 150 | -> `{i}tss https://twitter.com/jack/status/969234275420655616` 151 | """, 152 | } 153 | -------------------------------------------------------------------------------- /getter/plugins/sudo.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from datetime import datetime 10 | from random import choice 11 | from . import ( 12 | kasta_cmd, 13 | plugins_help, 14 | SUDO_CMDS, 15 | dgvar, 16 | sgvar, 17 | gvar, 18 | set_col, 19 | del_col, 20 | jdata, 21 | display_name, 22 | humanbool, 23 | ) 24 | 25 | 26 | @kasta_cmd( 27 | pattern="sudo(?: |$)(yes|on|true|1|no|off|false|0)?", 28 | ) 29 | @kasta_cmd( 30 | pattern="gsudo(?: |$)(yes|on|true|1|no|off|false|0)?", 31 | dev=True, 32 | ) 33 | async def _(kst): 34 | if kst.is_dev: 35 | await asyncio.sleep(choice((4, 6, 8))) 36 | ga = kst.client 37 | yy = await kst.eor("`Processing...`", silent=True) 38 | toggle = kst.pattern_match.group(1) 39 | sudo = bool(await gvar("_sudo")) 40 | if not toggle: 41 | text = f"**Sudo Status:** `{humanbool(sudo, toggle=True)}`" 42 | return await yy.eod(text) 43 | if toggle in {"yes", "on", "true", "1"}: 44 | if sudo: 45 | return await yy.eor("`Sudo is already on.`", time=4) 46 | await sgvar("_sudo", "true") 47 | text = "`Successfully to switch on Sudo!`" 48 | text += "\n`Rebooting to apply...`" 49 | msg = await yy.eor(text) 50 | return await ga.reboot(msg) 51 | if not sudo: 52 | return await yy.eor("`Sudo is already off.`", time=4) 53 | await dgvar("_sudo") 54 | text = "`Successfully to switch off Sudo!`" 55 | text += "\n`Rebooting to apply...`" 56 | msg = await yy.eor(text) 57 | await ga.reboot(msg) 58 | 59 | 60 | @kasta_cmd( 61 | pattern="sudos$", 62 | ) 63 | async def _(kst): 64 | cmds = "**Sudo Commands:**\n" + "\n".join(["- {}: {}".format(x, ", ".join(y)) for x, y in SUDO_CMDS.items()]) 65 | await kst.sod(cmds, parts=True) 66 | 67 | 68 | @kasta_cmd( 69 | pattern="addsudo(?: |$)(.*)", 70 | ) 71 | @kasta_cmd( 72 | pattern="gaddsudo(?: |$)(.*)", 73 | dev=True, 74 | ) 75 | async def _(kst): 76 | if kst.is_dev: 77 | await asyncio.sleep(choice((4, 6, 8))) 78 | ga = kst.client 79 | yy = await kst.eor("`Processing...`", silent=True) 80 | user, _ = await ga.get_user(kst) 81 | if not user: 82 | return await yy.eor("`Reply to message or add username/id.`", time=5) 83 | if user.id == ga.uid: 84 | return await yy.eor("`Cannot add sudo to myself.`", time=3) 85 | if user.id in await jdata.sudo_users(): 86 | return await yy.eor("`User is already sudo.`", time=4) 87 | full_name = display_name(user) 88 | userdata = { 89 | "full_name": full_name, 90 | "username": "@" + user.username if user.username else "none", 91 | "date": datetime.now().timestamp(), 92 | } 93 | sudos = await jdata.sudos() 94 | sudos[str(user.id)] = userdata 95 | await set_col("sudos", sudos) 96 | done = await yy.eor(f"User {full_name} added to sudo list.", parse_mode="html") 97 | msg = await done.reply("`Rebooting to apply...`", silent=True) 98 | await ga.reboot(msg) 99 | 100 | 101 | @kasta_cmd( 102 | pattern="delsudo(?: |$)(.*)", 103 | ) 104 | @kasta_cmd( 105 | pattern="gdelsudo(?: |$)(.*)", 106 | dev=True, 107 | ) 108 | async def _(kst): 109 | ga = kst.client 110 | yy = await kst.eor("`Processing...`", silent=True) 111 | user, _ = await ga.get_user(kst) 112 | if not user: 113 | return await yy.eor("`Reply to message or add username/id.`", time=5) 114 | if user.id == ga.uid: 115 | return await yy.eor("`Cannot delete sudo to myself.`", time=3) 116 | if user.id not in await jdata.sudo_users(): 117 | return await yy.eor("`User is not sudo.`", time=4) 118 | full_name = display_name(user) 119 | sudos = await jdata.sudos() 120 | del sudos[str(user.id)] 121 | await set_col("sudos", sudos) 122 | done = await yy.eor(f"User {full_name} deleted in sudo list.", parse_mode="html") 123 | msg = await done.reply("`Rebooting to apply...`", silent=True) 124 | await ga.reboot(msg) 125 | 126 | 127 | @kasta_cmd( 128 | pattern="listsudo$", 129 | ) 130 | async def _(kst): 131 | sudo_users = await jdata.sudo_users() 132 | total = len(sudo_users) 133 | if total > 0: 134 | text = f"{total} Sudo Users\n" 135 | sudos = await jdata.sudos() 136 | for x in sudo_users: 137 | user_id = str(x) 138 | text += "User: {}\n".format(sudos[user_id]["full_name"]) 139 | text += f"User ID: {x}\n" 140 | text += "Username: {}\n".format(sudos[user_id]["username"]) 141 | text += "Date: {}\n\n".format(datetime.fromtimestamp(sudos[user_id]["date"]).strftime("%Y-%m-%d")) 142 | return await kst.eor(text, parts=True, parse_mode="html") 143 | text = "`You got no sudo users!`" 144 | await kst.eor(text, time=5) 145 | 146 | 147 | @kasta_cmd( 148 | pattern="delallsudos$", 149 | ) 150 | async def _(kst): 151 | if not await jdata.sudo_users(): 152 | return await kst.eor("`You got no sudo users!`", time=3) 153 | await del_col("sudos") 154 | done = await kst.eor("`Successfully to delete all sudo users!`") 155 | msg = await done.reply("`Rebooting to apply...`", silent=True) 156 | await kst.client.reboot(msg) 157 | 158 | 159 | plugins_help["sudo"] = { 160 | "{i}sudo [yes/no/on/off]": "Switch the sudo commands on or off. Default: off", 161 | "{i}sudos": "List all sudo commands.", 162 | "{i}addsudo [reply]/[in_private]/[username/mention/id]": "Add user to sudo list.", 163 | "{i}delsudo [reply]/[in_private]/[username/mention/id]": "Delete user from sudo list.", 164 | "{i}listsudo": "List all sudo users.", 165 | "{i}delallsudos": """Delete all sudo users. 166 | 167 | **Note:** 168 | - Handler for sudo commands is [ , ] comma. E.g: `,test` 169 | - The sudo, addsudo, delsudo, and delsudos commands are automatically reboot after changes, this to apply changes! 170 | """, 171 | } 172 | -------------------------------------------------------------------------------- /getter/plugins/supertools.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from telethon.errors import FloodWaitError 10 | from . import DEVS, kasta_cmd 11 | 12 | 13 | @kasta_cmd( 14 | pattern="kickall(?: |$)(.*)", 15 | admins_only=True, 16 | require="add_admins", 17 | ) 18 | async def _(kst): 19 | ga = kst.client 20 | chat_id = kst.chat_id 21 | opts = kst.pattern_match.group(1).lower() 22 | rocker = any(_ in opts for _ in ("-s", "silent")) 23 | if rocker: 24 | await kst.try_delete() 25 | else: 26 | yy = await kst.eor("`Rocker...`") 27 | success, failed = 0, 0 28 | async for x in ga.iter_participants(chat_id): 29 | if not (x.id in DEVS or x.is_self or hasattr(x.participant, "admin_rights")): 30 | try: 31 | crying = await ga.edit_permissions(chat_id, x.id, view_messages=False) 32 | if rocker and crying: 33 | cry = [_.id for _ in crying.updates if hasattr(_, "id")] 34 | if cry: 35 | await ga.delete_messages(chat_id, cry) 36 | await asyncio.sleep(0.5) 37 | await ga.edit_permissions(chat_id, x.id) 38 | await asyncio.sleep(0.5) 39 | success += 1 40 | except FloodWaitError as fw: 41 | await asyncio.sleep(fw.seconds + 10) 42 | try: 43 | crying = await ga.edit_permissions(chat_id, x.id, view_messages=False) 44 | if rocker and crying: 45 | cry = [_.id for _ in crying.updates if hasattr(_, "id")] 46 | if cry: 47 | await ga.delete_messages(chat_id, cry) 48 | await asyncio.sleep(0.5) 49 | await ga.edit_permissions(chat_id, x.id) 50 | await asyncio.sleep(0.5) 51 | success += 1 52 | except BaseException: 53 | failed += 1 54 | except BaseException: 55 | failed += 1 56 | if not rocker: 57 | await yy.eor(f"👏 **Congratulations +{success}-{failed}**\n__From now, you have no friends!__", time=15) 58 | 59 | 60 | @kasta_cmd( 61 | pattern="banall(?: |$)(.*)", 62 | admins_only=True, 63 | require="add_admins", 64 | ) 65 | async def _(kst): 66 | ga = kst.client 67 | chat_id = kst.chat_id 68 | opts = kst.pattern_match.group(1).lower() 69 | lucifer = any(_ in opts for _ in ("-s", "silent")) 70 | if lucifer: 71 | await kst.try_delete() 72 | else: 73 | yy = await kst.eor("`GoHell...`") 74 | success, failed = 0, 0 75 | async for x in ga.iter_participants(chat_id): 76 | if not (x.id in DEVS or x.is_self or hasattr(x.participant, "admin_rights")): 77 | try: 78 | crying = await ga.edit_permissions(chat_id, x.id, view_messages=False) 79 | if lucifer and crying: 80 | cry = [_.id for _ in crying.updates if hasattr(_, "id")] 81 | if cry: 82 | await ga.delete_messages(chat_id, cry) 83 | await asyncio.sleep(0.5) 84 | success += 1 85 | except FloodWaitError as fw: 86 | await asyncio.sleep(fw.seconds + 10) 87 | try: 88 | crying = await ga.edit_permissions(chat_id, x.id, view_messages=False) 89 | if lucifer and crying: 90 | cry = [_.id for _ in crying.updates if hasattr(_, "id")] 91 | if cry: 92 | await ga.delete_messages(chat_id, cry) 93 | await asyncio.sleep(0.5) 94 | success += 1 95 | except BaseException: 96 | failed += 1 97 | except BaseException: 98 | failed += 1 99 | if not lucifer: 100 | await yy.eor(f"__You're Lucifer +{success}-{failed}__ 👁️", time=15) 101 | -------------------------------------------------------------------------------- /getter/plugins/translate.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from . import ( 9 | Root, 10 | Var, 11 | kasta_cmd, 12 | plugins_help, 13 | LANG_CODES, 14 | formatx_send, 15 | strip_format, 16 | strip_emoji, 17 | strip_ascii, 18 | sort_dict, 19 | aioify, 20 | import_lib, 21 | ) 22 | 23 | 24 | @kasta_cmd( 25 | pattern=r"tr(?: |$)([\s\S]*)", 26 | edited=True, 27 | ) 28 | async def _(kst): 29 | match = kst.pattern_match.group(1) 30 | args = match.split(" ") 31 | if args[0] in LANG_CODES: 32 | is_lang, lang = True, args[0] 33 | else: 34 | is_lang, lang = False, Var.LANG_CODE 35 | if kst.is_reply: 36 | words = (await kst.get_reply_message()).message 37 | if is_lang: 38 | try: 39 | words = match.split(maxsplit=1)[1] 40 | except BaseException: 41 | pass 42 | else: 43 | words = match 44 | if is_lang: 45 | try: 46 | words = match.split(maxsplit=1)[1] 47 | except BaseException: 48 | pass 49 | if not words: 50 | return await kst.eor("`Reply to text message or provide a text!`", time=5) 51 | yy = await kst.eor("`...`") 52 | try: 53 | from gpytranslate import Translator 54 | except ImportError: 55 | Translator = import_lib( 56 | lib_name="gpytranslate", 57 | pkg_name="gpytranslate==1.5.1", 58 | ).Translator 59 | try: 60 | text = strip_format(strip_emoji(words)) 61 | translator = Translator() 62 | translation = await translator(text, targetlang=lang) 63 | tr = f"**Detected:** `{await translator.detect(translation.orig)}`\n**Translated:** `{await translator.detect(translation.text)}`\n\n```{translation.text}```" 64 | await yy.eor(tr, parts=True) 65 | except Exception as err: 66 | await yy.eor(formatx_send(err), parse_mode="html") 67 | 68 | 69 | @kasta_cmd( 70 | pattern=r"tl(?: |$)([\s\S]*)", 71 | edited=True, 72 | ) 73 | async def _(kst): 74 | match = kst.pattern_match.group(1) 75 | args = match.split(" ") 76 | if args[0] in LANG_CODES: 77 | is_lang, lang = True, args[0] 78 | else: 79 | is_lang, lang = False, Var.LANG_CODE 80 | if kst.is_reply: 81 | words = (await kst.get_reply_message()).message 82 | if is_lang: 83 | try: 84 | words = match.split(maxsplit=1)[1] 85 | except BaseException: 86 | pass 87 | else: 88 | words = match 89 | if is_lang: 90 | try: 91 | words = match.split(maxsplit=1)[1] 92 | except BaseException: 93 | pass 94 | if not words: 95 | return await kst.eor("`Reply to text message or provide a text!`", time=5) 96 | try: 97 | from gpytranslate import Translator 98 | except ImportError: 99 | Translator = import_lib( 100 | lib_name="gpytranslate", 101 | pkg_name="gpytranslate==1.5.1", 102 | ).Translator 103 | try: 104 | text = strip_format(strip_emoji(words)) 105 | translation = await Translator()(text, targetlang=lang) 106 | await kst.sod(translation.text, parts=True) 107 | except Exception as err: 108 | await kst.eor(formatx_send(err), parse_mode="html") 109 | 110 | 111 | @kasta_cmd( 112 | pattern=r"t(t|)s(?: |$)([\s\S]*)", 113 | edited=True, 114 | ) 115 | async def _(kst): 116 | match = kst.pattern_match.group(2) 117 | args = match.split(" ") 118 | if args[0] in LANG_CODES: 119 | is_lang, lang = True, args[0] 120 | else: 121 | is_lang, lang = False, Var.LANG_CODE 122 | if kst.is_reply: 123 | words = (await kst.get_reply_message()).message 124 | if is_lang: 125 | try: 126 | words = match.split(maxsplit=1)[1] 127 | except BaseException: 128 | pass 129 | else: 130 | words = match 131 | if is_lang: 132 | try: 133 | words = match.split(maxsplit=1)[1] 134 | except BaseException: 135 | pass 136 | if not words: 137 | return await kst.eor("`Reply to text message or provide a text!`", time=5) 138 | yy = await kst.eor("`...`") 139 | try: 140 | from gtts import gTTS 141 | except ImportError: 142 | gTTS = import_lib( 143 | lib_name="gtts", 144 | pkg_name="gTTS==2.5.1", 145 | ).gTTS 146 | try: 147 | from gpytranslate import Translator 148 | except ImportError: 149 | Translator = import_lib( 150 | lib_name="gpytranslate", 151 | pkg_name="gpytranslate==1.5.1", 152 | ).Translator 153 | try: 154 | text = strip_ascii(strip_format(strip_emoji(words))) 155 | if kst.pattern_match.group(1).strip() != "t": 156 | text = (await Translator()(text, targetlang=lang)).text 157 | file = Root / "downloads/voice.mp3" 158 | voice = await aioify(gTTS, text, lang=lang, slow=False) 159 | voice.save(file) 160 | await yy.eor( 161 | file=file, 162 | voice_note=True, 163 | ) 164 | (file).unlink(missing_ok=True) 165 | except Exception as err: 166 | await yy.eor(formatx_send(err), parse_mode="html") 167 | 168 | 169 | @kasta_cmd( 170 | pattern="lang$", 171 | ) 172 | async def _(kst): 173 | lang = f"**{len(LANG_CODES)} Language Code:**\n" + "\n".join( 174 | [f"- {y}: {x}" for x, y in sort_dict(LANG_CODES).items()] 175 | ) 176 | await kst.sod(lang, parts=True) 177 | 178 | 179 | plugins_help["translate"] = { 180 | "{i}tr [lang_code] [text]/[reply]": f"Translate the message to required language. Default lang_code for all is `{Var.LANG_CODE}`.", 181 | "{i}tl [lang_code] [text]/[reply]": "Send or reply message as translated.", 182 | "{i}tts [lang_code] [text]/[reply]": "Text to speech.", 183 | "{i}ts [lang_code] [text]/[reply]": "Translate the message then text to speech.", 184 | "{i}lang": """List all language code. 185 | 186 | **Examples:** 187 | - Use default lang_code. 188 | -> `{i}tr ready` 189 | - With choosing lang_code. 190 | -> `{i}tr en siap` 191 | 192 | - Translate the replied message. 193 | -> `{i}tr [reply]` 194 | -> `{i}tr en [reply]` 195 | - Reply a message with translated text (must have lang_code). 196 | -> `{i}tr en siap` 197 | 198 | Examples above both for all commands! 199 | """, 200 | } 201 | -------------------------------------------------------------------------------- /getter/plugins/usage.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import asyncio 9 | from datetime import datetime 10 | from html import escape 11 | from json import dumps 12 | from math import floor 13 | from random import choice 14 | from . import ( 15 | getter_app, 16 | kasta_cmd, 17 | plugins_help, 18 | humanbytes, 19 | to_dict, 20 | formatx_send, 21 | mask_email, 22 | USERAGENTS, 23 | Fetch, 24 | hk, 25 | sendlog, 26 | ) 27 | 28 | dyno_text = """ 29 | 📦 Heroku App 30 | -> Name: {} 31 | -> Stack: {} 32 | -> Region: {} 33 | -> Created: {} 34 | -> Updated: {} 35 | -> Email: {} 36 | 37 | ⚙️ Heroku Dyno 38 | -> Dyno usage: 39 | • {}h {}m {}% 40 | -> Dyno hours quota remaining this month: 41 | • {}h {}m {}% 42 | """ 43 | usage_text = """ 44 | 🖥️ Uptime 45 | App: {} 46 | System: {} 47 | 48 | 📊 Data Usage 49 | Upload: {} 50 | Download: {} 51 | 52 | 💾 Disk Space 53 | Total: {} 54 | Used: {} 55 | Free: {} 56 | 57 | 📈 Memory Usage 58 | CPU: {} 59 | RAM: {} 60 | DISK: {} 61 | SWAP: {} 62 | """ 63 | 64 | 65 | @kasta_cmd( 66 | pattern="usage$", 67 | ) 68 | async def _(kst): 69 | yy = await kst.eor("`Processing...`") 70 | usage = default_usage() + await heroku_usage() if hk.is_heroku else default_usage() 71 | await yy.eor(usage, parse_mode="html") 72 | 73 | 74 | @kasta_cmd( 75 | pattern="heroku$", 76 | ) 77 | async def _(kst): 78 | yy = await kst.eor("`Processing...`") 79 | if not hk.api: 80 | return await yy.eod("Please set `HEROKU_API` in Config Vars.") 81 | if not hk.name: 82 | return await yy.eod("Please set `HEROKU_APP_NAME` in Config Vars.") 83 | try: 84 | conn = hk.heroku() 85 | app = conn.app(hk.name) 86 | except Exception as err: 87 | return await yy.eor(formatx_send(err), parse_mode="html") 88 | account = dumps(to_dict(conn.account()), indent=1, default=str) 89 | capp = dumps(to_dict(app.info), indent=1, default=str) 90 | dyno = dumps(to_dict(app.dynos()), indent=1, default=str) 91 | addons = dumps(to_dict(app.addons()), indent=1, default=str) 92 | buildpacks = dumps(to_dict(app.buildpacks()), indent=1, default=str) 93 | configs = dumps(app.config().to_dict(), indent=1, default=str) 94 | await sendlog(f"Account:\n
{escape(account)}
", fallback=True, parse_mode="html") 95 | await asyncio.sleep(1) 96 | await sendlog(f"App:\n
{escape(capp)}
", fallback=True, parse_mode="html") 97 | await asyncio.sleep(1) 98 | await sendlog(f"Dyno:\n
{escape(dyno)}
", fallback=True, parse_mode="html") 99 | await asyncio.sleep(1) 100 | await sendlog(f"Addons:\n
{escape(addons)}
", fallback=True, parse_mode="html") 101 | await asyncio.sleep(1) 102 | await sendlog(f"Buildpacks:\n
{escape(buildpacks)}
", fallback=True, parse_mode="html") 103 | await asyncio.sleep(1) 104 | await sendlog(f"Configs:\n
{escape(configs)}
", fallback=True, parse_mode="html") 105 | await yy.eor("`Heroku details sent at botlogs.`") 106 | 107 | 108 | def default_usage() -> str: 109 | import psutil 110 | 111 | try: 112 | UPLOAD = humanbytes(psutil.net_io_counters().bytes_sent) 113 | except BaseException: 114 | UPLOAD = 0 115 | try: 116 | DOWN = humanbytes(psutil.net_io_counters().bytes_recv) 117 | except BaseException: 118 | DOWN = 0 119 | try: 120 | workdir = psutil.disk_usage(".") 121 | TOTAL = humanbytes(workdir.total) 122 | USED = humanbytes(workdir.used) 123 | FREE = humanbytes(workdir.free) 124 | except BaseException: 125 | TOTAL = 0 126 | USED = 0 127 | FREE = 0 128 | try: 129 | cpu_freq = psutil.cpu_freq().current 130 | cpu_freq = f"{round(cpu_freq / 1000, 2)}GHz" if cpu_freq >= 1000 else f"{round(cpu_freq, 2)}MHz" 131 | CPU = f"{psutil.cpu_percent()}% ({psutil.cpu_count()}) {cpu_freq}" 132 | except BaseException: 133 | try: 134 | CPU = f"{psutil.cpu_percent()}%" 135 | except BaseException: 136 | CPU = "0%" 137 | try: 138 | RAM = f"{psutil.virtual_memory().percent}%" 139 | except BaseException: 140 | RAM = "0%" 141 | try: 142 | DISK = "{}%".format(psutil.disk_usage("/").percent) 143 | except BaseException: 144 | DISK = "0%" 145 | try: 146 | swap = psutil.swap_memory() 147 | SWAP = f"{humanbytes(swap.total)} | {swap.percent or 0}%" 148 | except BaseException: 149 | SWAP = "0 | 0%" 150 | return usage_text.format( 151 | getter_app.uptime, 152 | datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S"), 153 | UPLOAD, 154 | DOWN, 155 | TOTAL, 156 | USED, 157 | FREE, 158 | CPU, 159 | RAM, 160 | DISK, 161 | SWAP, 162 | ) 163 | 164 | 165 | async def heroku_usage() -> str: 166 | try: 167 | conn = hk.heroku() 168 | user = conn.account().id 169 | app = conn.app(hk.name) 170 | except Exception as err: 171 | return f"ERROR:\n{err}" 172 | headers = { 173 | "User-Agent": choice(USERAGENTS), 174 | "Authorization": f"Bearer {hk.api}", 175 | "Accept": "application/vnd.heroku+json; version=3.account-quotas", 176 | } 177 | url = f"https://api.heroku.com/accounts/{user}/actions/get-quota" 178 | res = await Fetch(url, headers=headers, re_json=True) 179 | if not res: 180 | return "Try again now!" 181 | quota = res["account_quota"] 182 | quota_used = res["quota_used"] 183 | remaining_quota = quota - quota_used 184 | percentage = floor(remaining_quota / quota * 100) 185 | minutes_remaining = remaining_quota / 60 186 | hours = floor(minutes_remaining / 60) 187 | minutes = floor(minutes_remaining % 60) 188 | Apps = res["apps"] 189 | try: 190 | Apps[0]["quota_used"] 191 | except IndexError: 192 | AppQuotaUsed = 0 193 | AppPercentage = 0 194 | else: 195 | AppQuotaUsed = Apps[0]["quota_used"] / 60 196 | AppPercentage = floor(Apps[0]["quota_used"] * 100 / quota) 197 | AppHours = floor(AppQuotaUsed / 60) 198 | AppMinutes = floor(AppQuotaUsed % 60) 199 | return dyno_text.format( 200 | app.name, 201 | app.stack.name, 202 | app.region.name, 203 | app.created_at.strftime("%Y-%m-%d %H:%M:%S"), 204 | app.updated_at.strftime("%Y-%m-%d %H:%M:%S"), 205 | mask_email(app.owner.email), 206 | AppHours, 207 | AppMinutes, 208 | AppPercentage, 209 | hours, 210 | minutes, 211 | percentage, 212 | ) 213 | 214 | 215 | plugins_help["usage"] = { 216 | "{i}usage": "Get overall usage, also heroku stats.", 217 | "{i}heroku": "Get the heroku information (account, app, dyno, addons, buildpacks, configs) and save in botlogs.", 218 | } 219 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | run: 5 | worker: python3 -m getter 6 | -------------------------------------------------------------------------------- /lite-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | getter: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile.lite 6 | image: getter:latest 7 | restart: on-failure 8 | volumes: 9 | - ./:/app 10 | env_file: 11 | - config.env 12 | network_mode: bridge 13 | -------------------------------------------------------------------------------- /local-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | getter: 3 | build: . 4 | image: getter:latest 5 | restart: on-failure 6 | volumes: 7 | - ./:/app 8 | env_file: 9 | - config.env 10 | network_mode: bridge 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.2.0" 3 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | # https://docs.astral.sh/ruff 3 | show-fixes = true 4 | line-length = 120 5 | indent-width = 4 6 | target-version = "py312" 7 | [tool.ruff.format] 8 | quote-style = "double" 9 | indent-style = "space" 10 | skip-magic-trailing-comma = false 11 | [tool.ruff.lint] 12 | preview = true 13 | select = [ 14 | "W", # warning 15 | "F", # pyflakes 16 | "I", # isort 17 | "A", # flake8-builtins 18 | "SIM", # flake8-simplify 19 | "C4", # flake8-comprehensions 20 | "B", # flake8-bugbear 21 | "PIE", # flake8-pie 22 | "RET", # flake8-return 23 | "TID", # flake8-tidy-imports 24 | "CPY", # flake8-copyright 25 | "UP", # pyupgrade 26 | "FURB", # refurb 27 | "PERF", # perflint 28 | "RUF", # ruff-specific rules 29 | "PLR6201", # Pylint: literal-membership 30 | "PLC1901", # Pylint: compare-to-empty-string 31 | ] 32 | # https://docs.astral.sh/ruff/rules/ 33 | ignore=[ 34 | "I001", 35 | "B904", 36 | "RET502", 37 | "RET503", 38 | "RUF001", 39 | "RUF017", 40 | "FURB118", 41 | "RUF052", 42 | "SIM105", 43 | ] 44 | [tool.ruff.lint.isort] 45 | force-single-line = true 46 | combine-as-imports = true 47 | section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] 48 | known-first-party = ["getter"] 49 | 50 | [tool.isort] 51 | # https://github.com/PyCQA/isort 52 | profile = "black" 53 | line_length = 120 54 | multi_line_output = 3 55 | include_trailing_comma = true 56 | force_grid_wrap = 4 57 | use_parentheses = true 58 | ensure_newline_before_comments = true 59 | lines_between_sections = 0 60 | no_inline_sort = true 61 | combine_as_imports = true 62 | default_section = "THIRDPARTY" 63 | sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] 64 | known_first_party = "getter" 65 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | ruff==0.11.13 2 | black==25.1.0 3 | isort==6.0.1 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | loguru==0.7.3 2 | python-dotenv==1.1.0 3 | https://github.com/kastaid/Telethon/archive/main.tar.gz 4 | cryptg==0.5.0 5 | psutil==7.0.0 6 | heroku3==5.2.1 7 | GitPython==3.1.44 8 | SQLAlchemy==2.0.41 9 | asyncpg==0.30.0 10 | aiosqlite==0.21.0 11 | sqlalchemy-json==0.7.0 12 | cachetools==5.5.2 13 | asyncache==0.3.1 14 | aiofiles==24.1.0 15 | aiocsv==1.3.2 16 | aiohttp[speedups]==3.12.11 17 | httpx==0.28.1 18 | Markdown==3.8 19 | beautifulsoup4==4.13.4 20 | emoji==2.14.1 21 | Unidecode==1.4.0 22 | validators==0.35.0 23 | telegraph==2.2.0 24 | requests==2.32.3 25 | lottie==0.7.2 26 | Pillow==11.1.0 27 | CairoSVG==2.8.2 28 | uvloop==0.21.0 29 | orjson==3.10.18 30 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import argparse 9 | import shlex 10 | import sys 11 | from pathlib import Path 12 | from subprocess import run 13 | from version import __version__ 14 | 15 | RST = "\x1b[0m" 16 | BOLD = "\x1b[1m" 17 | GREEN = "\x1b[32m" 18 | YELLOW = "\x1b[33m" 19 | BLUE = "\x1b[34m" 20 | CYAN = "\x1b[36m" 21 | 22 | python = "python3" 23 | nocache = f"{python} -B" 24 | app = f"{python} -m getter" 25 | app_watch = f"{python} -m scripts.autoreload {app}" 26 | ruff_check = "ruff check ." 27 | ruff_format = "ruff format ." 28 | isort = "isort --settings-file=pyproject.toml ." 29 | prettyjson = f"{nocache} -m scripts.prettyjson" 30 | 31 | 32 | def run_cmd(cmd: str) -> None: 33 | try: 34 | proc = run(shlex.split(cmd), shell=False) 35 | if proc.returncode != 0: 36 | print(f"Exit code {proc.returncode}") 37 | sys.exit(1) 38 | except BaseException: 39 | sys.exit(1) 40 | 41 | 42 | def clean() -> None: 43 | for _ in Path(".").rglob("*.py[co]"): 44 | _.unlink(missing_ok=True) 45 | for _ in Path(".").rglob("__pycache__"): 46 | _.rmdir() 47 | 48 | 49 | def lint() -> None: 50 | print(f"{CYAN}>> {prettyjson}{RST}") 51 | run_cmd(prettyjson) 52 | print(f"{CYAN}>> {isort}{RST}") 53 | run_cmd(isort) 54 | print(f"{CYAN}>> {ruff_check}{RST}") 55 | run_cmd(ruff_check) 56 | print(f"{CYAN}>> {ruff_format}{RST}") 57 | run_cmd(ruff_format) 58 | 59 | 60 | class CapitalisedHelpFormatter(argparse.HelpFormatter): 61 | def add_usage(self, usage, actions, groups, prefix=None): 62 | if not prefix: 63 | prefix = "Usage: " 64 | return super().add_usage(usage, actions, groups, prefix) 65 | 66 | 67 | parser = argparse.ArgumentParser( 68 | formatter_class=CapitalisedHelpFormatter, 69 | prog=f"{GREEN}{python} -m run{RST}", 70 | usage="%(prog)s [options]", 71 | epilog="Source code https://github.com/kastaid/getter", 72 | add_help=False, 73 | ) 74 | parser._optionals.title = "Options" 75 | parser.add_argument( 76 | "-p", 77 | "--prod", 78 | help="run in production mode", 79 | action="store_true", 80 | ) 81 | parser.add_argument( 82 | "-d", 83 | "--dev", 84 | help="run in development mode", 85 | action="store_true", 86 | ) 87 | parser.add_argument( 88 | "-w", 89 | "--watch", 90 | help="run and watch in development mode", 91 | action="store_true", 92 | ) 93 | parser.add_argument( 94 | "-l", 95 | "--lint", 96 | help="run linting and format code", 97 | action="store_true", 98 | ) 99 | parser.add_argument( 100 | "-c", 101 | "--clean", 102 | help="remove python caches", 103 | action="store_true", 104 | ) 105 | parser.add_argument( 106 | "-v", 107 | "--version", 108 | help="show this program version", 109 | action="version", 110 | version=__version__, 111 | ) 112 | parser.add_argument( 113 | "-h", 114 | "--help", 115 | help="show this help information", 116 | default=argparse.SUPPRESS, 117 | action="help", 118 | ) 119 | 120 | 121 | def main() -> None: 122 | args = parser.parse_args() 123 | if args.prod: 124 | print(f"{BOLD}{GREEN}PRODUCTION MODE...{RST}") 125 | clean() 126 | print(f"{BOLD}{BLUE}>> {app}{RST}") 127 | run_cmd(app) 128 | elif args.dev: 129 | print(f"{BOLD}{GREEN}DEVELOPMENT MODE...{RST}") 130 | clean() 131 | lint() 132 | print(f"{BOLD}{BLUE}>> {app}{RST}") 133 | run_cmd(app) 134 | elif args.watch: 135 | print(f"{BOLD}{GREEN}WATCHED DEVELOPMENT MODE...{RST}") 136 | clean() 137 | print(f"{BOLD}{BLUE}>> {app_watch}{RST}") 138 | run_cmd(app_watch) 139 | elif args.lint: 140 | print(f"{BOLD}{YELLOW}Run linting and format code...{RST}") 141 | clean() 142 | lint() 143 | elif args.clean: 144 | clean() 145 | else: 146 | print(f"{python} -m run --help") 147 | 148 | 149 | if __name__ == "__main__": 150 | raise SystemExit(main()) 151 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.12.2 -------------------------------------------------------------------------------- /sample_config.env: -------------------------------------------------------------------------------- 1 | # Do not run some logic if running in development 2 | # Set to False if production 3 | # Set to True if development 4 | DEV_MODE = False 5 | 6 | # Get from my.telegram.org at API development tools 7 | API_ID = 8 | API_HASH = 9 | 10 | # Telethon Session from t.me/strgen_bot 11 | # or replit.com/@notudope/strgen 12 | # or run locally python3 strgen.py 13 | STRING_SESSION = 14 | 15 | # Use postgresql or sqlite+aiosqlite 16 | DATABASE_URL = sqlite+aiosqlite:///./getter.db 17 | 18 | # If you already have the BOTLOGS group then use this! 19 | # or skip this to use autopilot instantly. 20 | #BOTLOGS = -100xxx 21 | 22 | # Initial command handler (prefix), default [ . ] 23 | # Supported characters https://github.com/kastaid/getter/blob/main/getter/config.py 24 | # HANDLER should be like / . ! + - _ ; ~ ^ % & a b c z 25 | # You have to choose one, remember to only choose one! 26 | HANDLER = . 27 | 28 | # Initial command without handler (prefix), default False 29 | NO_HANDLER = False 30 | 31 | # Set local timezone: Continent/Country 32 | # Default: Asia/Jakarta 33 | # Get list from gist.github.com/notudope/9c3b8a5389293d9fe34c6c1f2484eeb3#file-timezones-txt 34 | TZ = Asia/Jakarta 35 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from pathlib import Path 9 | 10 | Root: Path = Path(__file__).parent.parent 11 | 12 | EXTS = (".py", ".yml", ".env") 13 | 14 | WAIT_FOR = 1 15 | 16 | RST = "\x1b[0m" 17 | BOLD = "\x1b[1m" 18 | RED = "\x1b[31m" 19 | YELLOW = "\x1b[33m" 20 | -------------------------------------------------------------------------------- /scripts/autoreload.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | import signal 9 | import sys 10 | from os import getpid, kill 11 | from subprocess import CalledProcessError, Popen, check_call 12 | from time import sleep 13 | from . import ( 14 | BOLD, 15 | EXTS, 16 | RED, 17 | RST, 18 | WAIT_FOR, 19 | YELLOW, 20 | Root, 21 | ) 22 | 23 | try: 24 | import psutil 25 | except ModuleNotFoundError: 26 | print("Installing psutil...") 27 | check_call([sys.executable, "-m", "pip", "install", "-U", "psutil"]) 28 | finally: 29 | import psutil 30 | 31 | 32 | def file_time() -> float: 33 | return max(f.stat().st_mtime for f in Root.rglob("*") if f.suffix in EXTS) 34 | 35 | 36 | def print_stdout(procs) -> None: 37 | out = procs.stdout 38 | if out: 39 | print(out) 40 | 41 | 42 | def kill_process_tree(procs) -> None: 43 | try: 44 | parent = psutil.Process(procs.pid) 45 | child = parent.children(recursive=True) 46 | child.append(parent) 47 | for c in child: 48 | c.send_signal(signal.SIGTERM) 49 | except psutil.NoSuchProcess: 50 | pass 51 | procs.terminate() 52 | 53 | 54 | def main() -> None: 55 | if len(sys.argv) <= 1: 56 | print("python3 -m scripts.autoreload [command]") 57 | sys.exit(0) 58 | cmd = " ".join(sys.argv[1:]) 59 | procs = Popen(cmd, shell=True) 60 | last_mtime = file_time() 61 | try: 62 | while True: 63 | max_mtime = file_time() 64 | print_stdout(procs) 65 | if max_mtime > last_mtime: 66 | last_mtime = max_mtime 67 | print(f"{BOLD}{YELLOW}Restarting >> {procs.args}{RST}") 68 | kill_process_tree(procs) 69 | procs = Popen(cmd, shell=True) 70 | sleep(WAIT_FOR) 71 | except CalledProcessError as err: 72 | kill_process_tree(procs) 73 | sys.exit(err.returncode) 74 | except KeyboardInterrupt: 75 | print(f"{BOLD}{RED}Kill process [{procs.pid}]{RST}") 76 | kill_process_tree(procs) 77 | signal.signal(signal.SIGINT, signal.SIG_DFL) 78 | kill(getpid(), signal.SIGINT) 79 | except BaseException: 80 | print(f"{BOLD}{RED}Watch interrupted.{RST}") 81 | 82 | 83 | if __name__ == "__main__": 84 | raise SystemExit(main()) 85 | -------------------------------------------------------------------------------- /scripts/prettyjson.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | from json import dump, load 9 | from . import Root 10 | 11 | EXCLUDE = (".mypy_cache", "db") 12 | 13 | 14 | def main() -> None: 15 | for p in filter(lambda x: not str(x.parent).endswith(EXCLUDE), Root.rglob("*.json")): 16 | try: 17 | with open(p, encoding="utf-8") as f: 18 | data = load(f) 19 | with open(p, mode="w", encoding="utf-8") as f: 20 | dump(data, f, indent=4, sort_keys=False, ensure_ascii=False) 21 | print("Pretty print: ", p.name) 22 | except Exception as err: 23 | print("Failed to pretty print: ", str(err)) 24 | 25 | 26 | if __name__ == "__main__": 27 | raise SystemExit(main()) 28 | -------------------------------------------------------------------------------- /strgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # getter < https://t.me/kastaid > 3 | # Copyright (C) 2022-present kastaid 4 | # 5 | # This file is a part of < https://github.com/kastaid/getter/ > 6 | # Please read the GNU Affero General Public License in 7 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 8 | 9 | import asyncio 10 | import sys 11 | from subprocess import check_call 12 | 13 | try: 14 | import telethon as tl 15 | except ModuleNotFoundError: 16 | print("Installing Telethon...") 17 | # python3 -m pip install https://github.com/kastaid/Telethon/archive/main.tar.gz 18 | check_call( 19 | [ 20 | sys.executable, 21 | "-m", 22 | "pip", 23 | "install", 24 | "https://github.com/kastaid/Telethon/archive/main.zip", 25 | ] 26 | ) 27 | finally: 28 | import telethon as tl 29 | 30 | 31 | usage = """ 32 | Please go-to "my.telegram.org/apps" (to get API_ID and API_HASH): 33 | ~ Login using your Telegram account. 34 | ~ Click on API development tools. 35 | ~ Create a new application, by entering the required details. 36 | 37 | API_ID is "App api_id:" 38 | API_HASH is "App api_hash:" 39 | 40 | Or use: 41 | - @apiscrapperbot 42 | - @UseTGXBot 43 | ... 44 | """ 45 | 46 | template = """ 47 | **This is your {} based UserBots** `STRING_SESSION` 48 | ⚠️ **DO NOT SHARE WITH ANYONE** ⚠️ 49 | 50 | ```{}``` 51 | 52 | Generated by KASTA <3 @kastaid 53 | """ 54 | 55 | generated = """ 56 | Generated !! Check your Telegram "Saved Messages" to copy STRING_SESSION or copy from above. 57 | 58 | ~ Follow our channel https://t.me/kastaid 59 | """ 60 | 61 | print(usage) 62 | 63 | try: 64 | API_ID = int(input("Enter your API_ID here: ")) 65 | except ValueError: 66 | print(">>> API_ID must be an integer.\nQuitting...") 67 | sys.exit(0) 68 | API_HASH = input("Enter your API_HASH here: ") 69 | 70 | client = tl.TelegramClient( 71 | tl.sessions.StringSession(), 72 | api_id=API_ID, 73 | api_hash=API_HASH, 74 | ) 75 | 76 | 77 | async def main() -> None: 78 | try: 79 | await asyncio.sleep(1) 80 | print("Generating Telethon STRING_SESSION...") 81 | string_session = client.session.save() 82 | saved_messages = template.format("Telethon", string_session) 83 | print("\n" + string_session + "\n") 84 | await client.send_message("me", saved_messages) 85 | await asyncio.sleep(1) 86 | print(generated) 87 | sys.exit(0) 88 | except tl.errors.ApiIdInvalidError: 89 | print(">>> Your API_ID or API_HASH combination is invalid. Kindly recheck.\nQuitting...") 90 | sys.exit(0) 91 | except ValueError: 92 | print(">>> API_HASH must not be empty!\nQuitting...") 93 | sys.exit(0) 94 | except tl.errors.PhoneNumberInvalidError: 95 | print(">>> The phone number is invalid!\nQuitting...") 96 | sys.exit(0) 97 | 98 | 99 | with client: 100 | client.loop.run_until_complete(main()) 101 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | # getter < https://t.me/kastaid > 2 | # Copyright (C) 2022-present kastaid 3 | # 4 | # This file is a part of < https://github.com/kastaid/getter/ > 5 | # Please read the GNU Affero General Public License in 6 | # < https://github.com/kastaid/getter/blob/main/LICENSE/ >. 7 | 8 | 9 | def get_version() -> str: 10 | import json 11 | 12 | with open("manifest.json") as f: 13 | data = json.load(f) 14 | return data.get("version", "unknown") 15 | 16 | 17 | __version__ = get_version() 18 | --------------------------------------------------------------------------------