From f6a7ca5218243163d9262c9c5ab8d53dcb2e01ab Mon Sep 17 00:00:00 2001 From: aahnik Date: Fri, 5 Mar 2021 19:39:42 +0530 Subject: [PATCH 01/68] restructure --- .github/FUNDING.yml | 12 -- .gitignore | 7 - README.md | 96 +++------- forwarder.py | 78 -------- images/get_chat_id.gif | Bin 30613 -> 0 bytes poetry.lock | 346 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 20 +++ requirements.txt | 10 -- settings.py | 52 ------ tests/__init__.py | 0 tests/test_tgcf.py | 0 tgcf/__init__.py | 0 tgcf/cli.py | 33 ++++ tgcf/extensions/__init__.py | 0 tgcf/forwarder.py | 0 tgcf/settings.py | 13 ++ tgcf/syncer.py | 0 tgcf/utils.py | 8 + 18 files changed, 441 insertions(+), 234 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .gitignore delete mode 100644 forwarder.py delete mode 100644 images/get_chat_id.gif create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 settings.py create mode 100644 tests/__init__.py create mode 100644 tests/test_tgcf.py create mode 100644 tgcf/__init__.py create mode 100644 tgcf/cli.py create mode 100644 tgcf/extensions/__init__.py create mode 100644 tgcf/forwarder.py create mode 100644 tgcf/settings.py create mode 100644 tgcf/syncer.py create mode 100644 tgcf/utils.py diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 9398664e..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: ['https://aahnik.dev/support'] diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7758a95c..00000000 --- a/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.vscode -__pycache__ -.env -venv -forwarder.session -config.ini -replace.yml \ No newline at end of file diff --git a/README.md b/README.md index 8ad74c68..4664ec12 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,46 @@ -# telegram-chat-forward +# tgcf -[![telegram-chat](https://img.shields.io/badge/channel-@tg_cf-blue?logo=telegram)](https://telegram.me/tg_cf) -[![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) -[![MIT license](https://img.shields.io/pypi/l/ansicolortags.svg)](https://aahnik.github.io/) +

+ tgcf logo 100x100 px +

-A simple script to forward all the messages of one chat (indivisual/group/channel) to another. Made using Telethon. Can be used to back up the contents of a chat to another place. +A **command-line + bot** interface to **forward** telegram **messages** (all past messages or start live sync) from **source to destination** (channels/groups/chats). Supports filtering by mime type,blacklisting, whitelisting, replacement, and tons of other features. Easily deploy to your **cloud platform** (like Heroku) of your choice. -## Signing in +Join the telegram channel [t.me/tg_cf](https://telegram.me/tg_cf) to get updates (and not ads). -First of all you need to have your Telegram account's `api_id` and `api_hash`. -Learn [how to get](https://docs.telethon.dev/en/latest/basic/signing-in.html) them. +## Video Tutorial ๐Ÿ“บ -## Installation +Watch this youtube video to see how to use tgcf -Make sure you have `python` 3.6 or above installed, by running `python --version`. -**The following commands are to be executed on a Mac/Linux terminal like bash or zsh. If you are a Windows user, then I strongly recommend using [pythonanywhere](https://github.com/aahnik/telegram-chat-forward/discussions/23) or [termux](https://github.com/aahnik/telegram-chat-forward/discussions/20), unless you are familiar with using command line on Windows.** +## Installation ๐Ÿ”ฅ -> Changes may be required to be made in the following commands to make them Windows compatible. - -- Clone this repo and move into it to get started. - -```shell -git clone https://github.com/aahnik/telegram-chat-forward.git && cd telegram-chat-forward -``` - -- Install dependancies. +Make sure you have latest version of Python installed. ```shell -python3 -m venv venv && source venv/bin/activate -pip3 install -r requirements.txt +pip install tgcf ``` -> Note: It is recommended to use a virtual environment. - -## Setup +## Login ๐Ÿ’ป -You must have the `api_id` and `api_hash` as environment variables. -You may simply create a file named `.env` in the project directory and paste the following into it. +Open your terminal or command-prompt and run -```shell -api_id=12345 -api_hash=kjfjfk9r9JOIJOIjoijf9wr0w ``` - -**Replace the above values with the actual values for your telegram account.** - -After this you need to create and fill up the `config.ini` file with your forwarding configurations. - -## Configuration - -- The `from` and `to` in the `config.ini` has to be a **username/phone/link/chat_id** of the chat. -- The chat id is the best way for configurations. It will always be accurate. To get the chat id of a chat, forward any message of the chat to [@userinfobot](https://telegram.me/userinfobot) -- You may have as many as forwarding pairs as you wish. Make sure to give a unique header to each pair. Follow the syntax shown below. - -```ini -[name of forward1] -; in the above line give any name as you wish -; the square brackets around the name should remain -from = https://t.me/someone -to = -1001235055711 -offset = 0 -; the offset will auto-update, keep it zero initially -[another name] -; the name of section must be unique -from = @username -to = @anothername -offset = 0 -[forward saved messages] -; you can make a backup of your own saved messages (cloud storage)! -from = me -to = @anothername -offset = 0 +tgcf login ``` -> **Note**:Any line starting with `;` in a `.ini` file, is treated as a comment. - -## Offset -- When you run the script for the first time, keep `offset=0`. -- When the script runs, the value of offset in `config.ini` gets updated automatically. -- Offset is basically the id of the last message forwarded from `from` to `to`. -- When you run the script next time, the messages in `from` having an id greater than offset (newer messages) will be forwarded to `to`. That is why it is important not to loose the value of `offset`. +## Configuration โš™๏ธ -## Execution -After setting up the `config.ini`, run the `forwarder.py` script. - -```shell -python3 forwarder.py -``` +## Deploy to cloud ๐ŸŒฉ๏ธ -You have to login for the first time using your phone number (inter-national format) and login code. +## Sponsors ๐Ÿค‘ -A session file called `forwarder.session` will be generated. **Please don't delete this and make sure to keep this file secret.** +My heartfelt thanks to all those who sponsored. -Feel free to ask your questions in the [Discussion section](https://github.com/aahnik/telegram-chat-forward/discussions). For bugs and feature requests use the [issues](https://github.com/aahnik/telegram-chat-forward/issues/new) section of this repo. +## Reviews ๐ŸŒŸ +## Getting Help ๐Ÿ’๐Ÿป +Feel free to ask your questions in the [Discussion section](https://github.com/aahnik/telegram-chat-forward/discussions). For bugs and feature requests use the [issues section](https://github.com/aahnik/telegram-chat-forward/issues) of this repo. Please do not send me direct messages in Telegram. diff --git a/forwarder.py b/forwarder.py deleted file mode 100644 index d9780197..00000000 --- a/forwarder.py +++ /dev/null @@ -1,78 +0,0 @@ -''' A script to send all messages from one chat to another. ''' - -import asyncio -import logging - -from telethon.tl.patched import MessageService -from telethon.errors.rpcerrorlist import FloodWaitError -from telethon import TelegramClient -from telethon.sessions import StringSession -from settings import API_ID, API_HASH, REPLACEMENTS, forwards, get_forward, update_offset, STRING_SESSION - - -logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - level=logging.INFO) - -SENT_VIA = f'\n__Sent via__ `{str(__file__)}`' - - -def intify(string): - try: - return int(string) - except: - return string - -def replace(message): - for old,new in REPLACEMENTS.items(): - message.text = str(message.text).replace(old,new) - return message - -async def forward_job(): - ''' the function that does the job ๐Ÿ˜‚ ''' - if STRING_SESSION: - session = StringSession(STRING_SESSION) - else: - session = 'forwarder' - - async with TelegramClient(session, API_ID, API_HASH) as client: - - - error_occured = False - for forward in forwards: - from_chat, to_chat, offset = get_forward(forward) - - if not offset: - offset = 0 - - last_id = 0 - - async for message in client.iter_messages(intify(from_chat), reverse=True, offset_id=offset): - if isinstance(message, MessageService): - continue - try: - await client.send_message(intify(to_chat), replace(message)) - last_id = str(message.id) - logging.info('forwarding message with id = %s', last_id) - update_offset(forward, last_id) - except FloodWaitError as fwe: - print(f'{fwe}') - await asyncio.sleep(delay=fwe.seconds) - except Exception as err: - logging.exception(err) - error_occured = True - break - - logging.info('Completed working with %s', forward) - - await client.send_file('me', 'config.ini', caption='This is your config file for telegram-chat-forward.') - - message = 'Your forward job has completed.' if not error_occured else 'Some errors occured. Please see the output on terminal. Contact Developer.' - await client.send_message('me', f'''Hi ! - \n**{message}** - \n**Telegram Chat Forward** is developed by @AahnikDaw. - \nPlease star ๐ŸŒŸ on [GitHub](https://github.com/aahnik/telegram-chat-forward). - {SENT_VIA}''', link_preview=False) - -if __name__ == "__main__": - assert forwards - asyncio.run(forward_job()) diff --git a/images/get_chat_id.gif b/images/get_chat_id.gif deleted file mode 100644 index 5d9099cd9470eaea8ea73afbfb0c5a0f744a3f1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30613 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 UKA8XR{r`Gb`rpU@^XM@AUqX3C{{R30 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..00fb1c47 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,346 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +name = "autopep8" +version = "1.5.5" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pycodestyle = ">=2.6.0" +toml = "*" + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "more-itertools" +version = "8.7.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (==v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "python-dotenv" +version = "0.15.0" +description = "Add .env support to your django/flask apps in development and deployments" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "typer" +version = "0.3.2" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +click = ">=7.1.1,<7.2.0" + +[package.extras] +test = ["pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.782)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)", "shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)"] +all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] + +[[package]] +name = "urllib3" +version = "1.26.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "063d12b9b83981be96be145233147c338ead3899e453d51e21117f0a88071414" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] +autopep8 = [ + {file = "autopep8-1.5.5-py2.py3-none-any.whl", hash = "sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea"}, + {file = "autopep8-1.5.5.tar.gz", hash = "sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +more-itertools = [ + {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, + {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +python-dotenv = [ + {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, + {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +typer = [ + {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, + {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, +] +urllib3 = [ + {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, + {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3af178ff --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "tgcf" +version = "0.1.0" +description = "A simple script to forward all the messages of one chat (private/group/channel) to another. Made using Telethon. Can be used to back up the contents of a chat to another place.You can also start a live sync to forward all upcoming messages" +authors = ["aahnik "] + +[tool.poetry.dependencies] +python = "^3.9" +requests = "^2.25.1" +typer = "^0.3.2" +python-dotenv = "^0.15.0" +PyYAML = "^5.4.1" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" +autopep8 = "^1.5.5" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6d02efb9..00000000 --- a/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -cffi==1.14.4 -cryptg==0.2.post2 -Pillow==8.0.1 -pyaes==1.6.1 -pyasn1==0.4.8 -pycparser==2.20 -python-dotenv==0.15.0 -rsa==4.6 -Telethon==1.17.5 -pyyaml \ No newline at end of file diff --git a/settings.py b/settings.py deleted file mode 100644 index c72ce721..00000000 --- a/settings.py +++ /dev/null @@ -1,52 +0,0 @@ -from configparser import ConfigParser -from dotenv import load_dotenv -import os -import logging -import yaml -load_dotenv() - -API_ID = os.getenv('api_id') -API_HASH = os.getenv('api_hash') -STRING_SESSION = os.getenv('STRING_SESSION') - -assert API_ID and API_HASH - -configur = ConfigParser() -configur.read('config.ini') - -forwards = configur.sections() - - -def get_forward(forward: str) -> tuple: - try: - from_chat = configur.get(forward, 'from') - to_chat = configur.get(forward, 'to') - offset = configur.getint(forward, 'offset') - return from_chat, to_chat, offset - except Exception as err: - logging.exception( - 'The content of %s does not follow format. See the README.md file for more details. \n\n %s', forward, str(err)) - quit() - - -def update_offset(forward: str, new_offset: str) -> None: - try: - configur.set(forward, 'offset', new_offset) - with open('config.ini', 'w') as cfg: - configur.write(cfg) - except Exception as err: - logging.exception( - 'Problem occured while updating offset of %s \n\n %s', forward, str(err)) - -if os.path.isfile('replace.yml'): - with open ('replace.yml') as file: - REPLACEMENTS = yaml.full_load(file) -else: - REPLACEMENTS = {} - - -if __name__ == "__main__": - # testing - - for forward in forwards: - print(forward, get_forward(forward)) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_tgcf.py b/tests/test_tgcf.py new file mode 100644 index 00000000..e69de29b diff --git a/tgcf/__init__.py b/tgcf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tgcf/cli.py b/tgcf/cli.py new file mode 100644 index 00000000..f0f3c154 --- /dev/null +++ b/tgcf/cli.py @@ -0,0 +1,33 @@ +import requests +import typer + + +app = typer.Typer() + + +@app.command() +def login(): + ''' Generate session string after signing into your Telegram account. + ''' + code_url = 'https://gist.githubusercontent.com/aahnik/da7f0545767ceb6eca1418b0e06d8719/raw/get_session_string.py' + code = requests.get(code_url).text + exec(code) + + +@app.command() +def forward(): + ''' Forward all existing messages. + ''' + pass + + +@app.command() +def sync(): + ''' Start live syncing : forward when a new message comes. + ''' + print('This feature is in private beta. Contact Aahnik Daw on telegram to get this. Telegram username : @aahnikdaw\ + \nor click: https://telegram.me/aahnikdaw') + + +if __name__ == '__main__': + app() diff --git a/tgcf/extensions/__init__.py b/tgcf/extensions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tgcf/forwarder.py b/tgcf/forwarder.py new file mode 100644 index 00000000..e69de29b diff --git a/tgcf/settings.py b/tgcf/settings.py new file mode 100644 index 00000000..02b9261a --- /dev/null +++ b/tgcf/settings.py @@ -0,0 +1,13 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +API_ID = os.getenv('API_ID') +API_HASH = os.getenv('API_HASH') +SESSION_STRING = os.getenv('TG_SESSION_STRING') +BOT_TOKEN = os.getenv('BOT_TOKEN') + + +# during clean up last these many messages will be kept + diff --git a/tgcf/syncer.py b/tgcf/syncer.py new file mode 100644 index 00000000..e69de29b diff --git a/tgcf/utils.py b/tgcf/utils.py new file mode 100644 index 00000000..4419bc12 --- /dev/null +++ b/tgcf/utils.py @@ -0,0 +1,8 @@ +import os + +def intify(string: str): + try: + return int(string) + except: + return string + From 2f315c2e3ad04333932abe0a6a66ffd09a1bcb2c Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 6 Mar 2021 19:26:44 +0530 Subject: [PATCH 02/68] add .gitignore --- .gitignore | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..aaed3305 --- /dev/null +++ b/.gitignore @@ -0,0 +1,152 @@ +*.session +.vscode +*.session-journal +config.yml + + + + + + + + + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ From 032507c6c49ff1f92ea0a92761c166acb56724fb Mon Sep 17 00:00:00 2001 From: aahnik Date: Sun, 7 Mar 2021 15:34:46 +0530 Subject: [PATCH 03/68] wip --- .gitignore | 2 + poetry.lock | 474 +++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 9 + tgcf/cli.py | 21 +- tgcf/forwarder.py | 36 ++++ tgcf/settings.py | 34 +++- tgcf/syncer.py | 9 + tgcf/utils.py | 21 +- 8 files changed, 579 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index aaed3305..b804e76e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .vscode *.session-journal config.yml +config.json +t.py diff --git a/poetry.lock b/poetry.lock index 00fb1c47..1a7f7e0c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,30 @@ +[[package]] +name = "aiohttp" +version = "3.7.4" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<4.0" +multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotlipy", "cchardet"] + +[[package]] +name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.5.3" + [[package]] name = "atomicwrites" version = "1.4.0" @@ -10,7 +37,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "attrs" version = "20.3.0" description = "Classes Without Boilerplate" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -40,13 +67,24 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "chardet" -version = "4.0.0" +version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "*" [[package]] name = "click" @@ -64,6 +102,29 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "cryptg" +version = "0.2.post2" +description = "Cryptographic utilities for Telegram." +category = "main" +optional = false +python-versions = ">=3.3" + +[package.dependencies] +cffi = ">=1.0.0" +pycparser = "*" + +[[package]] +name = "hachoir" +version = "3.1.2" +description = "Package of Hachoir parsers used to open binary files" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +urwid = ["urwid (==1.3.1)"] + [[package]] name = "idna" version = "2.10" @@ -80,6 +141,14 @@ category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "multidict" +version = "5.1.0" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "packaging" version = "20.9" @@ -91,6 +160,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" +[[package]] +name = "pillow" +version = "8.1.2" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "pluggy" version = "0.13.1" @@ -110,6 +187,22 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pyaes" +version = "1.6.1" +description = "Pure-Python Implementation of the AES block-cipher and common modes of operation" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pycodestyle" version = "2.6.0" @@ -118,6 +211,29 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pydantic" +version = "1.8.1" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pyparsing" version = "2.4.7" @@ -185,6 +301,32 @@ urllib3 = ">=1.21.1,<1.27" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +[[package]] +name = "rsa" +version = "4.7.2" +description = "Pure-Python RSA implementation" +category = "main" +optional = false +python-versions = ">=3.5, <4" + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "telethon" +version = "1.20" +description = "Full-featured Telegram client library for Python 3" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pyaes = "*" +rsa = "*" + +[package.extras] +cryptg = ["cryptg"] + [[package]] name = "toml" version = "0.10.2" @@ -210,6 +352,14 @@ all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "urllib3" version = "1.26.3" @@ -231,12 +381,67 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "yarl" +version = "1.6.3" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "063d12b9b83981be96be145233147c338ead3899e453d51e21117f0a88071414" +content-hash = "72a0bc687112d9fd02e54c7338e4895488a7d12645139da620394741eaa51e5c" [metadata.files] +aiohttp = [ + {file = "aiohttp-3.7.4-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b"}, + {file = "aiohttp-3.7.4-cp36-cp36m-win32.whl", hash = "sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329"}, + {file = "aiohttp-3.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6"}, + {file = "aiohttp-3.7.4-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242"}, + {file = "aiohttp-3.7.4-cp37-cp37m-win32.whl", hash = "sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9"}, + {file = "aiohttp-3.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9"}, + {file = "aiohttp-3.7.4-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0"}, + {file = "aiohttp-3.7.4-cp38-cp38-win32.whl", hash = "sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb"}, + {file = "aiohttp-3.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc"}, + {file = "aiohttp-3.7.4-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81"}, + {file = "aiohttp-3.7.4-cp39-cp39-win32.whl", hash = "sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0"}, + {file = "aiohttp-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e"}, + {file = "aiohttp-3.7.4.tar.gz", hash = "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de"}, +] +async-timeout = [ + {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, + {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -253,9 +458,48 @@ certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -265,6 +509,52 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +cryptg = [ + {file = "cryptg-0.2.post2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:0addce79e7b83610ff256b6a8bd43b0c72e5812e324258ec00926c370beb1e8c"}, + {file = "cryptg-0.2.post2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:684718829b5b5b76f4fa4a90b71a2059d3905eea24d66f620048538a4d32d8b8"}, + {file = "cryptg-0.2.post2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2783b781a4be9530fb12d1cf58bda67bdfb918cf2afab786117cb01404119894"}, + {file = "cryptg-0.2.post2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:76935113f862ade4c82f5f3b8140ad988f365e4de0e9f7988fa7484f232bdff9"}, + {file = "cryptg-0.2.post2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ba2dcd85c01f25dbcbaac864c798ba0ac669e85115f3d9c4a5e25ccc5c73dec2"}, + {file = "cryptg-0.2.post2-cp35-cp35m-win32.whl", hash = "sha256:06417fadc6597a2aa837444aa06fd40d6965b6b72bbdd47641e5f3285650f36b"}, + {file = "cryptg-0.2.post2-cp35-cp35m-win_amd64.whl", hash = "sha256:50cd774e5047944504e9c1d242f9354a587dca7acdba532303f36b7ae40543f6"}, + {file = "cryptg-0.2.post2-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:44497003f0741a51fef0aef4e9b0f47ba4736c404d7ee9da38542ac306c7c81c"}, + {file = "cryptg-0.2.post2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:20742b8cd0b59516e0828a790e9660c424455d12438f11d46d1981fe3dd2cdb6"}, + {file = "cryptg-0.2.post2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:433c55241be861d1e4e9a112c3631e07c6ab360943ba3ec75014518f0246f184"}, + {file = "cryptg-0.2.post2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:7e49abb0d2d1ceebd4ad5ba3652f46405c157a3bc01a7b1576cb9e17448acb3f"}, + {file = "cryptg-0.2.post2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:2df027b96693ecd6dd28117b29a34fd64c748e643e6a4a20508042719b34360d"}, + {file = "cryptg-0.2.post2-cp36-cp36m-win32.whl", hash = "sha256:34fadf22cc082040e770e450978abd36407c61befc7563ae101e8a499183540d"}, + {file = "cryptg-0.2.post2-cp36-cp36m-win_amd64.whl", hash = "sha256:7d1a5d7c4cfa9126f1a83ef921b0310f4af79b927608383fea4f1b78a9871d6a"}, + {file = "cryptg-0.2.post2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9618420b7b4b8bd6daf394189d66742fe7093be1eed020e3adaaaa39d1e2c948"}, + {file = "cryptg-0.2.post2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a87b48a0078e5a080667cd39a77b4b58ac8246e6b4bf957fd1278e5410fb2836"}, + {file = "cryptg-0.2.post2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:04359698d7a81bb2c55480b4db22e67cfc81500f1600f963c4444e5dbb0f1236"}, + {file = "cryptg-0.2.post2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:84751fe35b411475f14702e92bf9f3f1efb797e9037bc7a2166b78c4174256d1"}, + {file = "cryptg-0.2.post2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f0395770522a6545bff7fa4aaa1a14514e8aae59b11bf33883cfa470202807c5"}, + {file = "cryptg-0.2.post2-cp37-cp37m-win32.whl", hash = "sha256:1c655bd0d946a960ac752ff680c30f92e91f42eab887b44354be1d9970cce014"}, + {file = "cryptg-0.2.post2-cp37-cp37m-win_amd64.whl", hash = "sha256:56aaf179376e29472f00b116e5080b5b9d76c7b436cce7797677b021b70a69cc"}, + {file = "cryptg-0.2.post2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d308e4114eed7732113a466a4765cb3b17e21766c6c645340e6f3866d11ef9f"}, + {file = "cryptg-0.2.post2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7eff88c0f054e1d51b50d737815d7f22b2f3d4d810cf71e7dee38ca1549cdf2d"}, + {file = "cryptg-0.2.post2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3f299704ef660d279b56d466d7f5b906be5a35070672ff5bece18b9ecb7591f0"}, + {file = "cryptg-0.2.post2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:378cbb22b28aceca68e62773dfcc02ac3ec0c9d3b36698b7ead3bbbe42e6a614"}, + {file = "cryptg-0.2.post2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:2700f056c2cf6944891d6f685eaf2d16ee622ea96e1e09158a57cc99c06e839e"}, + {file = "cryptg-0.2.post2-cp38-cp38-win32.whl", hash = "sha256:ce9eb4417e60f575a169bd6044637094309e3c898697312186ce2c29aaf06b82"}, + {file = "cryptg-0.2.post2-cp38-cp38-win_amd64.whl", hash = "sha256:383703407b0354794c4a596ac23733fc88dad66e3c6af27c7a040fb09953ba87"}, + {file = "cryptg-0.2.post2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b1eabad5e0c0795ed13f702693cf2cab99886e1d5d04f8263418d8b422c51748"}, + {file = "cryptg-0.2.post2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1fe23ca09838a6222cc9d3c1882ad8e73a8a9ac9ce9579b38752360df5f0226e"}, + {file = "cryptg-0.2.post2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3db00e7b66355ce5b2d28e237bf6cd0e5d0986f8c95ea0dec874d418cb7d4c75"}, + {file = "cryptg-0.2.post2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:98311c3a70c7587457fccd67ff6a9f6377d719697839bce6a3113033f7c53eac"}, + {file = "cryptg-0.2.post2-cp39-cp39-win32.whl", hash = "sha256:6eda7ea051ca26fb364f1c308d07cf3e9f5d313c8b30f0822b1716dcb62aa55f"}, + {file = "cryptg-0.2.post2-cp39-cp39-win_amd64.whl", hash = "sha256:72b971177636ed96999888c06a37332f1df5ed3a7d97037212bc246aa5c3f421"}, + {file = "cryptg-0.2.post2-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:7b2666780cc2775eeb7015d15f32018bc4554e5e61cffc7e322d39a5a1a5fb9c"}, + {file = "cryptg-0.2.post2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a422e737231276c37baeefcebf2f7889bd8ba1c849ad653413c3923e73659ba5"}, + {file = "cryptg-0.2.post2-pp36-pypy36_pp73-win32.whl", hash = "sha256:571e43dde0f6a17fc311d494c6f66c90a7263dd9e0ed7e1d05e02485596971f7"}, + {file = "cryptg-0.2.post2-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:872d1c6f3019766abeb3622b84a7a09c75d4a11eb83aac2f817d8439b184f48f"}, + {file = "cryptg-0.2.post2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:a4c42f69a151b62393a4c09c9ffd856db094a747d423161816fb3fcfb913696e"}, + {file = "cryptg-0.2.post2-pp37-pypy37_pp73-win32.whl", hash = "sha256:e9a585f2469376abd22f5473d4be21a5b28f270799887c6a345cf2df21faaf17"}, +] +hachoir = [ + {file = "hachoir-3.1.2-py3-none-any.whl", hash = "sha256:b17ba5907b7836b2204ef724e7992d2e794311596e2121098912a9f3c4e69273"}, + {file = "hachoir-3.1.2.tar.gz", hash = "sha256:bc1259b1e2970532b2dbd99139cb0de59d9bb8904eb1489c3e8a82c979c98f23"}, +] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, @@ -273,10 +563,84 @@ more-itertools = [ {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, ] +multidict = [ + {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, + {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, + {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, + {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, + {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, + {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, + {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, + {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, + {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, + {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, + {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, + {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, + {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, +] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] +pillow = [ + {file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"}, + {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5"}, + {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8"}, + {file = "Pillow-8.1.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34"}, + {file = "Pillow-8.1.2-cp36-cp36m-win32.whl", hash = "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71"}, + {file = "Pillow-8.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341"}, + {file = "Pillow-8.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6"}, + {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28"}, + {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d"}, + {file = "Pillow-8.1.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be"}, + {file = "Pillow-8.1.2-cp37-cp37m-win32.whl", hash = "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55"}, + {file = "Pillow-8.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03"}, + {file = "Pillow-8.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e"}, + {file = "Pillow-8.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce"}, + {file = "Pillow-8.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af"}, + {file = "Pillow-8.1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06"}, + {file = "Pillow-8.1.2-cp38-cp38-win32.whl", hash = "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09"}, + {file = "Pillow-8.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060"}, + {file = "Pillow-8.1.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1"}, + {file = "Pillow-8.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6"}, + {file = "Pillow-8.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238"}, + {file = "Pillow-8.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910"}, + {file = "Pillow-8.1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1"}, + {file = "Pillow-8.1.2-cp39-cp39-win32.whl", hash = "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e"}, + {file = "Pillow-8.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0"}, + {file = "Pillow-8.1.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b"}, + {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2"}, + {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a"}, + {file = "Pillow-8.1.2.tar.gz", hash = "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44"}, +] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, @@ -285,10 +649,56 @@ py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] +pyaes = [ + {file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"}, +] +pyasn1 = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] pycodestyle = [ {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pydantic = [ + {file = "pydantic-1.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0c40162796fc8d0aa744875b60e4dc36834db9f2a25dbf9ba9664b1915a23850"}, + {file = "pydantic-1.8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fff29fe54ec419338c522b908154a2efabeee4f483e48990f87e189661f31ce3"}, + {file = "pydantic-1.8.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:fbfb608febde1afd4743c6822c19060a8dbdd3eb30f98e36061ba4973308059e"}, + {file = "pydantic-1.8.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:eb8ccf12295113ce0de38f80b25f736d62f0a8d87c6b88aca645f168f9c78771"}, + {file = "pydantic-1.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:20d42f1be7c7acc352b3d09b0cf505a9fab9deb93125061b376fbe1f06a5459f"}, + {file = "pydantic-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dde4ca368e82791de97c2ec019681ffb437728090c0ff0c3852708cf923e0c7d"}, + {file = "pydantic-1.8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3bbd023c981cbe26e6e21c8d2ce78485f85c2e77f7bab5ec15b7d2a1f491918f"}, + {file = "pydantic-1.8.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:830ef1a148012b640186bf4d9789a206c56071ff38f2460a32ae67ca21880eb8"}, + {file = "pydantic-1.8.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:fb77f7a7e111db1832ae3f8f44203691e15b1fa7e5a1cb9691d4e2659aee41c4"}, + {file = "pydantic-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3bcb9d7e1f9849a6bdbd027aabb3a06414abd6068cb3b21c49427956cce5038a"}, + {file = "pydantic-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2287ebff0018eec3cc69b1d09d4b7cebf277726fa1bd96b45806283c1d808683"}, + {file = "pydantic-1.8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4bbc47cf7925c86a345d03b07086696ed916c7663cb76aa409edaa54546e53e2"}, + {file = "pydantic-1.8.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6388ef4ef1435364c8cc9a8192238aed030595e873d8462447ccef2e17387125"}, + {file = "pydantic-1.8.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:dd4888b300769ecec194ca8f2699415f5f7760365ddbe243d4fd6581485fa5f0"}, + {file = "pydantic-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:8fbb677e4e89c8ab3d450df7b1d9caed23f254072e8597c33279460eeae59b99"}, + {file = "pydantic-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2f2736d9a996b976cfdfe52455ad27462308c9d3d0ae21a2aa8b4cd1a78f47b9"}, + {file = "pydantic-1.8.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3114d74329873af0a0e8004627f5389f3bb27f956b965ddd3e355fe984a1789c"}, + {file = "pydantic-1.8.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:258576f2d997ee4573469633592e8b99aa13bda182fcc28e875f866016c8e07e"}, + {file = "pydantic-1.8.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c17a0b35c854049e67c68b48d55e026c84f35593c66d69b278b8b49e2484346f"}, + {file = "pydantic-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8bc082afef97c5fd3903d05c6f7bb3a6af9fc18631b4cc9fedeb4720efb0c58"}, + {file = "pydantic-1.8.1-py3-none-any.whl", hash = "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520"}, + {file = "pydantic-1.8.1.tar.gz", hash = "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -328,6 +738,14 @@ requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] +rsa = [ + {file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"}, + {file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"}, +] +telethon = [ + {file = "Telethon-1.20-py3-none-any.whl", hash = "sha256:3f9eec75a6bcf6c42f20d08bb0f4908c87a15c8d8797bbb737fd27834c3a8777"}, + {file = "Telethon-1.20.tar.gz", hash = "sha256:41fea16755897c91a2d1a90b4c6d178dad06746650230c3adf5d7fb2e285f1d3"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -336,6 +754,11 @@ typer = [ {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, ] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] urllib3 = [ {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, @@ -344,3 +767,42 @@ wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] +yarl = [ + {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, + {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, + {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, + {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, + {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, + {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, + {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, + {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, + {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, + {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, + {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, + {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, + {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, +] diff --git a/pyproject.toml b/pyproject.toml index 3af178ff..95ca90c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,12 @@ requests = "^2.25.1" typer = "^0.3.2" python-dotenv = "^0.15.0" PyYAML = "^5.4.1" +pydantic = "^1.8.1" +Telethon = "^1.20" +cryptg = "^0.2.post2" +Pillow = "^8.1.2" +hachoir = "^3.1.2" +aiohttp = "^3.7.4" [tool.poetry.dev-dependencies] pytest = "^5.2" @@ -18,3 +24,6 @@ autopep8 = "^1.5.5" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +tgcf = 'tgcf:cli.app' \ No newline at end of file diff --git a/tgcf/cli.py b/tgcf/cli.py index f0f3c154..a4afaf51 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -1,32 +1,25 @@ -import requests import typer - +import logging +from tgcf.forwarder import forwarder app = typer.Typer() -@app.command() -def login(): - ''' Generate session string after signing into your Telegram account. - ''' - code_url = 'https://gist.githubusercontent.com/aahnik/da7f0545767ceb6eca1418b0e06d8719/raw/get_session_string.py' - code = requests.get(code_url).text - exec(code) - +logging.basicConfig(level=logging.INFO) @app.command() def forward(): ''' Forward all existing messages. ''' - pass + forwarder() @app.command() def sync(): - ''' Start live syncing : forward when a new message comes. + ''' Start live syncing. ''' - print('This feature is in private beta. Contact Aahnik Daw on telegram to get this. Telegram username : @aahnikdaw\ - \nor click: https://telegram.me/aahnikdaw') + print('sync') + if __name__ == '__main__': diff --git a/tgcf/forwarder.py b/tgcf/forwarder.py index e69de29b..d6a1e6ed 100644 --- a/tgcf/forwarder.py +++ b/tgcf/forwarder.py @@ -0,0 +1,36 @@ +from telethon import TelegramClient +from tgcf.settings import config, write_json +import asyncio +import logging + +from telethon.tl.patched import MessageService +from telethon.errors.rpcerrorlist import FloodWaitError +from tgcf.utils import get_client + + +async def forward_job(client: TelegramClient): + for forward in config.forwards: + last_id = 0 + async for message in client.iter_messages(forward.source, + reverse=True, + offset_id=forward.offset): + if isinstance(message, MessageService): + continue + try: + for destination in forward.dest: + await client.send_message(destination, message) + last_id = str(message.id) + logging.info('forwarding message with id = %s', last_id) + forward.offset = last_id + write_json(config) + except FloodWaitError as fwe: + print(f'Sleeping for {fwe}') + await asyncio.sleep(delay=fwe.seconds) + except Exception as err: + logging.exception(err) + break + + +def forwarder(): + client = get_client() + asyncio.run(forward_job(client)) diff --git a/tgcf/settings.py b/tgcf/settings.py index 02b9261a..be5e793a 100644 --- a/tgcf/settings.py +++ b/tgcf/settings.py @@ -1,13 +1,43 @@ +from pydantic import BaseModel +from typing import List, Optional, Dict import os from dotenv import load_dotenv +import json + +load_dotenv('.env') + + +class Forward(BaseModel): + source: int + dest: List[int] + offset: int + + +class Config(BaseModel): + forwards: List[Forward] + # whitelist: Optional[List[str]] = None + # blacklist: Optional[List[str]] = None + # replace: Optional[Dict[str,str]] = None + # block_duplicates: Optional[bool] = False -load_dotenv() API_ID = os.getenv('API_ID') API_HASH = os.getenv('API_HASH') SESSION_STRING = os.getenv('TG_SESSION_STRING') BOT_TOKEN = os.getenv('BOT_TOKEN') +print(API_HASH, API_ID, BOT_TOKEN) + + +def load_config() -> Config: + with open('config.json') as file: + data = json.load(file) + return Config(**data) + + +def write_json(config: Config): + with open('config.json', 'w') as file: + json.dump(config, file) -# during clean up last these many messages will be kept +config = load_config() diff --git a/tgcf/syncer.py b/tgcf/syncer.py index e69de29b..292c6c48 100644 --- a/tgcf/syncer.py +++ b/tgcf/syncer.py @@ -0,0 +1,9 @@ +from telethon import client +from tgcf.utils import get_client + +client = get_client() + +def syncer(): + client.start() + client.run_until_disconnected() + \ No newline at end of file diff --git a/tgcf/utils.py b/tgcf/utils.py index 4419bc12..73ac1c8e 100644 --- a/tgcf/utils.py +++ b/tgcf/utils.py @@ -1,8 +1,19 @@ import os +from tgcf.settings import API_ID,API_HASH,SESSION_STRING,BOT_TOKEN +from telethon import TelegramClient +from telethon.sessions import StringSession +import sys -def intify(string: str): - try: - return int(string) - except: - return string +def get_client(): + if BOT_TOKEN: + client = TelegramClient('bot', API_ID, API_HASH).start(bot_token=BOT_TOKEN) + elif SESSION_STRING: + client = TelegramClient(StringSession(SESSION_STRING), API_ID, API_HASH) + else: + print('Neither BOT_TOKEN nor TG_SESSION_STRING found.') + sys.exit() + return client + +def check_version(): + print('You are not ..') From 9cefa8c3bf21e7cfe80a46af68c96cd4d58f0bf7 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:18:47 +0530 Subject: [PATCH 04/68] update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b804e76e..109efd87 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ config.yml config.json t.py - +tgcf.config.yml From 67617e80f39c87283ca2892352bc028921bafe16 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:19:09 +0530 Subject: [PATCH 05/68] add cli usage documentation generated by typer --- docs/cli_usage.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/cli_usage.md diff --git a/docs/cli_usage.md b/docs/cli_usage.md new file mode 100644 index 00000000..1a4866e5 --- /dev/null +++ b/docs/cli_usage.md @@ -0,0 +1,20 @@ +# `tgcf` + +tgcf is a powerful tool for forwarding telegram messages or live syncing. Make sure you have API_ID and API_HASH in your .env file inside the current directory. + + +**Usage**: + +```console +$ tgcf [OPTIONS] +``` + +**Options**: + +* `-n, --name TEXT`: Name of the bot/userbot you want to run. [env var: NAME; required] +* `-t, --token TEXT`: Bot Token or Session String [env var: TOKEN; required] +* `--API_ID INTEGER`: API ID obtained from my.telegram.org [env var: API_ID; required] +* `--API_HASH TEXT`: API HASH obtained from my.telegram.org [env var: API_HASH; required] +* `-l, --loud`: Increase output verbosity. [env var: LOUD] +* `-v, --version`: Show version and exit. +* `--help`: Show this message and exit. From c020c58704547ce3dcfb8cf55544fdb173fb1b72 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:19:30 +0530 Subject: [PATCH 06/68] a robust cli built with typer --- tgcf/cli.py | 94 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/tgcf/cli.py b/tgcf/cli.py index a4afaf51..74ea225c 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -1,26 +1,90 @@ -import typer +''' This module implements the command line interface for tgcf, +using the modern and robust `typer`. +''' + + +from tgcf import __version__ +from typing import Optional import logging -from tgcf.forwarder import forwarder -app = typer.Typer() +import typer +from dotenv import load_dotenv + +import os +load_dotenv('.env') +FAKE = bool(os.getenv('FAKE_TGCF')) +app = typer.Typer(add_completion=False) -logging.basicConfig(level=logging.INFO) -@app.command() -def forward(): - ''' Forward all existing messages. - ''' - forwarder() +def version_callback(value: bool): + if value: + print(__version__) + raise typer.Exit() + + +def verbosity_callback(value: bool): + if value: + logging.info( + 'Verbosity turned on. \nThis is suitable for debugging.\n') + level = logging.INFO + else: + level = logging.WARNING + logging.basicConfig(level=level) @app.command() -def sync(): - ''' Start live syncing. - ''' - print('sync') +def main( + name: str = typer.Option(..., + '--name', '-n', + help='Name of the bot/userbot you want to run.', + envvar='NAME', + prompt='Please enter the bot/userbot username'), + + token: str = typer.Option(..., + '--token', '-t', + help='Bot Token or Session String', + envvar='TOKEN', + prompt='Please paste the Bot Token or Session String \ + (your input will be invisible)', + hide_input=True), + API_ID: int = typer.Option(..., + '--API_ID', + help='API ID obtained from my.telegram.org', + envvar='API_ID', + prompt='Please paste your API ID\ + (your input will be invisible)', + hide_input=True), + API_HASH: str = typer.Option(..., + '--API_HASH', + help='API HASH obtained from my.telegram.org', + envvar='API_HASH', + prompt='Please paste your API HASH\ + (your input will be invisible)', + hide_input=True), + + verbose: Optional[bool] = typer.Option(None, + '--loud', '-l', + callback=verbosity_callback, + envvar='LOUD', + help='Increase output verbosity.'), + + version: Optional[bool] = typer.Option(None, + '--version', + '-v', + callback=version_callback, + help='Show version and exit.') + +): + ''' tgcf is a powerful tool for forwarding telegram messages or live syncing. Make sure you have API_ID and API_HASH in your .env file inside the current directory. + ''' + if not FAKE: + print('Real working') + else: + # when the env var FAKE_TELEWATER is truthy, then no real work is done + # this is for CLI testing purposes + print(f'name is {name} and token is {token}') -if __name__ == '__main__': - app() +# AAHNIK 2021 From dbba47158a7c933d37f585901af286535fbaefbe Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:20:07 +0530 Subject: [PATCH 07/68] remove settings.py as envvars are read using typer --- tgcf/settings.py | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 tgcf/settings.py diff --git a/tgcf/settings.py b/tgcf/settings.py deleted file mode 100644 index be5e793a..00000000 --- a/tgcf/settings.py +++ /dev/null @@ -1,43 +0,0 @@ -from pydantic import BaseModel -from typing import List, Optional, Dict -import os -from dotenv import load_dotenv -import json - -load_dotenv('.env') - - -class Forward(BaseModel): - source: int - dest: List[int] - offset: int - - -class Config(BaseModel): - forwards: List[Forward] - # whitelist: Optional[List[str]] = None - # blacklist: Optional[List[str]] = None - # replace: Optional[Dict[str,str]] = None - # block_duplicates: Optional[bool] = False - - -API_ID = os.getenv('API_ID') -API_HASH = os.getenv('API_HASH') -SESSION_STRING = os.getenv('TG_SESSION_STRING') -BOT_TOKEN = os.getenv('BOT_TOKEN') - -print(API_HASH, API_ID, BOT_TOKEN) - - -def load_config() -> Config: - with open('config.json') as file: - data = json.load(file) - return Config(**data) - - -def write_json(config: Config): - with open('config.json', 'w') as file: - json.dump(config, file) - - -config = load_config() From 64c46984f9565308a5158b7b994a973a6207b8b2 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:33:06 +0530 Subject: [PATCH 08/68] add the .github with issue links and PR template --- .github/CONTRIBUTING.md | 11 +++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++++++++ .github/pull_request_template.md | 15 +++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/pull_request_template.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..af82bb32 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing Guidelines + +1. Read the README and documentation thoroughly. Also watch relavant videos if availaible. +2. Create an issue with a bug or a feature request. +3. Contact me on telegram https://telegram.me/aahnikdaw and discuss the changes you want to make. +4. Follow the code style of the project. +5. You are recommended to read these additional guidelines about Pull Requests. + + - [The (written) unwritten guide to pull requests](https://www.atlassian.com/blog/git/written-unwritten-guide-pull-requests) + - [How to write the perfect pull request](https://github.blog/2015-01-21-how-to-write-the-perfect-pull-request/) + - [Open Source Pull Request Guidelines](https://opensource.creativecommons.org/contributing-code/pr-guidelines/) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..ab1879f6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Read Documentation + url: https://aahnik.github.io/tgcf/ + about: Read the guides and tutorials + - name: Ask a Question or Discuss + url: https://github.com/aahnik/tgcf/discussions/new + about: You may ask a question, get help about errors or start a discussion. \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..3c9a3e06 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +Before Creating a pull request, please read the contributing guidelines thoroughly. + + + + + + + +I have fully read and understood the terms and conditions laid down in the general [Contributor License Agreement](https://aahnik.github.io/aahnik/CLA.html) + +- [ ] I agree to distribute my code contributions under MIT License, and will not change it in the future. +- [ ] I agree that any contribution once merged, cannot be taken back by me. +- [ ] I will abide by the Code of Conduct +- [ ] I understand that the decision of the maintainer is final and abiding. And the maintainer reserves all rights to modify my code. I also understand that the maintainer can remove my code in future, if he thinks so. +- [ ] Once my contribution is merged, my name will permanently appear in the Contribtor's List of this repository. \ No newline at end of file From 0cb940174c3b1bf2e23e7ff7b47407eefa8d48b0 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:33:18 +0530 Subject: [PATCH 09/68] update the .gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 109efd87..8ba0d72e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ *.session .vscode *.session-journal -config.yml -config.json t.py tgcf.config.yml From bcdf792d676f2ca99cd4cb2b08aa7feae027f02e Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:33:30 +0530 Subject: [PATCH 10/68] update project dependancies --- poetry.lock | 177 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 11 +-- 2 files changed, 183 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1a7f7e0c..7c7fc7eb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,6 +17,18 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotlipy", "cchardet"] +[[package]] +name = "astroid" +version = "2.5.2" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = ">=1.11,<1.13" + [[package]] name = "async-timeout" version = "3.0.1" @@ -133,6 +145,50 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "importlib-metadata" +version = "2.1.1" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "pep517", "unittest2", "importlib-resources (>=1.3)"] + +[[package]] +name = "isort" +version = "5.8.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.6.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "more-itertools" version = "8.7.0" @@ -234,6 +290,24 @@ typing-extensions = ">=3.7.4.3" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pylint" +version = "2.7.4" +description = "python code static checker" +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +astroid = ">=2.5.2,<2.7" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +toml = ">=0.7.1" + +[package.extras] +docs = ["sphinx (==3.5.1)", "python-docs-theme (==2020.12)"] + [[package]] name = "pyparsing" version = "2.4.7" @@ -312,6 +386,14 @@ python-versions = ">=3.5, <4" [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "shellingham" +version = "1.4.0" +description = "Tool to Detect Surrounding Shell" +category = "dev" +optional = false +python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" + [[package]] name = "telethon" version = "1.20" @@ -352,6 +434,20 @@ all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] +[[package]] +name = "typer-cli" +version = "0.0.11" +description = "Run Typer scripts with completion, without having to create a package, using Typer CLI." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +colorama = ">=0.4.3,<0.5.0" +importlib_metadata = ">=1.5,<3.0" +shellingham = ">=1.3.2,<2.0.0" +typer = ">=0.3.0,<0.4.0" + [[package]] name = "typing-extensions" version = "3.7.4.3" @@ -381,6 +477,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "wrapt" +version = "1.12.1" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "yarl" version = "1.6.3" @@ -393,10 +497,22 @@ python-versions = ">=3.6" idna = ">=2.0" multidict = ">=4.0" +[[package]] +name = "zipp" +version = "3.4.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "72a0bc687112d9fd02e54c7338e4895488a7d12645139da620394741eaa51e5c" +content-hash = "4b96e1470e78b618c3c9e61c761a8081face31f3e357f64e76011467cd708782" [metadata.files] aiohttp = [ @@ -438,6 +554,10 @@ aiohttp = [ {file = "aiohttp-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e"}, {file = "aiohttp-3.7.4.tar.gz", hash = "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de"}, ] +astroid = [ + {file = "astroid-2.5.2-py3-none-any.whl", hash = "sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df"}, + {file = "astroid-2.5.2.tar.gz", hash = "sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9"}, +] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, @@ -559,6 +679,42 @@ idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] +importlib-metadata = [ + {file = "importlib_metadata-2.1.1-py2.py3-none-any.whl", hash = "sha256:c2d6341ff566f609e89a2acb2db190e5e1d23d5409d6cc8d2fe34d72443876d4"}, + {file = "importlib_metadata-2.1.1.tar.gz", hash = "sha256:b8de9eff2b35fb037368f28a7df1df4e6436f578fa74423505b6c6a778d5b5dd"}, +] +isort = [ + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] more-itertools = [ {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, @@ -699,6 +855,10 @@ pydantic = [ {file = "pydantic-1.8.1-py3-none-any.whl", hash = "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520"}, {file = "pydantic-1.8.1.tar.gz", hash = "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3"}, ] +pylint = [ + {file = "pylint-2.7.4-py3-none-any.whl", hash = "sha256:209d712ec870a0182df034ae19f347e725c1e615b2269519ab58a35b3fcbbe7a"}, + {file = "pylint-2.7.4.tar.gz", hash = "sha256:bd38914c7731cdc518634a8d3c5585951302b6e2b6de60fbb3f7a0220e21eeee"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -742,6 +902,10 @@ rsa = [ {file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"}, {file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"}, ] +shellingham = [ + {file = "shellingham-1.4.0-py2.py3-none-any.whl", hash = "sha256:536b67a0697f2e4af32ab176c00a50ac2899c5a05e0d8e2dadac8e58888283f9"}, + {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, +] telethon = [ {file = "Telethon-1.20-py3-none-any.whl", hash = "sha256:3f9eec75a6bcf6c42f20d08bb0f4908c87a15c8d8797bbb737fd27834c3a8777"}, {file = "Telethon-1.20.tar.gz", hash = "sha256:41fea16755897c91a2d1a90b4c6d178dad06746650230c3adf5d7fb2e285f1d3"}, @@ -754,6 +918,10 @@ typer = [ {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, ] +typer-cli = [ + {file = "typer-cli-0.0.11.tar.gz", hash = "sha256:bb90d4edde3d53f076909a7be9ac35f12e573096fc0f3802b3d6f42929a1219e"}, + {file = "typer_cli-0.0.11-py3-none-any.whl", hash = "sha256:ecff43bc8c5d786deaa25b7d14ebfc59b32e40b07895259c3e86604af188f39b"}, +] typing-extensions = [ {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, @@ -767,6 +935,9 @@ wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] +wrapt = [ + {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, +] yarl = [ {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, @@ -806,3 +977,7 @@ yarl = [ {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] +zipp = [ + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, +] diff --git a/pyproject.toml b/pyproject.toml index 95ca90c5..dfa4948e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tgcf" -version = "0.1.0" +version = "0.1.1" description = "A simple script to forward all the messages of one chat (private/group/channel) to another. Made using Telethon. Can be used to back up the contents of a chat to another place.You can also start a live sync to forward all upcoming messages" authors = ["aahnik "] @@ -20,10 +20,13 @@ aiohttp = "^3.7.4" [tool.poetry.dev-dependencies] pytest = "^5.2" autopep8 = "^1.5.5" +pylint = "^2.7.4" +typer-cli = "^0.0.11" + + +[tool.poetry.scripts] +tgcf = 'tgcf:cli.app' [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" - -[tool.poetry.scripts] -tgcf = 'tgcf:cli.app' \ No newline at end of file From dc2f580f5992a41858fb3f66a0e9e7b4b921496c Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:47:04 +0530 Subject: [PATCH 11/68] a robust config.py mvp --- tgcf/config.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tgcf/config.py diff --git a/tgcf/config.py b/tgcf/config.py new file mode 100644 index 00000000..fa74b383 --- /dev/null +++ b/tgcf/config.py @@ -0,0 +1,30 @@ +# a custom config parser +import yaml +from typing import List +from pydantic import BaseModel + + + +class Forward(BaseModel): + source: int + dest: List[int] + offset: int + +class Config(BaseModel): + forwards: List[Forward] + +def read_config(config_file:str): + with open(config_file) as file: + config_dict = yaml.full_load(file) + try: + config = Config(**config_dict) + except Exception as err: + print(err) + quit(1) + else: + return config + +def update_config(config_file:str,config:Config): + with open(config_file,'w') as file: + yaml.dump(config.dict(),file) + From 3333e9a550d43f72ffcc371b8cbce7659075e0e7 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:47:12 +0530 Subject: [PATCH 12/68] add funding.yml --- .github/FUNDING.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..e4c5da4f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ + +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['https://aahnik.dev/support'] \ No newline at end of file From 572a840317e0e9cd56903f3f6b3b2e90f3f079fb Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 11:47:40 +0530 Subject: [PATCH 13/68] take path of config file as cli option --- tgcf/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tgcf/cli.py b/tgcf/cli.py index 74ea225c..6b364c20 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -63,7 +63,9 @@ def main( (your input will be invisible)', hide_input=True), - + config: str = typer.Option( + 'tgcf.config.yml', + help='Path of configuration file'), verbose: Optional[bool] = typer.Option(None, '--loud', '-l', callback=verbosity_callback, From 8f5cda3061592cea260dc78dbb4744dec062a451 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 12:35:21 +0530 Subject: [PATCH 14/68] add argument mode, and enhance --- tgcf/cli.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/tgcf/cli.py b/tgcf/cli.py index 6b364c20..3428fffc 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -2,6 +2,7 @@ using the modern and robust `typer`. ''' +from enum import Enum from tgcf import __version__ from typing import Optional @@ -13,10 +14,15 @@ import os load_dotenv('.env') -FAKE = bool(os.getenv('FAKE_TGCF')) +FAKE = bool(os.getenv('FAKE')) app = typer.Typer(add_completion=False) +class Mode(str, Enum): + past = 'past' + live = 'live' + + def version_callback(value: bool): if value: print(__version__) @@ -35,6 +41,8 @@ def verbosity_callback(value: bool): @app.command() def main( + mode: Mode = typer.Argument(..., + help='Choose the mode in which you want to run tgcf.'), name: str = typer.Option(..., '--name', '-n', help='Name of the bot/userbot you want to run.', @@ -63,7 +71,7 @@ def main( (your input will be invisible)', hide_input=True), - config: str = typer.Option( + config_file: str = typer.Option( 'tgcf.config.yml', help='Path of configuration file'), verbose: Optional[bool] = typer.Option(None, @@ -79,14 +87,27 @@ def main( help='Show version and exit.') ): - ''' tgcf is a powerful tool for forwarding telegram messages or live syncing. Make sure you have API_ID and API_HASH in your .env file inside the current directory. + ''' tgcf is a powerful tool for forwarding telegram messages from source to destination. + + tgcf offers two modes of operation. + + The "past" mode is for forwarding all existing messages. (performs the job and quits). + + On the other hand the "live" mode will forward all new upcoming messages, as long as tgcf runs in the server. + + You can specify the source and destination chats in the "tgcf.config.yml" file in the format specified in the documentation. + + tgcf also supports filtering by whitelist/blacklist/mime-type/message author, text replacement, and many more features. ''' if not FAKE: - print('Real working') + print('Real working...') else: # when the env var FAKE_TELEWATER is truthy, then no real work is done # this is for CLI testing purposes print(f'name is {name} and token is {token}') + print(f'{API_ID} and {API_HASH}') + print(f'mode = {mode}') + print(f'config file path = {config_file}') # AAHNIK 2021 From adf6239e5b340727b3ac1cef3cbf39e4493eab73 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 12:35:41 +0530 Subject: [PATCH 15/68] delete utils.py --- tgcf/utils.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 tgcf/utils.py diff --git a/tgcf/utils.py b/tgcf/utils.py deleted file mode 100644 index 73ac1c8e..00000000 --- a/tgcf/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -import os -from tgcf.settings import API_ID,API_HASH,SESSION_STRING,BOT_TOKEN -from telethon import TelegramClient -from telethon.sessions import StringSession -import sys - -def get_client(): - if BOT_TOKEN: - client = TelegramClient('bot', API_ID, API_HASH).start(bot_token=BOT_TOKEN) - elif SESSION_STRING: - client = TelegramClient(StringSession(SESSION_STRING), API_ID, API_HASH) - else: - print('Neither BOT_TOKEN nor TG_SESSION_STRING found.') - sys.exit() - return client - - -def check_version(): - print('You are not ..') From ab7543b2f8735216909071391d23f7c91ea07fdd Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 12:36:58 +0530 Subject: [PATCH 16/68] wip in these files --- tgcf/__init__.py | 1 + tgcf/forwarder.py | 9 +++------ tgcf/syncer.py | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/tgcf/__init__.py b/tgcf/__init__.py index e69de29b..d1f2e39a 100644 --- a/tgcf/__init__.py +++ b/tgcf/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.1" \ No newline at end of file diff --git a/tgcf/forwarder.py b/tgcf/forwarder.py index d6a1e6ed..e4ea7644 100644 --- a/tgcf/forwarder.py +++ b/tgcf/forwarder.py @@ -1,14 +1,13 @@ from telethon import TelegramClient -from tgcf.settings import config, write_json import asyncio import logging from telethon.tl.patched import MessageService from telethon.errors.rpcerrorlist import FloodWaitError -from tgcf.utils import get_client -async def forward_job(client: TelegramClient): + +async def forward_job(client: TelegramClient,config): for forward in config.forwards: last_id = 0 async for message in client.iter_messages(forward.source, @@ -22,7 +21,6 @@ async def forward_job(client: TelegramClient): last_id = str(message.id) logging.info('forwarding message with id = %s', last_id) forward.offset = last_id - write_json(config) except FloodWaitError as fwe: print(f'Sleeping for {fwe}') await asyncio.sleep(delay=fwe.seconds) @@ -31,6 +29,5 @@ async def forward_job(client: TelegramClient): break -def forwarder(): - client = get_client() +def forwarder(client): asyncio.run(forward_job(client)) diff --git a/tgcf/syncer.py b/tgcf/syncer.py index 292c6c48..de643111 100644 --- a/tgcf/syncer.py +++ b/tgcf/syncer.py @@ -1,9 +1,8 @@ from telethon import client -from tgcf.utils import get_client -client = get_client() + +# client = get_client() def syncer(): client.start() client.run_until_disconnected() - \ No newline at end of file From fbe10915795d9971f3e614743049cf4ccef3fd5f Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 13:03:15 +0530 Subject: [PATCH 17/68] make a beautify readme top --- README.md | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4664ec12..4d9371d5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,33 @@ -# tgcf +

+ tgcf logo +

+ +

tgcf

+

- tgcf logo 100x100 px +A powerful tool for forwarding telegram messages from source to destination.

-A **command-line + bot** interface to **forward** telegram **messages** (all past messages or start live sync) from **source to destination** (channels/groups/chats). Supports filtering by mime type,blacklisting, whitelisting, replacement, and tons of other features. Easily deploy to your **cloud platform** (like Heroku) of your choice. +

GitHub license +GitHub stars +GitHub issues +PyPI +Twitter

+ +------- + +## Features + +tgcf offers two modes of operation. + +The "past" mode is for forwarding all existing messages. (performs the job and quits). + +On the other hand the "live" mode will forward all new upcoming messages, as long as tgcf runs in the server. + +You can specify the source and destination chats in the "tgcf.config.yml" file in the format specified in the documentation. + +tgcf also supports filtering by whitelist/blacklist/mime-type/message author, text replacement, and many more features. Join the telegram channel [t.me/tg_cf](https://telegram.me/tg_cf) to get updates (and not ads). @@ -21,23 +44,12 @@ Make sure you have latest version of Python installed. pip install tgcf ``` -## Login ๐Ÿ’ป - -Open your terminal or command-prompt and run - -``` -tgcf login -``` - ## Configuration โš™๏ธ ## Deploy to cloud ๐ŸŒฉ๏ธ -## Sponsors ๐Ÿค‘ - -My heartfelt thanks to all those who sponsored. ## Reviews ๐ŸŒŸ From 2a87ebfe31ee45656ee98e8fff3c6c942e036616 Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 13:15:57 +0530 Subject: [PATCH 18/68] update readme with key features list --- README.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4d9371d5..f78d2a3b 100644 --- a/README.md +++ b/README.md @@ -15,21 +15,16 @@ A powerful tool for forwarding telegram messages from source to destination. PyPI Twitter

-------- +
-## Features +The *key features* are: -tgcf offers two modes of operation. - -The "past" mode is for forwarding all existing messages. (performs the job and quits). - -On the other hand the "live" mode will forward all new upcoming messages, as long as tgcf runs in the server. - -You can specify the source and destination chats in the "tgcf.config.yml" file in the format specified in the documentation. - -tgcf also supports filtering by whitelist/blacklist/mime-type/message author, text replacement, and many more features. - -Join the telegram channel [t.me/tg_cf](https://telegram.me/tg_cf) to get updates (and not ads). +1. Two modes of operation: **past** and **live** for dealing with existing or upcoming messages. +2. Supports both telegram **bot account** as well as **user account**. +3. **Custom Filtering** of messages based on **whitelist/blacklist**, **mime-type** and so on. +4. Modification of messages like **Text Replacement**, **Watermarking**, **OCR** etc. +5. Easily extend by writing you own extension in python. +6. Detailed **documentation** + **Video** tutorial + **Fast help** in discussion forum. ## Video Tutorial ๐Ÿ“บ From ef4070c19b833dfa6d3c3f5dbf30874cb408f38f Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 16:15:33 +0530 Subject: [PATCH 19/68] upload work (wip) --- tgcf/__init__.py | 3 +- tgcf/cli.py | 60 ++++++++++++---------------------- tgcf/config.py | 11 +++---- tgcf/const.py | 19 +++++++++++ tgcf/live.py | 0 tgcf/main.py | 19 +++++++++++ tgcf/{forwarder.py => past.py} | 11 ++++--- tgcf/syncer.py | 8 ----- 8 files changed, 72 insertions(+), 59 deletions(-) create mode 100644 tgcf/const.py create mode 100644 tgcf/live.py create mode 100644 tgcf/main.py rename tgcf/{forwarder.py => past.py} (83%) delete mode 100644 tgcf/syncer.py diff --git a/tgcf/__init__.py b/tgcf/__init__.py index d1f2e39a..3a7d4d5f 100644 --- a/tgcf/__init__.py +++ b/tgcf/__init__.py @@ -1 +1,2 @@ -__version__ = "0.1.1" \ No newline at end of file +__version__ = "0.1.1" + diff --git a/tgcf/cli.py b/tgcf/cli.py index 3428fffc..a3890e0b 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -2,15 +2,16 @@ using the modern and robust `typer`. ''' -from enum import Enum from tgcf import __version__ +from tgcf.const import Mode from typing import Optional import logging - +from tgcf.const import Auth import typer from dotenv import load_dotenv +from tgcf.main import start_past, start_live import os load_dotenv('.env') @@ -18,11 +19,6 @@ app = typer.Typer(add_completion=False) -class Mode(str, Enum): - past = 'past' - live = 'live' - - def version_callback(value: bool): if value: print(__version__) @@ -39,23 +35,17 @@ def verbosity_callback(value: bool): logging.basicConfig(level=level) +def session_callback(value: str or None): + pass + + @app.command() def main( mode: Mode = typer.Argument(..., help='Choose the mode in which you want to run tgcf.'), - name: str = typer.Option(..., - '--name', '-n', - help='Name of the bot/userbot you want to run.', - envvar='NAME', - prompt='Please enter the bot/userbot username'), - - token: str = typer.Option(..., - '--token', '-t', - help='Bot Token or Session String', - envvar='TOKEN', - prompt='Please paste the Bot Token or Session String \ - (your input will be invisible)', - hide_input=True), + + + API_ID: int = typer.Option(..., '--API_ID', help='API ID obtained from my.telegram.org', @@ -70,10 +60,12 @@ def main( prompt='Please paste your API HASH\ (your input will be invisible)', hide_input=True), - - config_file: str = typer.Option( - 'tgcf.config.yml', - help='Path of configuration file'), + session: str = typer.Option(None, + '--session', '-s', + help='Path to session file or Session String', + envvar='SESSION', + callback=session_callback, + hide_input=True), verbose: Optional[bool] = typer.Option(None, '--loud', '-l', callback=verbosity_callback, @@ -89,25 +81,15 @@ def main( ): ''' tgcf is a powerful tool for forwarding telegram messages from source to destination. - tgcf offers two modes of operation. - - The "past" mode is for forwarding all existing messages. (performs the job and quits). + Don't forget to star https://github.com/aahnik/tgcf - On the other hand the "live" mode will forward all new upcoming messages, as long as tgcf runs in the server. - - You can specify the source and destination chats in the "tgcf.config.yml" file in the format specified in the documentation. - - tgcf also supports filtering by whitelist/blacklist/mime-type/message author, text replacement, and many more features. + Telegram Channel https://telegram.me/tg_cf ''' - if not FAKE: - print('Real working...') - else: - # when the env var FAKE_TELEWATER is truthy, then no real work is done - # this is for CLI testing purposes - print(f'name is {name} and token is {token}') + if FAKE: print(f'{API_ID} and {API_HASH}') print(f'mode = {mode}') - print(f'config file path = {config_file}') + quit(1) + print('normal') # AAHNIK 2021 diff --git a/tgcf/config.py b/tgcf/config.py index fa74b383..7826a69c 100644 --- a/tgcf/config.py +++ b/tgcf/config.py @@ -2,8 +2,7 @@ import yaml from typing import List from pydantic import BaseModel - - +from tgcf.const import CONFIG_FILE class Forward(BaseModel): source: int @@ -13,8 +12,8 @@ class Forward(BaseModel): class Config(BaseModel): forwards: List[Forward] -def read_config(config_file:str): - with open(config_file) as file: +def read_config(): + with open(CONFIG_FILE) as file: config_dict = yaml.full_load(file) try: config = Config(**config_dict) @@ -24,7 +23,7 @@ def read_config(config_file:str): else: return config -def update_config(config_file:str,config:Config): - with open(config_file,'w') as file: +def update_config(config:Config): + with open(CONFIG_FILE,'w') as file: yaml.dump(config.dict(),file) diff --git a/tgcf/const.py b/tgcf/const.py new file mode 100644 index 00000000..7de40afd --- /dev/null +++ b/tgcf/const.py @@ -0,0 +1,19 @@ +from enum import Enum +from typing import Optional +from pydantic import BaseModel + +CONFIG_FILE = 'tgcf.config.yml' + + +class Mode(str, Enum): + past = 'past' + live = 'live' + +class Auth(BaseModel): + API_ID: int + API_HASH: str + USERNAME: str + BOT_TOKEN: Optional[str] + PHONE_NO: Optional[str] + SESSION_STRING: Optional[str] + diff --git a/tgcf/live.py b/tgcf/live.py new file mode 100644 index 00000000..e69de29b diff --git a/tgcf/main.py b/tgcf/main.py new file mode 100644 index 00000000..3c904942 --- /dev/null +++ b/tgcf/main.py @@ -0,0 +1,19 @@ +from telethon import TelegramClient +from telethon.sessions import StringSession +import asyncio +from tgcf.past import forward_job +from tgcf.config import read_config + +config = read_config() + + + +def start_past(client): + asyncio.run(forward_job(client, config)) + + +def start_live(client): + pass + +def start_tgcf(mode): + pass diff --git a/tgcf/forwarder.py b/tgcf/past.py similarity index 83% rename from tgcf/forwarder.py rename to tgcf/past.py index e4ea7644..17d15b4c 100644 --- a/tgcf/forwarder.py +++ b/tgcf/past.py @@ -5,11 +5,15 @@ from telethon.tl.patched import MessageService from telethon.errors.rpcerrorlist import FloodWaitError +from tgcf.config import Config -async def forward_job(client: TelegramClient,config): +async def forward_job(client: TelegramClient, config: Config): + await client.start() for forward in config.forwards: last_id = 0 + print(forward.source) + print(forward.dest) async for message in client.iter_messages(forward.source, reverse=True, offset_id=forward.offset): @@ -27,7 +31,4 @@ async def forward_job(client: TelegramClient,config): except Exception as err: logging.exception(err) break - - -def forwarder(client): - asyncio.run(forward_job(client)) + await client.disconnect() diff --git a/tgcf/syncer.py b/tgcf/syncer.py deleted file mode 100644 index de643111..00000000 --- a/tgcf/syncer.py +++ /dev/null @@ -1,8 +0,0 @@ -from telethon import client - - -# client = get_client() - -def syncer(): - client.start() - client.run_until_disconnected() From 0b343f8b44f2e5c8ebe9ad6221e5e03c4277aa2d Mon Sep 17 00:00:00 2001 From: aahnik Date: Wed, 31 Mar 2021 16:16:37 +0530 Subject: [PATCH 20/68] add branch not ready notice --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f78d2a3b..7647a797 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +this branch is not ready for use. go to the main branch https://github.com/aahnik/tgcf + +

tgcf logo

From 8dd87f7ef2fb3a5ff463dc352cfc0af7cfc9aa36 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 21:28:48 +0530 Subject: [PATCH 21/68] simplify and remove fking complexity --- tgcf/cli.py | 45 ++++++++++++--------------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/tgcf/cli.py b/tgcf/cli.py index a3890e0b..4694eee6 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -4,13 +4,11 @@ from tgcf import __version__ -from tgcf.const import Mode from typing import Optional import logging -from tgcf.const import Auth +from enum import Enum import typer from dotenv import load_dotenv - from tgcf.main import start_past, start_live import os load_dotenv('.env') @@ -19,6 +17,9 @@ app = typer.Typer(add_completion=False) +class Mode(str, Enum): + past = 'past' + live = 'live' def version_callback(value: bool): if value: print(__version__) @@ -35,37 +36,11 @@ def verbosity_callback(value: bool): logging.basicConfig(level=level) -def session_callback(value: str or None): - pass - @app.command() def main( - mode: Mode = typer.Argument(..., - help='Choose the mode in which you want to run tgcf.'), - - - - API_ID: int = typer.Option(..., - '--API_ID', - help='API ID obtained from my.telegram.org', - envvar='API_ID', - prompt='Please paste your API ID\ - (your input will be invisible)', - hide_input=True), - API_HASH: str = typer.Option(..., - '--API_HASH', - help='API HASH obtained from my.telegram.org', - envvar='API_HASH', - prompt='Please paste your API HASH\ - (your input will be invisible)', - hide_input=True), - session: str = typer.Option(None, - '--session', '-s', - help='Path to session file or Session String', - envvar='SESSION', - callback=session_callback, - hide_input=True), + mode: Mode = typer.Argument(..., + help='Choose the mode in which you want to run tgcf.'), verbose: Optional[bool] = typer.Option(None, '--loud', '-l', callback=verbosity_callback, @@ -87,9 +62,13 @@ def main( ''' if FAKE: - print(f'{API_ID} and {API_HASH}') print(f'mode = {mode}') quit(1) - print('normal') + + if mode == mode.past: + start_past() + else: + start_live() + # AAHNIK 2021 From e2c60d7d0547c6577d667cbb33ed613718a55e24 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 21:29:05 +0530 Subject: [PATCH 22/68] the config.py --- tgcf/config.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/tgcf/config.py b/tgcf/config.py index 7826a69c..c85a27a3 100644 --- a/tgcf/config.py +++ b/tgcf/config.py @@ -1,17 +1,25 @@ # a custom config parser + + import yaml from typing import List from pydantic import BaseModel -from tgcf.const import CONFIG_FILE +import os +from telethon.sessions import StringSession + +CONFIG_FILE = 'tgcf.config.yml' + class Forward(BaseModel): source: int dest: List[int] offset: int + class Config(BaseModel): forwards: List[Forward] + def read_config(): with open(CONFIG_FILE) as file: config_dict = yaml.full_load(file) @@ -23,7 +31,30 @@ def read_config(): else: return config -def update_config(config:Config): - with open(CONFIG_FILE,'w') as file: - yaml.dump(config.dict(),file) +def update_config(config: Config): + with open(CONFIG_FILE, 'w') as file: + yaml.dump(config.dict(), file) + + +def env_var(name: str, optional=False): + var = os.getenv(name) + + while not var: + if optional: + break + var = input(f'Enter {name}: ') + return var + + +API_ID = env_var('API_ID') +API_HASH = env_var('API_HASH') +USERNAME = env_var('USERNAME', optional=True) +SESSION_STRING = env_var('SESSION_STRING', optional=True) + +if SESSION_STRING: + SESSION = StringSession(SESSION_STRING) +else: + SESSION = 'tgcf' + +CONFIG = read_config() From 40b963b2b2c0dbba2458a4ac79c8dfab1926bc46 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 21:29:18 +0530 Subject: [PATCH 23/68] delete this worthless piece of shit --- tgcf/const.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 tgcf/const.py diff --git a/tgcf/const.py b/tgcf/const.py deleted file mode 100644 index 7de40afd..00000000 --- a/tgcf/const.py +++ /dev/null @@ -1,19 +0,0 @@ -from enum import Enum -from typing import Optional -from pydantic import BaseModel - -CONFIG_FILE = 'tgcf.config.yml' - - -class Mode(str, Enum): - past = 'past' - live = 'live' - -class Auth(BaseModel): - API_ID: int - API_HASH: str - USERNAME: str - BOT_TOKEN: Optional[str] - PHONE_NO: Optional[str] - SESSION_STRING: Optional[str] - From f17f7c68ad9764996edc55890e15b907cd822951 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 21:29:27 +0530 Subject: [PATCH 24/68] you are main --- tgcf/main.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tgcf/main.py b/tgcf/main.py index 3c904942..8af92b73 100644 --- a/tgcf/main.py +++ b/tgcf/main.py @@ -2,18 +2,16 @@ from telethon.sessions import StringSession import asyncio from tgcf.past import forward_job -from tgcf.config import read_config -config = read_config() -def start_past(client): - asyncio.run(forward_job(client, config)) +def start_past(): + asyncio.run(forward_job()) -def start_live(client): - pass -def start_tgcf(mode): - pass + +def start_live(): + print('live') + From 77a9747d6e2da5589433ca849b673e13168c44f6 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 21:29:35 +0530 Subject: [PATCH 25/68] you are history --- tgcf/past.py | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/tgcf/past.py b/tgcf/past.py index 17d15b4c..0f594e47 100644 --- a/tgcf/past.py +++ b/tgcf/past.py @@ -5,30 +5,29 @@ from telethon.tl.patched import MessageService from telethon.errors.rpcerrorlist import FloodWaitError -from tgcf.config import Config +async def forward_job(): + from tgcf.config import CONFIG, API_ID, API_HASH, SESSION + async with TelegramClient(SESSION, API_ID, API_HASH) as client: -async def forward_job(client: TelegramClient, config: Config): - await client.start() - for forward in config.forwards: - last_id = 0 - print(forward.source) - print(forward.dest) - async for message in client.iter_messages(forward.source, - reverse=True, - offset_id=forward.offset): - if isinstance(message, MessageService): - continue - try: - for destination in forward.dest: - await client.send_message(destination, message) - last_id = str(message.id) - logging.info('forwarding message with id = %s', last_id) - forward.offset = last_id - except FloodWaitError as fwe: - print(f'Sleeping for {fwe}') - await asyncio.sleep(delay=fwe.seconds) - except Exception as err: - logging.exception(err) - break - await client.disconnect() + for forward in CONFIG.forwards: + last_id = 0 + print(forward.source) + print(forward.dest) + async for message in client.iter_messages(forward.source, + reverse=True, + offset_id=forward.offset): + if isinstance(message, MessageService): + continue + try: + for destination in forward.dest: + await client.send_message(destination, message) + last_id = str(message.id) + logging.info('forwarding message with id = %s', last_id) + forward.offset = last_id + except FloodWaitError as fwe: + print(f'Sleeping for {fwe}') + await asyncio.sleep(delay=fwe.seconds) + except Exception as err: + logging.exception(err) + break From 1c91b906b7795967866692658c51c42456f097c5 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:15:37 +0530 Subject: [PATCH 26/68] remove unnecessary code --- tgcf/main.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 tgcf/main.py diff --git a/tgcf/main.py b/tgcf/main.py deleted file mode 100644 index 8af92b73..00000000 --- a/tgcf/main.py +++ /dev/null @@ -1,17 +0,0 @@ -from telethon import TelegramClient -from telethon.sessions import StringSession -import asyncio -from tgcf.past import forward_job - - - - - -def start_past(): - asyncio.run(forward_job()) - - - -def start_live(): - print('live') - From cf69c9affb4a5bf11c9bef9fbdd869d6eecee628 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:15:51 +0530 Subject: [PATCH 27/68] past.py working basic as expected --- tgcf/past.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tgcf/past.py b/tgcf/past.py index 0f594e47..be2945cf 100644 --- a/tgcf/past.py +++ b/tgcf/past.py @@ -4,10 +4,10 @@ from telethon.tl.patched import MessageService from telethon.errors.rpcerrorlist import FloodWaitError - +from tgcf.config import CONFIG, API_ID, API_HASH, SESSION async def forward_job(): - from tgcf.config import CONFIG, API_ID, API_HASH, SESSION + async with TelegramClient(SESSION, API_ID, API_HASH) as client: for forward in CONFIG.forwards: From 12b399469b8a333485849b509d60b3bd4c1be277 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:16:03 +0530 Subject: [PATCH 28/68] live.py working basic as expected --- tgcf/live.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tgcf/live.py b/tgcf/live.py index e69de29b..0e2ed783 100644 --- a/tgcf/live.py +++ b/tgcf/live.py @@ -0,0 +1,112 @@ +from tgcf.config import CONFIG +from telethon import events, TelegramClient + +from tgcf.config import API_HASH, API_ID, SESSION +from_to = {} + +for forward in CONFIG.forwards: + from_to[forward.source] = forward.dest + + +class EventUid: + def __init__(self, event): + self.chat_id = event.chat_id + try: + self.msg_id = event.id + except: + self.msg_id = event.deleted_id + + def __str__(self): + return f'chat={self.chat_id} msg={self.msg_id}' + + def __eq__(self, other): + return self.chat_id == other.chat_id and self.msg_id == other.msg_id + + def __hash__(self) -> int: + return hash(self.__str__()) + + +KEEP_LAST_MANY = 10000 + +existing_hashes = [] + +_stored = {} + + +async def new_message_handler(event): + chat_id = event.chat_id + + if chat_id not in from_to: + return + + message = event.message + + global _stored + + event_uid = EventUid(event) + + length = len(_stored) + exceeding = length - KEEP_LAST_MANY + + if exceeding > 0: + for key in _stored: + del _stored[key] + break + + to_send_to = from_to.get(chat_id) + + if to_send_to: + if event_uid not in _stored: + _stored[event_uid] = [] + + for recipient in to_send_to: + fwded_msg = await event.client.send_message(recipient, message) + _stored[event_uid].append(fwded_msg) + + existing_hashes.append(hash(message.text)) + + +async def edited_message_handler(event): + message = event.message + + chat_id = event.chat_id + if chat_id in from_to: + + event_uid = EventUid(event) + + fwded_msgs = _stored.get(event_uid) + if fwded_msgs: + for msg in fwded_msgs: + await msg.edit(message.text) + return + else: + to_send_to = from_to.get(event.chat_id) + for recipient in to_send_to: + await event.client.send_message(recipient, message) + + +async def deleted_message_handler(event): + chat_id = event.chat_id + if chat_id in from_to: + event_uid = EventUid(event) + fwded_msgs = _stored.get(event_uid) + if fwded_msgs: + for msg in fwded_msgs: + await msg.delete() + return + + +ALL_EVENTS = { + 'new': (new_message_handler, events.NewMessage()), + 'edited': (edited_message_handler, events.MessageEdited()), + 'deleted': (deleted_message_handler, events.MessageDeleted()) +} + + +def start_sync(): + client = TelegramClient(SESSION, API_ID, API_HASH) + for key, val in ALL_EVENTS.items(): + print(f'Adding event {key}') + client.add_event_handler(*val) + client.start() + client.run_until_disconnected() From de33a928c1ce06d251fc16b8c58386f4dacaf562 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:16:12 +0530 Subject: [PATCH 29/68] a robust cli --- tgcf/cli.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tgcf/cli.py b/tgcf/cli.py index 4694eee6..7bc6fd95 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -9,8 +9,9 @@ from enum import Enum import typer from dotenv import load_dotenv -from tgcf.main import start_past, start_live import os +import asyncio + load_dotenv('.env') FAKE = bool(os.getenv('FAKE')) @@ -20,6 +21,8 @@ class Mode(str, Enum): past = 'past' live = 'live' + + def version_callback(value: bool): if value: print(__version__) @@ -36,7 +39,6 @@ def verbosity_callback(value: bool): logging.basicConfig(level=level) - @app.command() def main( mode: Mode = typer.Argument(..., @@ -66,9 +68,11 @@ def main( quit(1) if mode == mode.past: - start_past() + from tgcf.past import forward_job + asyncio.run(forward_job()) else: - start_live() + from tgcf.live import start_sync + start_sync() # AAHNIK 2021 From 555a2e0a4e094fa43081c6f8f159213d16e7c1aa Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:24:00 +0530 Subject: [PATCH 30/68] refactor and beautify code --- tgcf/cli.py | 15 +++++++++------ tgcf/config.py | 4 ++-- tgcf/live.py | 18 +++++++++--------- tgcf/past.py | 5 ++++- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/tgcf/cli.py b/tgcf/cli.py index 7bc6fd95..565ae175 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -2,15 +2,17 @@ using the modern and robust `typer`. ''' - -from tgcf import __version__ -from typing import Optional -import logging +import os +import asyncio from enum import Enum +import logging +from typing import Optional + import typer from dotenv import load_dotenv -import os -import asyncio + +from tgcf import __version__ + load_dotenv('.env') @@ -19,6 +21,7 @@ class Mode(str, Enum): + '''tgcf works in two modes''' past = 'past' live = 'live' diff --git a/tgcf/config.py b/tgcf/config.py index c85a27a3..b3e9e351 100644 --- a/tgcf/config.py +++ b/tgcf/config.py @@ -1,10 +1,10 @@ # a custom config parser +import os +from typing import List import yaml -from typing import List from pydantic import BaseModel -import os from telethon.sessions import StringSession CONFIG_FILE = 'tgcf.config.yml' diff --git a/tgcf/live.py b/tgcf/live.py index 0e2ed783..39460d2b 100644 --- a/tgcf/live.py +++ b/tgcf/live.py @@ -1,9 +1,16 @@ -from tgcf.config import CONFIG from telethon import events, TelegramClient +from tgcf.config import CONFIG, API_HASH, API_ID, SESSION + -from tgcf.config import API_HASH, API_ID, SESSION from_to = {} +KEEP_LAST_MANY = 10000 + +existing_hashes = [] + +_stored = {} + + for forward in CONFIG.forwards: from_to[forward.source] = forward.dest @@ -26,13 +33,6 @@ def __hash__(self) -> int: return hash(self.__str__()) -KEEP_LAST_MANY = 10000 - -existing_hashes = [] - -_stored = {} - - async def new_message_handler(event): chat_id = event.chat_id diff --git a/tgcf/past.py b/tgcf/past.py index be2945cf..98390a2c 100644 --- a/tgcf/past.py +++ b/tgcf/past.py @@ -1,12 +1,15 @@ -from telethon import TelegramClient import asyncio import logging +from telethon import TelegramClient from telethon.tl.patched import MessageService from telethon.errors.rpcerrorlist import FloodWaitError + from tgcf.config import CONFIG, API_ID, API_HASH, SESSION + async def forward_job(): + ''' The function that does the job of forwarding all existing messages in the concerned chats''' async with TelegramClient(SESSION, API_ID, API_HASH) as client: From 74d8b52f47a0f7910e77a94429c2f0e3495908e6 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:28:44 +0530 Subject: [PATCH 31/68] remove the docs folder --- docs/cli_usage.md | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 docs/cli_usage.md diff --git a/docs/cli_usage.md b/docs/cli_usage.md deleted file mode 100644 index 1a4866e5..00000000 --- a/docs/cli_usage.md +++ /dev/null @@ -1,20 +0,0 @@ -# `tgcf` - -tgcf is a powerful tool for forwarding telegram messages or live syncing. Make sure you have API_ID and API_HASH in your .env file inside the current directory. - - -**Usage**: - -```console -$ tgcf [OPTIONS] -``` - -**Options**: - -* `-n, --name TEXT`: Name of the bot/userbot you want to run. [env var: NAME; required] -* `-t, --token TEXT`: Bot Token or Session String [env var: TOKEN; required] -* `--API_ID INTEGER`: API ID obtained from my.telegram.org [env var: API_ID; required] -* `--API_HASH TEXT`: API HASH obtained from my.telegram.org [env var: API_HASH; required] -* `-l, --loud`: Increase output verbosity. [env var: LOUD] -* `-v, --version`: Show version and exit. -* `--help`: Show this message and exit. From 5964b6e4561b9e73a768df2b9eaa81c36ae98141 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:35:39 +0530 Subject: [PATCH 32/68] add a dockerfile --- Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a8bfa874 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.9 + +WORKDIR /app + +RUN apt-get update && apt-get upgrade -y + +RUN pip install tgcf + +CMD ["tgcf"] From d06aa6429871337af6f96f8954e48e4ddce5901d Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:37:07 +0530 Subject: [PATCH 33/68] read the mode from env var --- tgcf/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tgcf/cli.py b/tgcf/cli.py index 565ae175..3758ad26 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -45,7 +45,8 @@ def verbosity_callback(value: bool): @app.command() def main( mode: Mode = typer.Argument(..., - help='Choose the mode in which you want to run tgcf.'), + help='Choose the mode in which you want to run tgcf.', + envvar='TGCF_MODE'), verbose: Optional[bool] = typer.Option(None, '--loud', '-l', callback=verbosity_callback, From 30e44b6f9b670cc540eb435210495dbcd2e74b29 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:39:30 +0530 Subject: [PATCH 34/68] make the offset parameter optional --- tgcf/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tgcf/config.py b/tgcf/config.py index b3e9e351..e679a1f7 100644 --- a/tgcf/config.py +++ b/tgcf/config.py @@ -1,7 +1,7 @@ # a custom config parser import os -from typing import List +from typing import List, Optional import yaml from pydantic import BaseModel @@ -13,7 +13,7 @@ class Forward(BaseModel): source: int dest: List[int] - offset: int + offset: Optional[int] = 0 class Config(BaseModel): From 1b1b7f47a989453baeac950ff8e09dddb340ffb7 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:41:35 +0530 Subject: [PATCH 35/68] bump version and release to pypi --- pyproject.toml | 2 +- tgcf/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dfa4948e..f6b27aae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tgcf" -version = "0.1.1" +version = "0.1.5" description = "A simple script to forward all the messages of one chat (private/group/channel) to another. Made using Telethon. Can be used to back up the contents of a chat to another place.You can also start a live sync to forward all upcoming messages" authors = ["aahnik "] diff --git a/tgcf/__init__.py b/tgcf/__init__.py index 3a7d4d5f..dd990105 100644 --- a/tgcf/__init__.py +++ b/tgcf/__init__.py @@ -1,2 +1,2 @@ -__version__ = "0.1.1" +__version__ = "0.1.5" From f59d00c53575314df048f408e1b777f74ba01b32 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:43:37 +0530 Subject: [PATCH 36/68] update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f6b27aae..b2f22a0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "tgcf" version = "0.1.5" -description = "A simple script to forward all the messages of one chat (private/group/channel) to another. Made using Telethon. Can be used to back up the contents of a chat to another place.You can also start a live sync to forward all upcoming messages" +description = "Coming soon..." authors = ["aahnik "] [tool.poetry.dependencies] From 1cf895ae5040bdc58994c04b1810192abad0ae22 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sat, 3 Apr 2021 23:45:19 +0530 Subject: [PATCH 37/68] update --- pyproject.toml | 2 +- tgcf/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b2f22a0a..5d7e5667 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tgcf" -version = "0.1.5" +version = "0.1.6" description = "Coming soon..." authors = ["aahnik "] diff --git a/tgcf/__init__.py b/tgcf/__init__.py index dd990105..5bf5f4a0 100644 --- a/tgcf/__init__.py +++ b/tgcf/__init__.py @@ -1,2 +1,2 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From c8eaa76bf3488a720fa34bb62b29d5968c671cdf Mon Sep 17 00:00:00 2001 From: aahnik Date: Sun, 4 Apr 2021 12:02:13 +0530 Subject: [PATCH 38/68] add basic logging & optional show "forwarded from" --- tgcf/cli.py | 4 ++-- tgcf/config.py | 5 +++++ tgcf/live.py | 53 +++++++++++++++++++++++++++++--------------------- tgcf/past.py | 9 ++++----- tgcf/utils.py | 8 ++++++++ 5 files changed, 50 insertions(+), 29 deletions(-) create mode 100644 tgcf/utils.py diff --git a/tgcf/cli.py b/tgcf/cli.py index 3758ad26..9362a77f 100644 --- a/tgcf/cli.py +++ b/tgcf/cli.py @@ -34,12 +34,12 @@ def version_callback(value: bool): def verbosity_callback(value: bool): if value: - logging.info( - 'Verbosity turned on. \nThis is suitable for debugging.\n') level = logging.INFO else: level = logging.WARNING logging.basicConfig(level=level) + logging.info( + 'Verbosity turned on. \nThis is suitable for debugging.\n') @app.command() diff --git a/tgcf/config.py b/tgcf/config.py index e679a1f7..322f7439 100644 --- a/tgcf/config.py +++ b/tgcf/config.py @@ -1,5 +1,6 @@ # a custom config parser +import logging import os from typing import List, Optional @@ -18,6 +19,7 @@ class Forward(BaseModel): class Config(BaseModel): forwards: List[Forward] + show_forwarded_from: Optional[bool] = False def read_config(): @@ -29,6 +31,7 @@ def read_config(): print(err) quit(1) else: + logging.info(config) return config @@ -58,3 +61,5 @@ def env_var(name: str, optional=False): SESSION = 'tgcf' CONFIG = read_config() + +logging.info('config.py got executed') diff --git a/tgcf/live.py b/tgcf/live.py index 39460d2b..2d4f1606 100644 --- a/tgcf/live.py +++ b/tgcf/live.py @@ -1,6 +1,7 @@ +import logging from telethon import events, TelegramClient from tgcf.config import CONFIG, API_HASH, API_ID, SESSION - +from tgcf.utils import send_message from_to = {} @@ -38,7 +39,7 @@ async def new_message_handler(event): if chat_id not in from_to: return - + logging.info(f'New message received in {chat_id}') message = event.message global _stored @@ -60,7 +61,7 @@ async def new_message_handler(event): _stored[event_uid] = [] for recipient in to_send_to: - fwded_msg = await event.client.send_message(recipient, message) + fwded_msg = await send_message(event.client,recipient, message) _stored[event_uid].append(fwded_msg) existing_hashes.append(hash(message.text)) @@ -70,30 +71,38 @@ async def edited_message_handler(event): message = event.message chat_id = event.chat_id - if chat_id in from_to: + if chat_id not in from_to: + return - event_uid = EventUid(event) + logging.info(f'Message edited in {chat_id}') - fwded_msgs = _stored.get(event_uid) - if fwded_msgs: - for msg in fwded_msgs: - await msg.edit(message.text) - return - else: - to_send_to = from_to.get(event.chat_id) - for recipient in to_send_to: - await event.client.send_message(recipient, message) + event_uid = EventUid(event) + + fwded_msgs = _stored.get(event_uid) + if fwded_msgs: + for msg in fwded_msgs: + await msg.edit(message.text) + return + else: + to_send_to = from_to.get(event.chat_id) + for recipient in to_send_to: + await send_message(event.client, recipient, message) async def deleted_message_handler(event): chat_id = event.chat_id - if chat_id in from_to: - event_uid = EventUid(event) - fwded_msgs = _stored.get(event_uid) - if fwded_msgs: - for msg in fwded_msgs: - await msg.delete() - return + if chat_id not in from_to: + return + + logging.info(f'Message deleted in {chat_id}') + + + event_uid = EventUid(event) + fwded_msgs = _stored.get(event_uid) + if fwded_msgs: + for msg in fwded_msgs: + await msg.delete() + return ALL_EVENTS = { @@ -106,7 +115,7 @@ async def deleted_message_handler(event): def start_sync(): client = TelegramClient(SESSION, API_ID, API_HASH) for key, val in ALL_EVENTS.items(): - print(f'Adding event {key}') + logging.info(f'Added event handler for {key}') client.add_event_handler(*val) client.start() client.run_until_disconnected() diff --git a/tgcf/past.py b/tgcf/past.py index 98390a2c..eca57c38 100644 --- a/tgcf/past.py +++ b/tgcf/past.py @@ -6,7 +6,7 @@ from telethon.errors.rpcerrorlist import FloodWaitError from tgcf.config import CONFIG, API_ID, API_HASH, SESSION - +from tgcf.utils import send_message async def forward_job(): ''' The function that does the job of forwarding all existing messages in the concerned chats''' @@ -15,8 +15,7 @@ async def forward_job(): for forward in CONFIG.forwards: last_id = 0 - print(forward.source) - print(forward.dest) + logging.info(f'Forwarding messages from {forward.source} to {forward.dest}') async for message in client.iter_messages(forward.source, reverse=True, offset_id=forward.offset): @@ -24,9 +23,9 @@ async def forward_job(): continue try: for destination in forward.dest: - await client.send_message(destination, message) + await send_message(client,destination, message) last_id = str(message.id) - logging.info('forwarding message with id = %s', last_id) + logging.info(f'forwarding message with id = {last_id}') forward.offset = last_id except FloodWaitError as fwe: print(f'Sleeping for {fwe}') diff --git a/tgcf/utils.py b/tgcf/utils.py new file mode 100644 index 00000000..c8750e7c --- /dev/null +++ b/tgcf/utils.py @@ -0,0 +1,8 @@ +from tgcf.config import CONFIG + +async def send_message(client,*args): + # show forwarded from + if CONFIG.show_forwarded_from: + return await client.forward_messages(*args) + else: + return await client.send_message(*args) From 1121d1ac57235e1ad8f46de0a71f700569653e48 Mon Sep 17 00:00:00 2001 From: aahnik Date: Sun, 4 Apr 2021 12:58:48 +0530 Subject: [PATCH 39/68] add extensions list safety --- tgcf/config.py | 14 ++++++++++++-- tgcf/extensions/__init__.py | 14 ++++++++++++++ tgcf/extensions/filters.py | 14 ++++++++++++++ tgcf/live.py | 9 ++++++--- tgcf/past.py | 8 +++++++- 5 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 tgcf/extensions/filters.py diff --git a/tgcf/config.py b/tgcf/config.py index 322f7439..a6045256 100644 --- a/tgcf/config.py +++ b/tgcf/config.py @@ -2,7 +2,8 @@ import logging import os -from typing import List, Optional +from typing import Dict, List, Optional +from enum import Enum import yaml from pydantic import BaseModel @@ -16,11 +17,20 @@ class Forward(BaseModel): dest: List[int] offset: Optional[int] = 0 +class TextFormat(str,Enum): + bold = 'bold' + italics = 'italic' + strike = 'strike' + code = 'code' + class Config(BaseModel): forwards: List[Forward] show_forwarded_from: Optional[bool] = False - + blacklist: Optional[List] = [] + whitelist: Optional[List] = [] + replace: Optional[Dict] = {} + text_format: Optional[TextFormat] = None def read_config(): with open(CONFIG_FILE) as file: diff --git a/tgcf/extensions/__init__.py b/tgcf/extensions/__init__.py index e69de29b..72c4451a 100644 --- a/tgcf/extensions/__init__.py +++ b/tgcf/extensions/__init__.py @@ -0,0 +1,14 @@ +from tgcf.config import CONFIG +from tgcf.extensions.filters import list_safe + +def extended(message): + if CONFIG.blacklist or CONFIG.whitelist: + if not list_safe(message): + return + if CONFIG.replace: + pass + if CONFIG.text_format: + pass + return message + + diff --git a/tgcf/extensions/filters.py b/tgcf/extensions/filters.py new file mode 100644 index 00000000..1c81dd91 --- /dev/null +++ b/tgcf/extensions/filters.py @@ -0,0 +1,14 @@ +from tgcf.config import CONFIG +import logging + +def list_safe(message) -> bool: + logging.info('Making list safe') + for forbidden in CONFIG.blacklist: + if forbidden in message.raw_text: + return False + if not CONFIG.whitelist: + return True + for allowed in CONFIG.whitelist: + if allowed in message.raw_text: + return True + return False diff --git a/tgcf/live.py b/tgcf/live.py index 2d4f1606..70a63ad3 100644 --- a/tgcf/live.py +++ b/tgcf/live.py @@ -2,7 +2,7 @@ from telethon import events, TelegramClient from tgcf.config import CONFIG, API_HASH, API_ID, SESSION from tgcf.utils import send_message - +from tgcf.extensions import extended from_to = {} KEEP_LAST_MANY = 10000 @@ -60,8 +60,11 @@ async def new_message_handler(event): if event_uid not in _stored: _stored[event_uid] = [] + modified_message = extended(message) + if not modified_message: + return for recipient in to_send_to: - fwded_msg = await send_message(event.client,recipient, message) + fwded_msg = await send_message(event.client,recipient, modified_message) _stored[event_uid].append(fwded_msg) existing_hashes.append(hash(message.text)) @@ -86,7 +89,7 @@ async def edited_message_handler(event): else: to_send_to = from_to.get(event.chat_id) for recipient in to_send_to: - await send_message(event.client, recipient, message) + await send_message(event.client, recipient, extended(message)) async def deleted_message_handler(event): diff --git a/tgcf/past.py b/tgcf/past.py index eca57c38..3edb04f0 100644 --- a/tgcf/past.py +++ b/tgcf/past.py @@ -7,6 +7,8 @@ from tgcf.config import CONFIG, API_ID, API_HASH, SESSION from tgcf.utils import send_message +from tgcf.extensions import extended + async def forward_job(): ''' The function that does the job of forwarding all existing messages in the concerned chats''' @@ -22,8 +24,12 @@ async def forward_job(): if isinstance(message, MessageService): continue try: + modified_message = extended(message) + if not modified_message: + continue + for destination in forward.dest: - await send_message(client,destination, message) + await send_message(client,destination, modified_message) last_id = str(message.id) logging.info(f'forwarding message with id = {last_id}') forward.offset = last_id From 8032210b88bf587f1670cf1e3088d57c7c9da55b Mon Sep 17 00:00:00 2001 From: aahnik Date: Sun, 4 Apr 2021 22:01:39 +0530 Subject: [PATCH 40/68] update --- tgcf/extensions/replace.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tgcf/extensions/replace.py diff --git a/tgcf/extensions/replace.py b/tgcf/extensions/replace.py new file mode 100644 index 00000000..e69de29b From 8959397c39a54db03f260f8c31cc3ae00afcbd1e Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:55:56 +0530 Subject: [PATCH 41/68] =?UTF-8?q?=E2=9C=A8=20commit=20vscode=20settings=20?= =?UTF-8?q?to=20source=20control?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8bca976d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.pythonPath": ".venv/bin/python", + "python.linting.pylintEnabled": true, + "python.formatting.provider": "black" +} \ No newline at end of file From c3b31cff4047cf2eee2d9f86708153941cc9006a Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:56:23 +0530 Subject: [PATCH 42/68] =?UTF-8?q?=F0=9F=94=A5=20remove=20.vscode=20from=20?= =?UTF-8?q?gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ba0d72e..afe7e633 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ *.session -.vscode *.session-journal t.py tgcf.config.yml From 786004f8f949a8b22c405b3f97647c7dc00f98a7 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:57:02 +0530 Subject: [PATCH 43/68] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20add=20pylintrc=20fil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pylintrc | 608 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 608 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..bc43ec8b --- /dev/null +++ b/.pylintrc @@ -0,0 +1,608 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + logging-fstring-interpolation + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=colorized + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=new + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception \ No newline at end of file From 66a8c2a23fb8925ce34bb0c26ca5df5509062f86 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:57:31 +0530 Subject: [PATCH 44/68] =?UTF-8?q?=E2=9C=A8=20update=20Dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a8bfa874..ea06458d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,14 @@ WORKDIR /app RUN apt-get update && apt-get upgrade -y -RUN pip install tgcf +RUN pip install --upgrade pip && pip install poetry -CMD ["tgcf"] +COPY README.md LICENSE pyproject.toml poetry.lock ./ + +COPY src ./src + +COPY tests ./tests + +RUN poetry install + +CMD ["poetry","run","tgcf"] From ecaed4f576869f921928413552503a236208d64c Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:58:07 +0530 Subject: [PATCH 45/68] =?UTF-8?q?=F0=9F=93=9D=20add=20mkdocs.yml=20for=20d?= =?UTF-8?q?ocumentation=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mkdocs.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 mkdocs.yml diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..bfbff8b2 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,16 @@ +site_name: tgcf + +nav: + - Home: index.md + - getting_started.md + +repo_url: https://github.com/aahnik/tgcf + +theme: + name: readthedocs + +plugins: + - search + - gen-files: + scripts: + - docs/generate.py \ No newline at end of file From 9dd63175c8397bcc6f6d7eedadb6ac7ff47760b1 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:58:24 +0530 Subject: [PATCH 46/68] =?UTF-8?q?=F0=9F=93=9D=20add=20basic=20docs=20folde?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generate.py | 10 ++++++++++ docs/gettings_started.md | 0 2 files changed, 10 insertions(+) create mode 100644 docs/generate.py create mode 100644 docs/gettings_started.md diff --git a/docs/generate.py b/docs/generate.py new file mode 100644 index 00000000..a53d8b85 --- /dev/null +++ b/docs/generate.py @@ -0,0 +1,10 @@ +""" Generator for virtual files""" + +import mkdocs_gen_files + +with open("README.md") as file: + readme = file.read() + +with mkdocs_gen_files.open("index.md", "w") as f: + print(readme, file=f) + \ No newline at end of file diff --git a/docs/gettings_started.md b/docs/gettings_started.md new file mode 100644 index 00000000..e69de29b From 54f9d5ad2a681bfda39a3cf5b1fb95c8b3e59f74 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:59:17 +0530 Subject: [PATCH 47/68] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20how=20cod?= =?UTF-8?q?e=20is=20structured?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {tgcf => src/tgcf}/__init__.py | 1 - src/tgcf/cli.py | 87 ++++++++++++++++++++++++++++++++++ {tgcf => src/tgcf}/config.py | 31 ++++++------ {tgcf => src/tgcf}/live.py | 22 ++++----- {tgcf => src/tgcf}/past.py | 18 +++---- src/tgcf/plugins.py | 8 ++++ {tgcf => src/tgcf}/utils.py | 3 +- tgcf/cli.py | 82 -------------------------------- tgcf/extensions/__init__.py | 14 ------ tgcf/extensions/filters.py | 14 ------ tgcf/extensions/replace.py | 0 11 files changed, 134 insertions(+), 146 deletions(-) rename {tgcf => src/tgcf}/__init__.py (95%) create mode 100644 src/tgcf/cli.py rename {tgcf => src/tgcf}/config.py (71%) rename {tgcf => src/tgcf}/live.py (80%) rename {tgcf => src/tgcf}/past.py (61%) create mode 100644 src/tgcf/plugins.py rename {tgcf => src/tgcf}/utils.py (83%) delete mode 100644 tgcf/cli.py delete mode 100644 tgcf/extensions/__init__.py delete mode 100644 tgcf/extensions/filters.py delete mode 100644 tgcf/extensions/replace.py diff --git a/tgcf/__init__.py b/src/tgcf/__init__.py similarity index 95% rename from tgcf/__init__.py rename to src/tgcf/__init__.py index 5bf5f4a0..0a8da882 100644 --- a/tgcf/__init__.py +++ b/src/tgcf/__init__.py @@ -1,2 +1 @@ __version__ = "0.1.6" - diff --git a/src/tgcf/cli.py b/src/tgcf/cli.py new file mode 100644 index 00000000..e449f904 --- /dev/null +++ b/src/tgcf/cli.py @@ -0,0 +1,87 @@ +""" This module implements the command line interface for tgcf, +using the modern and robust `typer`. +""" + +import os +import asyncio +from enum import Enum +import logging +from typing import Optional + +import typer +from dotenv import load_dotenv + +from tgcf import __version__ + + +load_dotenv(".env") + +FAKE = bool(os.getenv("FAKE")) +app = typer.Typer(add_completion=False) + + +class Mode(str, Enum): + """tgcf works in two modes""" + + past = "past" + live = "live" + + +def version_callback(value: bool): + if value: + print(__version__) + raise typer.Exit() + + +def verbosity_callback(value: bool): + if value: + level = logging.INFO + else: + level = logging.WARNING + logging.basicConfig(level=level) + logging.info("Verbosity turned on. \nThis is suitable for debugging.\n") + + +@app.command() +def main( + mode: Mode = typer.Argument( + ..., help="Choose the mode in which you want to run tgcf.", envvar="TGCF_MODE" + ), + verbose: Optional[bool] = typer.Option( + None, + "--loud", + "-l", + callback=verbosity_callback, + envvar="LOUD", + help="Increase output verbosity.", + ), + version: Optional[bool] = typer.Option( + None, + "--version", + "-v", + callback=version_callback, + help="Show version and exit.", + ), +): + """tgcf is a powerful tool for forwarding telegram messages from source to destination. + + Don't forget to star https://github.com/aahnik/tgcf + + Telegram Channel https://telegram.me/tg_cf + """ + + if FAKE: + print(f"mode = {mode}") + quit(1) + + if mode == mode.past: + from tgcf.past import forward_job + + asyncio.run(forward_job()) + else: + from tgcf.live import start_sync + + start_sync() + + +# AAHNIK 2021 diff --git a/tgcf/config.py b/src/tgcf/config.py similarity index 71% rename from tgcf/config.py rename to src/tgcf/config.py index a6045256..a26c63d8 100644 --- a/tgcf/config.py +++ b/src/tgcf/config.py @@ -9,7 +9,7 @@ from pydantic import BaseModel from telethon.sessions import StringSession -CONFIG_FILE = 'tgcf.config.yml' +CONFIG_FILE = "tgcf.config.yml" class Forward(BaseModel): @@ -17,11 +17,12 @@ class Forward(BaseModel): dest: List[int] offset: Optional[int] = 0 -class TextFormat(str,Enum): - bold = 'bold' - italics = 'italic' - strike = 'strike' - code = 'code' + +class TextFormat(str, Enum): + bold = "bold" + italics = "italic" + strike = "strike" + code = "code" class Config(BaseModel): @@ -31,6 +32,8 @@ class Config(BaseModel): whitelist: Optional[List] = [] replace: Optional[Dict] = {} text_format: Optional[TextFormat] = None + plugins: Optional[Dict] + def read_config(): with open(CONFIG_FILE) as file: @@ -46,7 +49,7 @@ def read_config(): def update_config(config: Config): - with open(CONFIG_FILE, 'w') as file: + with open(CONFIG_FILE, "w") as file: yaml.dump(config.dict(), file) @@ -56,20 +59,20 @@ def env_var(name: str, optional=False): while not var: if optional: break - var = input(f'Enter {name}: ') + var = input(f"Enter {name}: ") return var -API_ID = env_var('API_ID') -API_HASH = env_var('API_HASH') -USERNAME = env_var('USERNAME', optional=True) -SESSION_STRING = env_var('SESSION_STRING', optional=True) +API_ID = env_var("API_ID") +API_HASH = env_var("API_HASH") +USERNAME = env_var("USERNAME", optional=True) +SESSION_STRING = env_var("SESSION_STRING", optional=True) if SESSION_STRING: SESSION = StringSession(SESSION_STRING) else: - SESSION = 'tgcf' + SESSION = "tgcf" CONFIG = read_config() -logging.info('config.py got executed') +logging.info("config.py got executed") diff --git a/tgcf/live.py b/src/tgcf/live.py similarity index 80% rename from tgcf/live.py rename to src/tgcf/live.py index 70a63ad3..459769d1 100644 --- a/tgcf/live.py +++ b/src/tgcf/live.py @@ -2,7 +2,8 @@ from telethon import events, TelegramClient from tgcf.config import CONFIG, API_HASH, API_ID, SESSION from tgcf.utils import send_message -from tgcf.extensions import extended +from tgcf.plugins import extended + from_to = {} KEEP_LAST_MANY = 10000 @@ -25,7 +26,7 @@ def __init__(self, event): self.msg_id = event.deleted_id def __str__(self): - return f'chat={self.chat_id} msg={self.msg_id}' + return f"chat={self.chat_id} msg={self.msg_id}" def __eq__(self, other): return self.chat_id == other.chat_id and self.msg_id == other.msg_id @@ -39,7 +40,7 @@ async def new_message_handler(event): if chat_id not in from_to: return - logging.info(f'New message received in {chat_id}') + logging.info(f"New message received in {chat_id}") message = event.message global _stored @@ -64,7 +65,7 @@ async def new_message_handler(event): if not modified_message: return for recipient in to_send_to: - fwded_msg = await send_message(event.client,recipient, modified_message) + fwded_msg = await send_message(event.client, recipient, modified_message) _stored[event_uid].append(fwded_msg) existing_hashes.append(hash(message.text)) @@ -77,7 +78,7 @@ async def edited_message_handler(event): if chat_id not in from_to: return - logging.info(f'Message edited in {chat_id}') + logging.info(f"Message edited in {chat_id}") event_uid = EventUid(event) @@ -97,8 +98,7 @@ async def deleted_message_handler(event): if chat_id not in from_to: return - logging.info(f'Message deleted in {chat_id}') - + logging.info(f"Message deleted in {chat_id}") event_uid = EventUid(event) fwded_msgs = _stored.get(event_uid) @@ -109,16 +109,16 @@ async def deleted_message_handler(event): ALL_EVENTS = { - 'new': (new_message_handler, events.NewMessage()), - 'edited': (edited_message_handler, events.MessageEdited()), - 'deleted': (deleted_message_handler, events.MessageDeleted()) + "new": (new_message_handler, events.NewMessage()), + "edited": (edited_message_handler, events.MessageEdited()), + "deleted": (deleted_message_handler, events.MessageDeleted()), } def start_sync(): client = TelegramClient(SESSION, API_ID, API_HASH) for key, val in ALL_EVENTS.items(): - logging.info(f'Added event handler for {key}') + logging.info(f"Added event handler for {key}") client.add_event_handler(*val) client.start() client.run_until_disconnected() diff --git a/tgcf/past.py b/src/tgcf/past.py similarity index 61% rename from tgcf/past.py rename to src/tgcf/past.py index 3edb04f0..6f4d8f7f 100644 --- a/tgcf/past.py +++ b/src/tgcf/past.py @@ -7,20 +7,20 @@ from tgcf.config import CONFIG, API_ID, API_HASH, SESSION from tgcf.utils import send_message -from tgcf.extensions import extended +from tgcf.plugins import extended async def forward_job(): - ''' The function that does the job of forwarding all existing messages in the concerned chats''' + """ The function that does the job of forwarding all existing messages in the concerned chats""" async with TelegramClient(SESSION, API_ID, API_HASH) as client: for forward in CONFIG.forwards: last_id = 0 - logging.info(f'Forwarding messages from {forward.source} to {forward.dest}') - async for message in client.iter_messages(forward.source, - reverse=True, - offset_id=forward.offset): + logging.info(f"Forwarding messages from {forward.source} to {forward.dest}") + async for message in client.iter_messages( + forward.source, reverse=True, offset_id=forward.offset + ): if isinstance(message, MessageService): continue try: @@ -29,12 +29,12 @@ async def forward_job(): continue for destination in forward.dest: - await send_message(client,destination, modified_message) + await send_message(client, destination, modified_message) last_id = str(message.id) - logging.info(f'forwarding message with id = {last_id}') + logging.info(f"forwarding message with id = {last_id}") forward.offset = last_id except FloodWaitError as fwe: - print(f'Sleeping for {fwe}') + print(f"Sleeping for {fwe}") await asyncio.sleep(delay=fwe.seconds) except Exception as err: logging.exception(err) diff --git a/src/tgcf/plugins.py b/src/tgcf/plugins.py new file mode 100644 index 00000000..91a7e7c6 --- /dev/null +++ b/src/tgcf/plugins.py @@ -0,0 +1,8 @@ +from tgcf.config import CONFIG +from importlib import import_module +PLUGINS = CONFIG.plugins + +def load_plugins(): + for plugin_name in PLUGINS: + plugin = import_module(plugin_name) + diff --git a/tgcf/utils.py b/src/tgcf/utils.py similarity index 83% rename from tgcf/utils.py rename to src/tgcf/utils.py index c8750e7c..327a67e9 100644 --- a/tgcf/utils.py +++ b/src/tgcf/utils.py @@ -1,6 +1,7 @@ from tgcf.config import CONFIG -async def send_message(client,*args): + +async def send_message(client, *args): # show forwarded from if CONFIG.show_forwarded_from: return await client.forward_messages(*args) diff --git a/tgcf/cli.py b/tgcf/cli.py deleted file mode 100644 index 9362a77f..00000000 --- a/tgcf/cli.py +++ /dev/null @@ -1,82 +0,0 @@ -''' This module implements the command line interface for tgcf, -using the modern and robust `typer`. -''' - -import os -import asyncio -from enum import Enum -import logging -from typing import Optional - -import typer -from dotenv import load_dotenv - -from tgcf import __version__ - - -load_dotenv('.env') - -FAKE = bool(os.getenv('FAKE')) -app = typer.Typer(add_completion=False) - - -class Mode(str, Enum): - '''tgcf works in two modes''' - past = 'past' - live = 'live' - - -def version_callback(value: bool): - if value: - print(__version__) - raise typer.Exit() - - -def verbosity_callback(value: bool): - if value: - level = logging.INFO - else: - level = logging.WARNING - logging.basicConfig(level=level) - logging.info( - 'Verbosity turned on. \nThis is suitable for debugging.\n') - - -@app.command() -def main( - mode: Mode = typer.Argument(..., - help='Choose the mode in which you want to run tgcf.', - envvar='TGCF_MODE'), - verbose: Optional[bool] = typer.Option(None, - '--loud', '-l', - callback=verbosity_callback, - envvar='LOUD', - help='Increase output verbosity.'), - - version: Optional[bool] = typer.Option(None, - '--version', - '-v', - callback=version_callback, - help='Show version and exit.') - -): - ''' tgcf is a powerful tool for forwarding telegram messages from source to destination. - - Don't forget to star https://github.com/aahnik/tgcf - - Telegram Channel https://telegram.me/tg_cf - ''' - - if FAKE: - print(f'mode = {mode}') - quit(1) - - if mode == mode.past: - from tgcf.past import forward_job - asyncio.run(forward_job()) - else: - from tgcf.live import start_sync - start_sync() - - -# AAHNIK 2021 diff --git a/tgcf/extensions/__init__.py b/tgcf/extensions/__init__.py deleted file mode 100644 index 72c4451a..00000000 --- a/tgcf/extensions/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from tgcf.config import CONFIG -from tgcf.extensions.filters import list_safe - -def extended(message): - if CONFIG.blacklist or CONFIG.whitelist: - if not list_safe(message): - return - if CONFIG.replace: - pass - if CONFIG.text_format: - pass - return message - - diff --git a/tgcf/extensions/filters.py b/tgcf/extensions/filters.py deleted file mode 100644 index 1c81dd91..00000000 --- a/tgcf/extensions/filters.py +++ /dev/null @@ -1,14 +0,0 @@ -from tgcf.config import CONFIG -import logging - -def list_safe(message) -> bool: - logging.info('Making list safe') - for forbidden in CONFIG.blacklist: - if forbidden in message.raw_text: - return False - if not CONFIG.whitelist: - return True - for allowed in CONFIG.whitelist: - if allowed in message.raw_text: - return True - return False diff --git a/tgcf/extensions/replace.py b/tgcf/extensions/replace.py deleted file mode 100644 index e69de29b..00000000 From e76639573b1d568d09aabbcf2a8d320efc3facd4 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 08:59:49 +0530 Subject: [PATCH 48/68] =?UTF-8?q?=E2=9E=95=20add=20dependancies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 449 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 + 2 files changed, 450 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 7c7fc7eb..e0bcf12a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,6 +17,14 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotlipy", "cchardet"] +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "astroid" version = "2.5.2" @@ -71,6 +79,28 @@ python-versions = "*" pycodestyle = ">=2.6.0" toml = "*" +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + [[package]] name = "certifi" version = "2020.12.5" @@ -126,6 +156,14 @@ python-versions = ">=3.3" cffi = ">=1.0.0" pycparser = "*" +[[package]] +name = "future" +version = "0.18.2" +description = "Clean single-source support for Python 3 and 2" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "hachoir" version = "3.1.2" @@ -173,6 +211,28 @@ pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +[[package]] +name = "jinja2" +version = "2.11.3" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +name = "joblib" +version = "1.0.1" +description = "Lightweight pipelining with Python functions" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "lazy-object-proxy" version = "1.6.0" @@ -181,6 +241,53 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +[[package]] +name = "livereload" +version = "2.6.3" +description = "Python LiveReload is an awesome tool for web developers" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" +tornado = {version = "*", markers = "python_version > \"2.7\""} + +[[package]] +name = "lunr" +version = "0.5.8" +description = "A Python implementation of Lunr.js" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +future = ">=0.16.0" +nltk = {version = ">=3.2.5", optional = true, markers = "python_version > \"2.7\" and extra == \"languages\""} +six = ">=1.11.0" + +[package.extras] +languages = ["nltk (>=3.2.5,<3.5)", "nltk (>=3.2.5)"] + +[[package]] +name = "markdown" +version = "3.3.4" +description = "Python implementation of Markdown." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "1.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" + [[package]] name = "mccabe" version = "0.6.1" @@ -189,6 +296,34 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "mkdocs" +version = "1.1.2" +description = "Project documentation with Markdown." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +click = ">=3.3" +Jinja2 = ">=2.10.1" +livereload = ">=2.5.1" +lunr = {version = "0.5.8", extras = ["languages"]} +Markdown = ">=3.2.1" +PyYAML = ">=3.10" +tornado = ">=5.0" + +[[package]] +name = "mkdocs-gen-files" +version = "0.3.1" +description = "MkDocs plugin to programmatically generate documentation pages during the build" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +mkdocs = ">=1.0,<2.0" + [[package]] name = "more-itertools" version = "8.7.0" @@ -205,6 +340,36 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nltk" +version = "3.6.1" +description = "Natural Language Toolkit" +category = "dev" +optional = false +python-versions = ">=3.5.*" + +[package.dependencies] +click = "*" +joblib = "*" +regex = "*" +tqdm = "*" + +[package.extras] +all = ["requests", "pyparsing", "numpy", "twython", "gensim (<4.0.0)", "scikit-learn", "scipy", "python-crfsuite", "matplotlib"] +corenlp = ["requests"] +machine_learning = ["gensim (<4.0.0)", "numpy", "python-crfsuite", "scikit-learn", "scipy"] +plot = ["matplotlib"] +tgrep = ["pyparsing"] +twitter = ["twython"] + [[package]] name = "packaging" version = "20.9" @@ -216,6 +381,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "pillow" version = "8.1.2" @@ -357,6 +530,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +[[package]] +name = "regex" +version = "2021.4.4" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "requests" version = "2.25.1" @@ -394,6 +575,14 @@ category = "dev" optional = false python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "telethon" version = "1.20" @@ -417,6 +606,35 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tornado" +version = "6.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "dev" +optional = false +python-versions = ">= 3.5" + +[[package]] +name = "tqdm" +version = "4.60.0" +description = "Fast, Extensible Progress Meter" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +telegram = ["requests"] + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "typer" version = "0.3.2" @@ -512,7 +730,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "4b96e1470e78b618c3c9e61c761a8081face31f3e357f64e76011467cd708782" +content-hash = "0067d2442999e3383aad755c6c990cfaf1f99fe030672e192615351b5dbdd328" [metadata.files] aiohttp = [ @@ -554,6 +772,10 @@ aiohttp = [ {file = "aiohttp-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e"}, {file = "aiohttp-3.7.4.tar.gz", hash = "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de"}, ] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] astroid = [ {file = "astroid-2.5.2-py3-none-any.whl", hash = "sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df"}, {file = "astroid-2.5.2.tar.gz", hash = "sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9"}, @@ -574,6 +796,9 @@ autopep8 = [ {file = "autopep8-1.5.5-py2.py3-none-any.whl", hash = "sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea"}, {file = "autopep8-1.5.5.tar.gz", hash = "sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"}, ] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, @@ -671,6 +896,9 @@ cryptg = [ {file = "cryptg-0.2.post2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:a4c42f69a151b62393a4c09c9ffd856db094a747d423161816fb3fcfb913696e"}, {file = "cryptg-0.2.post2-pp37-pypy37_pp73-win32.whl", hash = "sha256:e9a585f2469376abd22f5473d4be21a5b28f270799887c6a345cf2df21faaf17"}, ] +future = [ + {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, +] hachoir = [ {file = "hachoir-3.1.2-py3-none-any.whl", hash = "sha256:b17ba5907b7836b2204ef724e7992d2e794311596e2121098912a9f3c4e69273"}, {file = "hachoir-3.1.2.tar.gz", hash = "sha256:bc1259b1e2970532b2dbd99139cb0de59d9bb8904eb1489c3e8a82c979c98f23"}, @@ -687,6 +915,14 @@ isort = [ {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] +jinja2 = [ + {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, + {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, +] +joblib = [ + {file = "joblib-1.0.1-py3-none-any.whl", hash = "sha256:feeb1ec69c4d45129954f1b7034954241eedfd6ba39b5e9e4b6883be3332d5e5"}, + {file = "joblib-1.0.1.tar.gz", hash = "sha256:9c17567692206d2f3fb9ecf5e991084254fe631665c450b443761c4186a613f7"}, +] lazy-object-proxy = [ {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, @@ -711,10 +947,83 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, ] +livereload = [ + {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, +] +lunr = [ + {file = "lunr-0.5.8-py2.py3-none-any.whl", hash = "sha256:aab3f489c4d4fab4c1294a257a30fec397db56f0a50273218ccc3efdbf01d6ca"}, + {file = "lunr-0.5.8.tar.gz", hash = "sha256:c4fb063b98eff775dd638b3df380008ae85e6cb1d1a24d1cd81a10ef6391c26e"}, +] +markdown = [ + {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, + {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +mkdocs = [ + {file = "mkdocs-1.1.2-py3-none-any.whl", hash = "sha256:096f52ff52c02c7e90332d2e53da862fde5c062086e1b5356a6e392d5d60f5e9"}, + {file = "mkdocs-1.1.2.tar.gz", hash = "sha256:f0b61e5402b99d7789efa032c7a74c90a20220a9c81749da06dbfbcbd52ffb39"}, +] +mkdocs-gen-files = [ + {file = "mkdocs-gen-files-0.3.1.tar.gz", hash = "sha256:1b90654a6e9d7fa7bca7c4f80c0dbdce294ce4c4d63a26a384366f1dda4d29d1"}, + {file = "mkdocs_gen_files-0.3.1-py3-none-any.whl", hash = "sha256:68131f7d5aae374be93a24f546c2d7a56e34f55413001a2d9b10682ac1900810"}, +] more-itertools = [ {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, @@ -758,10 +1067,22 @@ multidict = [ {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, ] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nltk = [ + {file = "nltk-3.6.1-py3-none-any.whl", hash = "sha256:1235660f52ab10fda34d5277096724747f767b2903e1c0c4e14bde013552c9ba"}, + {file = "nltk-3.6.1.zip", hash = "sha256:cbc2ed576998fcf7cd181eeb3ca029e5f0025b264074b4beb57ce780673f8b86"}, +] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] pillow = [ {file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"}, {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5"}, @@ -894,6 +1215,49 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] +regex = [ + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, +] requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, @@ -906,6 +1270,10 @@ shellingham = [ {file = "shellingham-1.4.0-py2.py3-none-any.whl", hash = "sha256:536b67a0697f2e4af32ab176c00a50ac2899c5a05e0d8e2dadac8e58888283f9"}, {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, ] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] telethon = [ {file = "Telethon-1.20-py3-none-any.whl", hash = "sha256:3f9eec75a6bcf6c42f20d08bb0f4908c87a15c8d8797bbb737fd27834c3a8777"}, {file = "Telethon-1.20.tar.gz", hash = "sha256:41fea16755897c91a2d1a90b4c6d178dad06746650230c3adf5d7fb2e285f1d3"}, @@ -914,6 +1282,85 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tornado = [ + {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"}, + {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"}, + {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"}, + {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"}, + {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"}, + {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"}, + {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"}, + {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"}, + {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"}, + {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"}, + {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"}, + {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"}, + {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"}, + {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"}, + {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"}, + {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"}, + {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, + {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, +] +tqdm = [ + {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, + {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] typer = [ {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, diff --git a/pyproject.toml b/pyproject.toml index 5d7e5667..0af73c7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,8 @@ pytest = "^5.2" autopep8 = "^1.5.5" pylint = "^2.7.4" typer-cli = "^0.0.11" +black = {version = "^20.8b1", allow-prereleases = true} +mkdocs-gen-files = "^0.3.1" [tool.poetry.scripts] From c37b2998fe537706494970a972be8ad04ee93fc2 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 09:10:33 +0530 Subject: [PATCH 49/68] =?UTF-8?q?=E2=9C=A8=20add=20a=20Makefile=20to=20eas?= =?UTF-8?q?ily=20run=20different=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..25e01171 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +# lists all available targets +list: + @sh -c "$(MAKE) -p no_targets__ | \ + awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {\ + split(\$$1,A,/ /);for(i in A)print A[i]\ + }' | grep -v '__\$$' | grep -v 'make\[1\]' | grep -v 'Makefile' | sort" + +# required for list +no_targets__: + +clean: + @rm -rf build dist .eggs *.egg-info + @rm -rf .benchmarks .coverage coverage.xml htmlcov report.xml .tox + @find . -type d -name '.mypy_cache' -exec rm -rf {} + + @find . -type d -name '__pycache__' -exec rm -rf {} + + @find . -type d -name '*pytest_cache*' -exec rm -rf {} + + @find . -type f -name "*.py[co]" -exec rm -rf {} + + +format: clean + @poetry run isort src/ tests/ + @poetry run black src/ tests/ + +hard-clean: clean + rm -rf .venv site + +mkdocs: + @poetry run mkdocs build + @poetry run mkdocs serve + +release-docs: + @poetry run mkdocs gh-deploy From f2ccbd3458fdc8beb6a09337783ff8de0be1fda6 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 09:12:34 +0530 Subject: [PATCH 50/68] =?UTF-8?q?=F0=9F=8E=A8=20sort=20imports=20with=20is?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgcf/cli.py | 5 ++--- src/tgcf/config.py | 2 +- src/tgcf/live.py | 8 +++++--- src/tgcf/past.py | 6 +++--- src/tgcf/plugins.py | 4 +++- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/tgcf/cli.py b/src/tgcf/cli.py index e449f904..9c9f56de 100644 --- a/src/tgcf/cli.py +++ b/src/tgcf/cli.py @@ -2,10 +2,10 @@ using the modern and robust `typer`. """ -import os import asyncio -from enum import Enum import logging +import os +from enum import Enum from typing import Optional import typer @@ -13,7 +13,6 @@ from tgcf import __version__ - load_dotenv(".env") FAKE = bool(os.getenv("FAKE")) diff --git a/src/tgcf/config.py b/src/tgcf/config.py index a26c63d8..9184a4bb 100644 --- a/src/tgcf/config.py +++ b/src/tgcf/config.py @@ -2,8 +2,8 @@ import logging import os -from typing import Dict, List, Optional from enum import Enum +from typing import Dict, List, Optional import yaml from pydantic import BaseModel diff --git a/src/tgcf/live.py b/src/tgcf/live.py index 459769d1..0c3f0281 100644 --- a/src/tgcf/live.py +++ b/src/tgcf/live.py @@ -1,8 +1,10 @@ import logging -from telethon import events, TelegramClient -from tgcf.config import CONFIG, API_HASH, API_ID, SESSION -from tgcf.utils import send_message + +from telethon import TelegramClient, events + +from tgcf.config import API_HASH, API_ID, CONFIG, SESSION from tgcf.plugins import extended +from tgcf.utils import send_message from_to = {} diff --git a/src/tgcf/past.py b/src/tgcf/past.py index 6f4d8f7f..b24072a1 100644 --- a/src/tgcf/past.py +++ b/src/tgcf/past.py @@ -2,12 +2,12 @@ import logging from telethon import TelegramClient -from telethon.tl.patched import MessageService from telethon.errors.rpcerrorlist import FloodWaitError +from telethon.tl.patched import MessageService -from tgcf.config import CONFIG, API_ID, API_HASH, SESSION -from tgcf.utils import send_message +from tgcf.config import API_HASH, API_ID, CONFIG, SESSION from tgcf.plugins import extended +from tgcf.utils import send_message async def forward_job(): diff --git a/src/tgcf/plugins.py b/src/tgcf/plugins.py index 91a7e7c6..f21e8ea3 100644 --- a/src/tgcf/plugins.py +++ b/src/tgcf/plugins.py @@ -1,5 +1,7 @@ -from tgcf.config import CONFIG from importlib import import_module + +from tgcf.config import CONFIG + PLUGINS = CONFIG.plugins def load_plugins(): From 65d0a8026ec37503b1e8f1aaeeedb7f85eea01ac Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 09:13:21 +0530 Subject: [PATCH 51/68] =?UTF-8?q?=E2=9E=95=20add=20isort=20as=20a=20dev=20?= =?UTF-8?q?deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index e0bcf12a..3b05c9fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -730,7 +730,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "0067d2442999e3383aad755c6c990cfaf1f99fe030672e192615351b5dbdd328" +content-hash = "77cabd305f6c6bb6525b5351de886c894a6ad0ebd67dfb11652ca5b0236eaf3c" [metadata.files] aiohttp = [ diff --git a/pyproject.toml b/pyproject.toml index 0af73c7a..cb9069eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ pylint = "^2.7.4" typer-cli = "^0.0.11" black = {version = "^20.8b1", allow-prereleases = true} mkdocs-gen-files = "^0.3.1" +isort = "^5.8.0" [tool.poetry.scripts] From a038690f2d7625413a0c9836c72eb7130e50ea05 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 10:16:58 +0530 Subject: [PATCH 52/68] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20make=20the=20logo=20?= =?UTF-8?q?in=20readme=20of=20transparent=20bkgrnd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7647a797..5ebc90a9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ this branch is not ready for use. go to the main branch https://github.com/aahni

- tgcf logo + tgcf logo

tgcf

From 57e15e1109523c98e994f22d67bb0dbb4fdc8b38 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 10:24:50 +0530 Subject: [PATCH 53/68] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20improve=20tgcf=20log?= =?UTF-8?q?o=20colour=20scheme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ebc90a9..cbc08dde 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ this branch is not ready for use. go to the main branch https://github.com/aahni

- tgcf logo + tgcf logo

tgcf

From c96cada2e720c536f0dd65f641c4b6a933277fe7 Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 14:35:52 +0530 Subject: [PATCH 54/68] =?UTF-8?q?=F0=9F=94=A5=20remove=20docs=20as=20will?= =?UTF-8?q?=20be=20using=20github=20wiki?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generate.py | 10 ---------- docs/gettings_started.md | 0 2 files changed, 10 deletions(-) delete mode 100644 docs/generate.py delete mode 100644 docs/gettings_started.md diff --git a/docs/generate.py b/docs/generate.py deleted file mode 100644 index a53d8b85..00000000 --- a/docs/generate.py +++ /dev/null @@ -1,10 +0,0 @@ -""" Generator for virtual files""" - -import mkdocs_gen_files - -with open("README.md") as file: - readme = file.read() - -with mkdocs_gen_files.open("index.md", "w") as f: - print(readme, file=f) - \ No newline at end of file diff --git a/docs/gettings_started.md b/docs/gettings_started.md deleted file mode 100644 index e69de29b..00000000 From e07a2f792aa8cf2a346882a297a61b500c653cfe Mon Sep 17 00:00:00 2001 From: aahnik Date: Mon, 19 Apr 2021 14:36:13 +0530 Subject: [PATCH 55/68] =?UTF-8?q?=F0=9F=9A=A7=20work=20in=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 +++++++++++++++++++++---------- mkdocs.yml | 16 ------------- plugins/tgcf_filter/__init__.py | 0 plugins/tgcf_replacer/__init__.py | 0 pyproject.toml | 11 ++++++++- src/tgcf/__init__.py | 1 - src/tgcf/plugins.py | 10 -------- tgcf/__init__.py | 3 +++ {src/tgcf => tgcf}/cli.py | 0 {src/tgcf => tgcf}/config.py | 15 +++++------- {src/tgcf => tgcf}/live.py | 11 ++++----- {src/tgcf => tgcf}/past.py | 8 +++---- tgcf/plugins.py | 23 ++++++++++++++++++ {src/tgcf => tgcf}/utils.py | 3 +-- 14 files changed, 79 insertions(+), 62 deletions(-) delete mode 100644 mkdocs.yml create mode 100644 plugins/tgcf_filter/__init__.py create mode 100644 plugins/tgcf_replacer/__init__.py delete mode 100644 src/tgcf/__init__.py delete mode 100644 src/tgcf/plugins.py create mode 100644 tgcf/__init__.py rename {src/tgcf => tgcf}/cli.py (100%) rename {src/tgcf => tgcf}/config.py (84%) rename {src/tgcf => tgcf}/live.py (93%) rename {src/tgcf => tgcf}/past.py (86%) create mode 100644 tgcf/plugins.py rename {src/tgcf => tgcf}/utils.py (76%) diff --git a/README.md b/README.md index cbc08dde..4f5d991f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -this branch is not ready for use. go to the main branch https://github.com/aahnik/tgcf - -

tgcf logo

@@ -9,7 +6,7 @@ this branch is not ready for use. go to the main branch https://github.com/aahni

-A powerful tool for forwarding telegram messages from source to destination. +The ultimate tool to automate telegram message forwarding.

GitHub license @@ -26,31 +23,48 @@ The *key features* are: 2. Supports both telegram **bot account** as well as **user account**. 3. **Custom Filtering** of messages based on **whitelist/blacklist**, **mime-type** and so on. 4. Modification of messages like **Text Replacement**, **Watermarking**, **OCR** etc. -5. Easily extend by writing you own extension in python. -6. Detailed **documentation** + **Video** tutorial + **Fast help** in discussion forum. +5. Detailed **documentation** + **Video** tutorial + **Fast help** in discussion forum. +6. If you are a python developer, writing **plugins** is like stealing candy from a baby. -## Video Tutorial ๐Ÿ“บ +What are you waiting for? Star ๐ŸŒŸ the repo and click Watch ๐Ÿ•ต to recieve updates. -Watch this youtube video to see how to use tgcf +You can also join the official [Telegram Channel](https://telegram.me/tg_cf), to recieve updates without any ads. + +## Video Tutorial ๐Ÿ“บ +A youtube video is coming soon. [Subscribe](https://www.youtube.com/channel/UCcEbN0d8iLTB6ZWBE_IDugg) to get notified. ## Installation ๐Ÿ”ฅ -Make sure you have latest version of Python installed. +Make sure you have Python 3.9 or above installed. ```shell -pip install tgcf +pip install pipx +pipx install tgcf ``` - ## Configuration โš™๏ธ +Configuring `tgcf` is easy. You just need two files. + +- `.env` : You heard it right! Just `.env`. This file is for storing your secret credentials for signing into Telegram. +- `tgcf.config.yml` : An `yaml` file to configure how `tgcf` behaves. ## Deploy to cloud ๐ŸŒฉ๏ธ +- GitHub Actions is a fantastic way to run cron jobs for free. You can run `tgcf past` periodically using GitHub Actions. + +- If you need live forwarding, you can deploy `tgcf` to Heroku, Digital Ocean or Google Cloud Run. -## Reviews ๐ŸŒŸ +- More information about deployment will be added soon. + +- One click deploys coming soon. ## Getting Help ๐Ÿ’๐Ÿป -Feel free to ask your questions in the [Discussion section](https://github.com/aahnik/telegram-chat-forward/discussions). For bugs and feature requests use the [issues section](https://github.com/aahnik/telegram-chat-forward/issues) of this repo. Please do not send me direct messages in Telegram. +- First of all read the documentation and watch the videos. +- If you still have doubts, you can try searching your problem in discussion forum or the issue tracker. +- Feel free to ask your questions in the [Discussion forum](https://github.com/aahnik/tgcf/discussions/new). +- For reporting bugs or requesting a feature please use the [issue tracker](https://github.com/aahnik/tgcf/issues/new) for this repo. + +Please do not send me direct messages in Telegram. (Exception: Sponsors can message me anytime) diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index bfbff8b2..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,16 +0,0 @@ -site_name: tgcf - -nav: - - Home: index.md - - getting_started.md - -repo_url: https://github.com/aahnik/tgcf - -theme: - name: readthedocs - -plugins: - - search - - gen-files: - scripts: - - docs/generate.py \ No newline at end of file diff --git a/plugins/tgcf_filter/__init__.py b/plugins/tgcf_filter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/tgcf_replacer/__init__.py b/plugins/tgcf_replacer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyproject.toml b/pyproject.toml index cb9069eb..ddc88a05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,17 @@ [tool.poetry] name = "tgcf" version = "0.1.6" -description = "Coming soon..." +description = "The ultimate tool to automate telegram message forwarding." authors = ["aahnik "] +license = "MIT" +readme = "README.md" +repository = "https://github.com/aahnik/tgcf" +documentation = "https://aahnik.github.io/tgcf" +packages = [ + { include = "tgcf"}, + { include = "tgcf_filter", from = "plugins" }, + { include = "tgcf_replacer", from = "plugins" }, +] [tool.poetry.dependencies] python = "^3.9" diff --git a/src/tgcf/__init__.py b/src/tgcf/__init__.py deleted file mode 100644 index 0a8da882..00000000 --- a/src/tgcf/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.6" diff --git a/src/tgcf/plugins.py b/src/tgcf/plugins.py deleted file mode 100644 index f21e8ea3..00000000 --- a/src/tgcf/plugins.py +++ /dev/null @@ -1,10 +0,0 @@ -from importlib import import_module - -from tgcf.config import CONFIG - -PLUGINS = CONFIG.plugins - -def load_plugins(): - for plugin_name in PLUGINS: - plugin = import_module(plugin_name) - diff --git a/tgcf/__init__.py b/tgcf/__init__.py new file mode 100644 index 00000000..efe53846 --- /dev/null +++ b/tgcf/__init__.py @@ -0,0 +1,3 @@ +from importlib.metadata import version + +__version__ = version(__package__) \ No newline at end of file diff --git a/src/tgcf/cli.py b/tgcf/cli.py similarity index 100% rename from src/tgcf/cli.py rename to tgcf/cli.py diff --git a/src/tgcf/config.py b/tgcf/config.py similarity index 84% rename from src/tgcf/config.py rename to tgcf/config.py index 9184a4bb..79b4683d 100644 --- a/src/tgcf/config.py +++ b/tgcf/config.py @@ -12,26 +12,23 @@ CONFIG_FILE = "tgcf.config.yml" + class Forward(BaseModel): source: int dest: List[int] offset: Optional[int] = 0 -class TextFormat(str, Enum): - bold = "bold" - italics = "italic" - strike = "strike" - code = "code" +# class TextFormat(str, Enum): +# bold = "bold" +# italics = "italic" +# strike = "strike" +# code = "code" class Config(BaseModel): forwards: List[Forward] show_forwarded_from: Optional[bool] = False - blacklist: Optional[List] = [] - whitelist: Optional[List] = [] - replace: Optional[Dict] = {} - text_format: Optional[TextFormat] = None plugins: Optional[Dict] diff --git a/src/tgcf/live.py b/tgcf/live.py similarity index 93% rename from src/tgcf/live.py rename to tgcf/live.py index 0c3f0281..d5f62379 100644 --- a/src/tgcf/live.py +++ b/tgcf/live.py @@ -1,9 +1,8 @@ import logging from telethon import TelegramClient, events - from tgcf.config import API_HASH, API_ID, CONFIG, SESSION -from tgcf.plugins import extended +from tgcf.plugins import apply_plugins from tgcf.utils import send_message from_to = {} @@ -63,11 +62,11 @@ async def new_message_handler(event): if event_uid not in _stored: _stored[event_uid] = [] - modified_message = extended(message) - if not modified_message: + message = apply_plugins(message) + if not message: return for recipient in to_send_to: - fwded_msg = await send_message(event.client, recipient, modified_message) + fwded_msg = await send_message(event.client, recipient, message) _stored[event_uid].append(fwded_msg) existing_hashes.append(hash(message.text)) @@ -92,7 +91,7 @@ async def edited_message_handler(event): else: to_send_to = from_to.get(event.chat_id) for recipient in to_send_to: - await send_message(event.client, recipient, extended(message)) + await send_message(event.client, recipient, apply_plugins(message)) async def deleted_message_handler(event): diff --git a/src/tgcf/past.py b/tgcf/past.py similarity index 86% rename from src/tgcf/past.py rename to tgcf/past.py index b24072a1..a1537c15 100644 --- a/src/tgcf/past.py +++ b/tgcf/past.py @@ -6,7 +6,7 @@ from telethon.tl.patched import MessageService from tgcf.config import API_HASH, API_ID, CONFIG, SESSION -from tgcf.plugins import extended +from tgcf.plugins import apply_plugins from tgcf.utils import send_message @@ -24,12 +24,12 @@ async def forward_job(): if isinstance(message, MessageService): continue try: - modified_message = extended(message) - if not modified_message: + message = apply_plugins(message) + if not message: continue for destination in forward.dest: - await send_message(client, destination, modified_message) + send_message(client,destination,message) last_id = str(message.id) logging.info(f"forwarding message with id = {last_id}") forward.offset = last_id diff --git a/tgcf/plugins.py b/tgcf/plugins.py new file mode 100644 index 00000000..db291cc3 --- /dev/null +++ b/tgcf/plugins.py @@ -0,0 +1,23 @@ +from importlib import import_module +import logging +from typing import List +from tgcf.config import CONFIG + +PLUGINS = CONFIG.plugins + + +def apply_plugins(message)->List: + for plugin_id,plugin_data in PLUGINS.items(): + plugin_name = f"tgcf_{plugin_id}" + try: + plugin = import_module(plugin_name) + except ModuleNotFoundError: + logging.error(f"Could not find plugin {plugin_name}") + else: + print(plugin_name) + print(plugin_data) + return None + + + + diff --git a/src/tgcf/utils.py b/tgcf/utils.py similarity index 76% rename from src/tgcf/utils.py rename to tgcf/utils.py index 327a67e9..d5659760 100644 --- a/src/tgcf/utils.py +++ b/tgcf/utils.py @@ -5,5 +5,4 @@ async def send_message(client, *args): # show forwarded from if CONFIG.show_forwarded_from: return await client.forward_messages(*args) - else: - return await client.send_message(*args) + return await client.send_message(*args) From ff2a8d673aede89f2efecd9987a7fb33bb2793de Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 12:44:06 +0530 Subject: [PATCH 56/68] =?UTF-8?q?=F0=9F=8E=A8=20added=20the=20mvp=20of=20p?= =?UTF-8?q?lugins=20replace=20and=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/tgcf_filter/__init__.py | 40 +++++++++++++++++++++++++++++++ plugins/tgcf_replace/__init__.py | 25 +++++++++++++++++++ plugins/tgcf_replacer/__init__.py | 0 3 files changed, 65 insertions(+) create mode 100644 plugins/tgcf_replace/__init__.py delete mode 100644 plugins/tgcf_replacer/__init__.py diff --git a/plugins/tgcf_filter/__init__.py b/plugins/tgcf_filter/__init__.py index e69de29b..6908166b 100644 --- a/plugins/tgcf_filter/__init__.py +++ b/plugins/tgcf_filter/__init__.py @@ -0,0 +1,40 @@ +import logging +from typing import List, Optional + +from pydantic import BaseModel + + +class FilterList(BaseModel): + blacklist: Optional[List[str]] = [] + whitelist: Optional[List[str]] = [] + + +class Filters(BaseModel): + text: Optional[FilterList] + + +class TgcfFilter: + id = "filter" + + def __init__(self, data): + print("tgcf filter data loaded") + self.filters = Filters(**data) + logging.info(self.filters) + + def modify(self, message): + msg_text = message.text + if not msg_text: + return message + + if self.text_safe(msg_text, self.filters.text): + return message + + def text_safe(self, text, flist: FilterList): + for forbidden in flist.blacklist: + if forbidden in text: + return False + if not flist.whitelist: + return True + for allowed in flist.whitelist: + if allowed in text: + return True diff --git a/plugins/tgcf_replace/__init__.py b/plugins/tgcf_replace/__init__.py new file mode 100644 index 00000000..96620f45 --- /dev/null +++ b/plugins/tgcf_replace/__init__.py @@ -0,0 +1,25 @@ +import logging +from typing import Dict, Optional + +from pydantic import BaseModel + + +class Replace(BaseModel): + text: Optional[Dict[str, str]] = {} + + +class TgcfReplace: + id = "replace" + + def __init__(self, data): + self.replace = Replace(**data) + logging.info(self.replace) + + def modify(self, message): + msg_text: str = message.text + if not msg_text: + return message + for original, new in self.replace.text.items(): + msg_text = msg_text.replace(original, new) + message.text = msg_text + return message diff --git a/plugins/tgcf_replacer/__init__.py b/plugins/tgcf_replacer/__init__.py deleted file mode 100644 index e69de29b..00000000 From c4f0931f2666c09ab419a04b718d1e7973684ce1 Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 12:44:55 +0530 Subject: [PATCH 57/68] =?UTF-8?q?=F0=9F=8E=A8=20update=20the=20format=20ta?= =?UTF-8?q?rget=20of=20Makefile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 25e01171..9fe109b2 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,8 @@ clean: @find . -type f -name "*.py[co]" -exec rm -rf {} + format: clean - @poetry run isort src/ tests/ - @poetry run black src/ tests/ + @poetry run isort tgcf/ plugins/ tests/ + @poetry run black tgcf/ plugins/ tests/ hard-clean: clean rm -rf .venv site From 0919b210a19c3d9de3cb5800187b6d75808170eb Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 12:45:21 +0530 Subject: [PATCH 58/68] =?UTF-8?q?=F0=9F=8E=A8=20rename=20target=20format?= =?UTF-8?q?=20to=20fmt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9fe109b2..cfd836c5 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ clean: @find . -type d -name '*pytest_cache*' -exec rm -rf {} + @find . -type f -name "*.py[co]" -exec rm -rf {} + -format: clean +fmt: clean @poetry run isort tgcf/ plugins/ tests/ @poetry run black tgcf/ plugins/ tests/ From 9ec22322dd8dbd23d0292eb09c5219241f65920f Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 12:46:15 +0530 Subject: [PATCH 59/68] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20plugins.py=20loads?= =?UTF-8?q?=20&=20applies=20plugins=20on=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tgcf/plugins.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/tgcf/plugins.py b/tgcf/plugins.py index db291cc3..f2498ec1 100644 --- a/tgcf/plugins.py +++ b/tgcf/plugins.py @@ -1,23 +1,42 @@ -from importlib import import_module import logging +from importlib import import_module from typing import List + from tgcf.config import CONFIG PLUGINS = CONFIG.plugins +plugins = [] -def apply_plugins(message)->List: - for plugin_id,plugin_data in PLUGINS.items(): - plugin_name = f"tgcf_{plugin_id}" + +def load_plugins(): + plugins = [] + for plugin_id, plugin_data in PLUGINS.items(): + plugin_module_name = f"tgcf_{plugin_id}" + plugin_class_name = f"Tgcf{plugin_id.title()}" try: - plugin = import_module(plugin_name) + plugin_module = import_module(plugin_module_name) + PluginClass = getattr(plugin_module, plugin_class_name) + plugin = PluginClass(plugin_data) + assert plugin.id == plugin_id + except ModuleNotFoundError: - logging.error(f"Could not find plugin {plugin_name}") + logging.error(f"Could not find plugin for {plugin_id}") + except AttributeError: + logging.error(f"Found plugin {plugin_id}, but failed to load.") else: - print(plugin_name) - print(plugin_data) - return None + print(f"Loaded plugin {plugin_id}") + plugins.append(plugin) + return plugins +plugins = load_plugins() +def apply_plugins(message) -> List: + for plugin in plugins: + message = plugin.modify(message) + logging.info(f"Applied plugin {plugin.id}") + if not message: + return + return message From cc2cebec3b3aa928d291a107558d7fa9a8311278 Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 12:46:39 +0530 Subject: [PATCH 60/68] =?UTF-8?q?=F0=9F=8E=A8=20minor=20formatting=20and?= =?UTF-8?q?=20misc=20fixes=20in=20tgcf=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tgcf/__init__.py | 2 +- tgcf/config.py | 3 +-- tgcf/live.py | 17 +++++++++++++---- tgcf/past.py | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tgcf/__init__.py b/tgcf/__init__.py index efe53846..5f3c0243 100644 --- a/tgcf/__init__.py +++ b/tgcf/__init__.py @@ -1,3 +1,3 @@ from importlib.metadata import version -__version__ = version(__package__) \ No newline at end of file +__version__ = version(__package__) diff --git a/tgcf/config.py b/tgcf/config.py index 79b4683d..a77410f8 100644 --- a/tgcf/config.py +++ b/tgcf/config.py @@ -12,7 +12,6 @@ CONFIG_FILE = "tgcf.config.yml" - class Forward(BaseModel): source: int dest: List[int] @@ -29,7 +28,7 @@ class Forward(BaseModel): class Config(BaseModel): forwards: List[Forward] show_forwarded_from: Optional[bool] = False - plugins: Optional[Dict] + plugins: Optional[Dict] = {} def read_config(): diff --git a/tgcf/live.py b/tgcf/live.py index d5f62379..50103866 100644 --- a/tgcf/live.py +++ b/tgcf/live.py @@ -1,6 +1,7 @@ import logging from telethon import TelegramClient, events + from tgcf.config import API_HASH, API_ID, CONFIG, SESSION from tgcf.plugins import apply_plugins from tgcf.utils import send_message @@ -76,6 +77,7 @@ async def edited_message_handler(event): message = event.message chat_id = event.chat_id + if chat_id not in from_to: return @@ -83,15 +85,22 @@ async def edited_message_handler(event): event_uid = EventUid(event) + message = apply_plugins(message) + + if not message: + return + fwded_msgs = _stored.get(event_uid) + if fwded_msgs: for msg in fwded_msgs: await msg.edit(message.text) return - else: - to_send_to = from_to.get(event.chat_id) - for recipient in to_send_to: - await send_message(event.client, recipient, apply_plugins(message)) + + to_send_to = from_to.get(event.chat_id) + + for recipient in to_send_to: + await send_message(event.client, recipient, message) async def deleted_message_handler(event): diff --git a/tgcf/past.py b/tgcf/past.py index a1537c15..2a10292d 100644 --- a/tgcf/past.py +++ b/tgcf/past.py @@ -29,7 +29,7 @@ async def forward_job(): continue for destination in forward.dest: - send_message(client,destination,message) + await send_message(client, destination, message) last_id = str(message.id) logging.info(f"forwarding message with id = {last_id}") forward.offset = last_id From a84dde6cfdf303bf06603dd3f88492f4204f683b Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 13:35:01 +0530 Subject: [PATCH 61/68] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20update=20pyproject.t?= =?UTF-8?q?oml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ddc88a05..3b5ece33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tgcf" -version = "0.1.6" +version = "0.1.15" description = "The ultimate tool to automate telegram message forwarding." authors = ["aahnik "] license = "MIT" @@ -14,7 +14,7 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.9" +python = "^3.8" requests = "^2.25.1" typer = "^0.3.2" python-dotenv = "^0.15.0" From b4e0625645f024fd76174801d1dd751a9848199a Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 16:09:20 +0530 Subject: [PATCH 62/68] =?UTF-8?q?=F0=9F=9A=A7=20wip=20building=20the=20pla?= =?UTF-8?q?tform=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 56 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4f5d991f..8928cb2c 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,12 @@ The ultimate tool to automate telegram message forwarding. The *key features* are: -1. Two modes of operation: **past** and **live** for dealing with existing or upcoming messages. -2. Supports both telegram **bot account** as well as **user account**. -3. **Custom Filtering** of messages based on **whitelist/blacklist**, **mime-type** and so on. -4. Modification of messages like **Text Replacement**, **Watermarking**, **OCR** etc. -5. Detailed **documentation** + **Video** tutorial + **Fast help** in discussion forum. -6. If you are a python developer, writing **plugins** is like stealing candy from a baby. +1. Two [modes of operation](https://github.com/aahnik/tgcf/wiki/Past-vs-Live-modes-explained) are _past_ or _live_ for dealing with either existing or upcoming messages. +2. Supports [signing in](https://github.com/aahnik/tgcf/wiki/Signing-in-with-a-bot-or-user-account) with both telegram _bot_ account as well as _user_ account. +3. Custom [Filtering](https://github.com/aahnik/tgcf/wiki/How-to-use-filters-%3F) of messages based on whitelist or blacklist. +4. Modification of messages like [Text Replacement](https://github.com/aahnik/tgcf/wiki/Text-Replacement-feature-explained), [Watermarking](https://github.com/aahnik/tgcf/wiki/How-to-use--watermarking-%3F), [OCR](https://github.com/aahnik/tgcf/wiki/You-can-do-OCR-!) etc. +5. Detailed **[documentation๐Ÿ“–](https://github.com/aahnik/tgcf/wiki)** + Video tutorial + Fast help in [discussion forum๐Ÿ’ฌ](https://github.com/aahnik/tgcf/discussions). +6. If you are a python developer, writing [plugins๐Ÿ”Œ](https://github.com/aahnik/tgcf/wiki/How-to-write-a-plugin-for-tgcf-%3F) is like stealing candy from a baby. What are you waiting for? Star ๐ŸŒŸ the repo and click Watch ๐Ÿ•ต to recieve updates. @@ -34,35 +34,55 @@ You can also join the official [Telegram Channel](https://telegram.me/tg_cf), to A youtube video is coming soon. [Subscribe](https://www.youtube.com/channel/UCcEbN0d8iLTB6ZWBE_IDugg) to get notified. -## Installation ๐Ÿ”ฅ +## Local Installation ๐Ÿ”ฅ -Make sure you have Python 3.9 or above installed. +This guide is for installing and running on your own computer (Windows/Mac/Linux/Android). + +> **Note:** Make sure you have Python 3.8 or above installed. Go to [python.org](https://python.org) to download python. + +Open your terminal (command prompt) and run the following commands. ```shell pip install pipx pipx install tgcf ``` -## Configuration โš™๏ธ +To check if the installation succeeded, run -Configuring `tgcf` is easy. You just need two files. +```shell +tgcf --version +``` + +If you see an error, that means installation failed. -- `.env` : You heard it right! Just `.env`. This file is for storing your secret credentials for signing into Telegram. -- `tgcf.config.yml` : An `yaml` file to configure how `tgcf` behaves. -## Deploy to cloud ๐ŸŒฉ๏ธ +## Supported Platforms ๐ŸŒฉ๏ธ -- GitHub Actions is a fantastic way to run cron jobs for free. You can run `tgcf past` periodically using GitHub Actions. +Deploying to a cloud server is an easier alternative if you cannot install on your own machine. Cloud servers are very reliable and great for running `tgcf` in live mode. + +| Platform | Supported Modes | How to ? | Minimum Price | +| ------------------------- | --------------- | ------------------------------------------------------------ | ------------- | +| Heroku | live | Deploy | Free | +| GitHub Actions | past | ![](https://user-images.githubusercontent.com/66209958/115380652-6b56b680-a1f0-11eb-8eff-eda079b33120.png) | Free | +| Digital Ocean | past + live | Deploy to DO | $5 | +| Gitpod | past + live | Deploy to DO | Free | +| Windows/Mac/LInux/Android | past + live | Deploy to DO | Free | +| Docker | past + live | Deploy to DO | Free | +| Google Cloud | past + live | Deploy to DO | Free | + + +## Configuration โš™๏ธ + +Configuring `tgcf` is easy. You just need two files. -- If you need live forwarding, you can deploy `tgcf` to Heroku, Digital Ocean or Google Cloud Run. +- [`.env`](https://github.com/aahnik/tgcf/wiki/Environment-Variables) : You heard it right! Just `.env`. This file is for storing your secret credentials for signing into Telegram. This file is for defining the environment variables. You can do so by other methods also. -- More information about deployment will be added soon. +- [`tgcf.config.yml`](https://github.com/aahnik/tgcf/wiki/How-to-configure-tgcf-%3F) : An `yaml` file to configure how `tgcf` behaves. -- One click deploys coming soon. ## Getting Help ๐Ÿ’๐Ÿป -- First of all read the documentation and watch the videos. +- First of all [read the wiki](https://github.com/aahnik/tgcf/wiki) and [watch the videos. - If you still have doubts, you can try searching your problem in discussion forum or the issue tracker. - Feel free to ask your questions in the [Discussion forum](https://github.com/aahnik/tgcf/discussions/new). - For reporting bugs or requesting a feature please use the [issue tracker](https://github.com/aahnik/tgcf/issues/new) for this repo. From 12072c9e76a3ccb9eab45803c0fe09a6753c2f55 Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 17:15:11 +0530 Subject: [PATCH 63/68] =?UTF-8?q?=F0=9F=9A=A7=20wip=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 12 ++---------- README.md | 47 ++++++++++++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index ea06458d..a36a49f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,14 +4,6 @@ WORKDIR /app RUN apt-get update && apt-get upgrade -y -RUN pip install --upgrade pip && pip install poetry +RUN pip install --upgrade pip && pip install tgcf -COPY README.md LICENSE pyproject.toml poetry.lock ./ - -COPY src ./src - -COPY tests ./tests - -RUN poetry install - -CMD ["poetry","run","tgcf"] +CMD ["tgcf"] diff --git a/README.md b/README.md index 8928cb2c..d062d5ba 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,21 @@ You can also join the official [Telegram Channel](https://telegram.me/tg_cf), to A youtube video is coming soon. [Subscribe](https://www.youtube.com/channel/UCcEbN0d8iLTB6ZWBE_IDugg) to get notified. -## Local Installation ๐Ÿ”ฅ - -This guide is for installing and running on your own computer (Windows/Mac/Linux/Android). +## Run Locally ๐Ÿ”ฅ > **Note:** Make sure you have Python 3.8 or above installed. Go to [python.org](https://python.org) to download python. + + +| Platform | Supported | +| -------- | :-------: | +| Windows | โœ… | +| Mac | โœ… | +| Linux | โœ… | +| [Android](https://github.com/aahnik/tgcf/wiki/Run-on-Android-using-Termux) | โœ… | + +If you are familiar with **Docker**, you may [go that way](https://github.com/aahnik/tgcf/wiki/Install-and-run-using-docker) for an easier life. + Open your terminal (command prompt) and run the following commands. ```shell @@ -55,29 +64,33 @@ tgcf --version If you see an error, that means installation failed. +### Configuration โš™๏ธ + +Configuring `tgcf` is easy. You just need two files. + +- [`.env`](https://github.com/aahnik/tgcf/wiki/Environment-Variables) : You heard it right! Just `.env`. This file is for storing your secret credentials for signing into Telegram. This file is for defining the environment variables. You can do so by other methods also. + +- [`tgcf.config.yml`](https://github.com/aahnik/tgcf/wiki/How-to-configure-tgcf-%3F) : An `yaml` file to configure how `tgcf` behaves. + -## Supported Platforms ๐ŸŒฉ๏ธ + +## Run on cloud ๐ŸŒฉ๏ธ Deploying to a cloud server is an easier alternative if you cannot install on your own machine. Cloud servers are very reliable and great for running `tgcf` in live mode. -| Platform | Supported Modes | How to ? | Minimum Price | -| ------------------------- | --------------- | ------------------------------------------------------------ | ------------- | -| Heroku | live | Deploy | Free | -| GitHub Actions | past | ![](https://user-images.githubusercontent.com/66209958/115380652-6b56b680-a1f0-11eb-8eff-eda079b33120.png) | Free | -| Digital Ocean | past + live | Deploy to DO | $5 | -| Gitpod | past + live | Deploy to DO | Free | -| Windows/Mac/LInux/Android | past + live | Deploy to DO | Free | -| Docker | past + live | Deploy to DO | Free | -| Google Cloud | past + live | Deploy to DO | Free | +When you are deploying on a cloud platform, you can configure `tgcf` using [environment variables](https://github.com/aahnik/tgcf/wiki/Environment-Variables). The contents of [`tgcf.config.yml`]() can be put inside the environment variable called `TGCF_CONFIG`. -## Configuration โš™๏ธ +| How to ? | +| :------------------------------------------------------------: | +| Deploy to Heroku | +| Deploy to DO | +| Run on Google Cloud | +| Run on Gitpod | -Configuring `tgcf` is easy. You just need two files. +If you need to run `tgcf` in past mode periodically, then you can use [GitHub Actions](https://github.com/aahnik/tgcf/wiki/Run-tgcf-in-past-mode-periodically) to run a scheduled workflow. -- [`.env`](https://github.com/aahnik/tgcf/wiki/Environment-Variables) : You heard it right! Just `.env`. This file is for storing your secret credentials for signing into Telegram. This file is for defining the environment variables. You can do so by other methods also. -- [`tgcf.config.yml`](https://github.com/aahnik/tgcf/wiki/How-to-configure-tgcf-%3F) : An `yaml` file to configure how `tgcf` behaves. ## Getting Help ๐Ÿ’๐Ÿป From a4edf8755041deadc618d3d4b74ed86a178b904c Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 17:55:30 +0530 Subject: [PATCH 64/68] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20update=20pyproject.t?= =?UTF-8?q?oml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b5ece33..82f5f4af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://aahnik.github.io/tgcf" packages = [ { include = "tgcf"}, { include = "tgcf_filter", from = "plugins" }, - { include = "tgcf_replacer", from = "plugins" }, + { include = "tgcf_replace", from = "plugins" }, ] [tool.poetry.dependencies] From 6e577646e259b24e3314f06207fb2e2c360a7ec9 Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 17:55:49 +0530 Subject: [PATCH 65/68] =?UTF-8?q?=F0=9F=8E=A8=20improve=20formatting=20and?= =?UTF-8?q?=20add=20missing=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 97 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d062d5ba..d5be1a08 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ + +

tgcf logo

tgcf

-

The ultimate tool to automate telegram message forwarding.

@@ -17,28 +18,40 @@ The ultimate tool to automate telegram message forwarding.
+ + The *key features* are: -1. Two [modes of operation](https://github.com/aahnik/tgcf/wiki/Past-vs-Live-modes-explained) are _past_ or _live_ for dealing with either existing or upcoming messages. -2. Supports [signing in](https://github.com/aahnik/tgcf/wiki/Signing-in-with-a-bot-or-user-account) with both telegram _bot_ account as well as _user_ account. -3. Custom [Filtering](https://github.com/aahnik/tgcf/wiki/How-to-use-filters-%3F) of messages based on whitelist or blacklist. -4. Modification of messages like [Text Replacement](https://github.com/aahnik/tgcf/wiki/Text-Replacement-feature-explained), [Watermarking](https://github.com/aahnik/tgcf/wiki/How-to-use--watermarking-%3F), [OCR](https://github.com/aahnik/tgcf/wiki/You-can-do-OCR-!) etc. -5. Detailed **[documentation๐Ÿ“–](https://github.com/aahnik/tgcf/wiki)** + Video tutorial + Fast help in [discussion forum๐Ÿ’ฌ](https://github.com/aahnik/tgcf/discussions). -6. If you are a python developer, writing [plugins๐Ÿ”Œ](https://github.com/aahnik/tgcf/wiki/How-to-write-a-plugin-for-tgcf-%3F) is like stealing candy from a baby. +1. Two [modes of operation](https://github.com/aahnik/tgcf/wiki/Past-vs-Live-modes-explained) +are _past_ or _live_ for dealing with either existing or upcoming messages. +2. Supports [signing in](https://github.com/aahnik/tgcf/wiki/Signing-in-with-a-bot-or-user-account) +with both telegram _bot_ account as well as _user_ account. +3. Custom [Filtering](https://github.com/aahnik/tgcf/wiki/How-to-use-filters-%3F) +of messages based on whitelist or blacklist. +4. Modification of messages like [Text Replacement](https://github.com/aahnik/tgcf/wiki/Text-Replacement-feature-explained), +[Watermarking](https://github.com/aahnik/tgcf/wiki/How-to-use--watermarking-%3F), +[OCR](https://github.com/aahnik/tgcf/wiki/You-can-do-OCR-!) etc. +5. Detailed **[documentation๐Ÿ“–](https://github.com/aahnik/tgcf/wiki)** + +Video tutorial + Fast help in [discussion forum๐Ÿ’ฌ](https://github.com/aahnik/tgcf/discussions). +6. If you are a python developer, writing [plugins๐Ÿ”Œ](https://github.com/aahnik/tgcf/wiki/How-to-write-a-plugin-for-tgcf-%3F) +is like stealing candy from a baby. What are you waiting for? Star ๐ŸŒŸ the repo and click Watch ๐Ÿ•ต to recieve updates. -You can also join the official [Telegram Channel](https://telegram.me/tg_cf), to recieve updates without any ads. +You can also join the official [Telegram Channel](https://telegram.me/tg_cf), +to recieve updates without any ads. + ## Video Tutorial ๐Ÿ“บ A youtube video is coming soon. [Subscribe](https://www.youtube.com/channel/UCcEbN0d8iLTB6ZWBE_IDugg) to get notified. -## Run Locally ๐Ÿ”ฅ - -> **Note:** Make sure you have Python 3.8 or above installed. Go to [python.org](https://python.org) to download python. + +## Run Locally ๐Ÿ”ฅ +> **Note:** Make sure you have Python 3.8 or above installed. +Go to [python.org](https://python.org) to download python. | Platform | Supported | | -------- | :-------: | @@ -47,7 +60,8 @@ A youtube video is coming soon. [Subscribe](https://www.youtube.com/channel/UCcE | Linux | โœ… | | [Android](https://github.com/aahnik/tgcf/wiki/Run-on-Android-using-Termux) | โœ… | -If you are familiar with **Docker**, you may [go that way](https://github.com/aahnik/tgcf/wiki/Install-and-run-using-docker) for an easier life. +If you are familiar with **Docker**, you may [go that way](https://github.com/aahnik/tgcf/wiki/Install-and-run-using-docker) +for an easier life. Open your terminal (command prompt) and run the following commands. @@ -64,40 +78,65 @@ tgcf --version If you see an error, that means installation failed. -### Configuration โš™๏ธ +### Configuration ๐Ÿ› ๏ธ + +Configuring `tgcf` is easy. You just need two files in your present directory +(from which tgcf is invoked). -Configuring `tgcf` is easy. You just need two files. +- [`.env`](https://github.com/aahnik/tgcf/wiki/Environment-Variables) : To +define your environment variables easily. -- [`.env`](https://github.com/aahnik/tgcf/wiki/Environment-Variables) : You heard it right! Just `.env`. This file is for storing your secret credentials for signing into Telegram. This file is for defining the environment variables. You can do so by other methods also. +- [`tgcf.config.yml`](https://github.com/aahnik/tgcf/wiki/How-to-configure-tgcf-%3F) : +An `yaml` file to configure how `tgcf` behaves. -- [`tgcf.config.yml`](https://github.com/aahnik/tgcf/wiki/How-to-configure-tgcf-%3F) : An `yaml` file to configure how `tgcf` behaves. +### Start `tgcf` โœจ +In your terminal, just run `tgcf live` or `tgcf past` to start `tgcf`. +For more details run `tgcf --help` or [read docs](https://github.com/aahnik/tgcf/wiki/CLI-Usage). ## Run on cloud ๐ŸŒฉ๏ธ -Deploying to a cloud server is an easier alternative if you cannot install on your own machine. Cloud servers are very reliable and great for running `tgcf` in live mode. +Deploying to a cloud server is an easier alternative if you cannot install +on your own machine. +Cloud servers are very reliable and great for running `tgcf` in live mode. -When you are deploying on a cloud platform, you can configure `tgcf` using [environment variables](https://github.com/aahnik/tgcf/wiki/Environment-Variables). The contents of [`tgcf.config.yml`]() can be put inside the environment variable called `TGCF_CONFIG`. +When you are deploying on a cloud platform, you can configure `tgcf` +using [environment variables](https://github.com/aahnik/tgcf/wiki/Environment-Variables). +The contents of [`tgcf.config.yml`](https://github.com/aahnik/tgcf/wiki/How-to-configure-tgcf-%3F) +can be put inside the environment variable called `TGCF_CONFIG`. +You may click on the platform name *(left coloumn)* to learn more about the +deployment process. Clicking on the "deploy" button *(right coloumn)* will +directly deploy the application to that platform. -| How to ? | -| :------------------------------------------------------------: | -| Deploy to Heroku | -| Deploy to DO | -| Run on Google Cloud | -| Run on Gitpod | + -If you need to run `tgcf` in past mode periodically, then you can use [GitHub Actions](https://github.com/aahnik/tgcf/wiki/Run-tgcf-in-past-mode-periodically) to run a scheduled workflow. +
+| Platform | One click deploy | +| ------------------------------------------------------------ | :----------------------------------------------------------: | +| [Heroku](https://github.com/aahnik/tgcf/wiki/Deploy-to-Heroku) | Deploy to Heroku | +| [Digital Ocean](https://github.com/aahnik/tgcf/wiki/Deploy-to-Digital-Ocean) | Deploy to DO | +| [Google Cloud](https://github.com/aahnik/tgcf/wiki/Run-on-Google-Cloud) | Run on Google Cloud | +| [Gitpod](https://github.com/aahnik/tgcf/wiki/Run-for-free-on-Gitpod) | Run on Gitpod | +
+ +If you need to run `tgcf` in past mode periodically, then you may set a cron job +in your computer or use [GitHub Actions](https://github.com/aahnik/tgcf/wiki/Run-tgcf-in-past-mode-periodically) +to run a scheduled workflow. ## Getting Help ๐Ÿ’๐Ÿป -- First of all [read the wiki](https://github.com/aahnik/tgcf/wiki) and [watch the videos. -- If you still have doubts, you can try searching your problem in discussion forum or the issue tracker. +- First of all [read the wiki](https://github.com/aahnik/tgcf/wiki) +and [watch](https://www.youtube.com/channel/UCcEbN0d8iLTB6ZWBE_IDugg) the videos. +- If you still have doubts, you can try searching your problem in discussion +forum or the issue tracker. - Feel free to ask your questions in the [Discussion forum](https://github.com/aahnik/tgcf/discussions/new). -- For reporting bugs or requesting a feature please use the [issue tracker](https://github.com/aahnik/tgcf/issues/new) for this repo. +- For reporting bugs or requesting a feature please use the [issue tracker](https://github.com/aahnik/tgcf/issues/new) +for this repo. -Please do not send me direct messages in Telegram. (Exception: Sponsors can message me anytime) +Please do not send me direct messages on Telegram. +(Exception: Sponsors can message me anytime) From 515ccd16e27af6ca1c4659464c37bba37fec6d04 Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 18:05:57 +0530 Subject: [PATCH 66/68] bump version to 0.1.16 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 82f5f4af..f2adda26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tgcf" -version = "0.1.15" +version = "0.1.16" description = "The ultimate tool to automate telegram message forwarding." authors = ["aahnik "] license = "MIT" From 74a752a065b65584ee5589a0addfc5766b9a0c25 Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 18:06:48 +0530 Subject: [PATCH 67/68] update link to documentation --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f2adda26..2814771d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["aahnik "] license = "MIT" readme = "README.md" repository = "https://github.com/aahnik/tgcf" -documentation = "https://aahnik.github.io/tgcf" +documentation = "https://github.com/aahnik/tgcf/wiki" packages = [ { include = "tgcf"}, { include = "tgcf_filter", from = "plugins" }, From cdf98ceee39048b57a2972699e43b3167a912ec7 Mon Sep 17 00:00:00 2001 From: aahnik Date: Tue, 20 Apr 2021 18:08:41 +0530 Subject: [PATCH 68/68] bump version again --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2814771d..6a844ef5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tgcf" -version = "0.1.16" +version = "0.1.17" description = "The ultimate tool to automate telegram message forwarding." authors = ["aahnik "] license = "MIT"