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/)
+
+
+
-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
zcmeFYXHXOW+yA@i2?Pi|^w5idp?3|v_bP&hUIe8Jh?)cfp+|~I$PACKrRyM*)a$@z+BL6zXMZ`o!e5*v2G(-<`
ziJl~iiDAY59ynETd1DC?5eW%V30+qS`=e5F22u&-IB6Wt*b_&sl$I8kRu-30<(8FK
zmQ#?HQ#ulqUWp7S
zE6XTXyj2lHtH|M0tOHf9JUe6+q$(q-c34kM$3e~Dh+1fdx{ToAb07_i6PiaHwLFZq
zVk@;}__Wm(wLM_kS3tTY3_W83eHn=UA#wdvFvEaABWb`$U&&YoX>99nVsO}`ck)NV2U9nDBadS`9^_Jw40TT*H_ww{UMC4&3XPiKUfJjt
z}0Gjck*J33yx?7Vuub9AQb
z-s8I+jXmuZ_XeNc?`1wJ4t>O|a5pz~vD8$@LqGrk
zNdJJsH~>yS_3t(CuQtj0pC2TJ$*PCu{+*|xcI_jU7;e(!4+IBXKUe%9(yFAj_IDVaw&LLsf
z)ihCSQ>^0OGLb#i=yJ`e!mhhz=7vvaPJR;~SYr$_g{z9ZRD*JU>DfB(ep&pnWGe*Eyqm+h$=C&wQ@`t^NhBlqw5!S7%D
zU)~98S}L#)EPr`xL(|YEn&OR|iJX7gq
zs)^kCb$(kp-ct9B?(q3srkcV4`fW5FEa}H~sp1GO=|k`k3VLceq>wJ_aA{~8DBsL#
zh>TETFoTOdydpv_82?1d@yH~xLlEvKw(F5H+sRuAW_8G^({|WzSI?Nd*Ilk8uj9fh
z2TdAJNd9~;>xkZJXY|R|ckymk%r)aPrKA`HX~YG9k5sB`?jt={m$8|T$UX%>P`W`%=DvG1@aerFFsCwRYV63ba`U_+e}?(tE$gF<
z{&Xr;n1bL{!nK02X&9h`AI!860JSm9p}bIObvPXRqY9Qg9iV5Xez4$k$1WRJ=FLv_s9F4dGvL#s)1~!`+IxJ`RHcXM&aSCqDHXb9m4Kz
zf|d_^sB6-Sp`K|%IB0u{;6sXOFg!&sBR!W@$3|J||Yt*_EbDxl8k^aMd24j?N3l33v*Zgw1Fb(n2g>TkaFVoJ-CA
zfKMky{z|ycnKMs){K#;Huv3)=1n~ULTCm{0snjbMOTB{X+*8}>KD(!ul0)g49?N7S
z2?$ekM?nouVB1P2^C9F&VD_~}61J%f4W)w614Ov$Fo=r+=o2iRQs1J;f|`2F&j16>
zk?RCHxU&O6(X2f*i6mS(lPtpCG16p~0jr7fk~h_9uS5cQM{5U;rs3cpipW2-OEo}y
zF{y$NaXX{u`Pa4w#0)CbZ`XpPoWcH@)D}H7JMip-6tO?V(elHF6^80V6_W5
z0M}f+xSOE~kqI}jfuiy2WlvNgB62bI-%2#&Spj5>uM$MTpM*Sa?2c?=n(ytgK^Hgt
zxo;_fOfnU-DHv=oYlEVVk1P@Hka@@IcRZ76o$Y`ZHnh%Y6SHCG%~XV_hs}a+m7(-7Mc1#|)lHP|fdyiut@V@1GP9<+_7}q7mND7xZVBBt
zx!_i(ImYrHb{^tf?kg=Rmr5{m$wtKWa|dekaME2k>~?UMr+dlj1~$_XA>5{)e*{SQ
zzE%sJ42NIXn1vusGmzpWD3-O<9R~nNA`Yo6KzJu02vwlvq7d7lVpV{QrX7M5ZWJ*j
zNu*c_Qie1ky+;58>Qpba=P_q`r_(_YZM31%3&I}5JpEz?!0CpC@|KqjfJ*>Q#Tde+
z8iw1+GZ|3p8ngklWEHQ5;wx`4H&QU|$jh1E}i=5$UC>
z@wE^DK(nEO$s`2Bqfc&<&~-&;4hCNq=76XoxKLnUL$+Xt!o5ch8MKFj>WXP61a1qfAJ$%TvtkmVoFwGDg
zTndzEsLvw0=B9e^UPU10ySy|L+0#&e9P%iV1d{|neVXJGa`-vESrQ2%PC}l0PUax(
z01#vLG=fRaVA#>n<)V-a_%aSUPThUZlZy}8%dss=?z?>BAs^fV!%ggg;H%-+%~f@D
zUcUh8Gof5cI#4D1bklGgh%5Yqa0JvTR>%Rq@+YZBUgI=i1Lj035x`Cu5GaNn03!ft
z^~XWzE>0jLoB)=ABhUmyz{z0(#9;>(bDda@8SRBY$uP1Wz@3xd`}PV8`ktd4E#pGQ
zh~jpj9!zNB0X~phofsW;>f9p(CJ1`EovLOd-4U}>iB4dVOne@8Uk_ozY`n>bgTKMS
zF-#$2M6h
z?E^R;iH$+xAiO(%K^JIV%FUkh;r=g{XfeR;*F-oG4Z^VQcVgZHtyVb5gF8_g!617k
zR04530Ry;`0RE(F4cG`r
zZ0pp4);fXB$==$^ffgwM4jF^-JKW`HF+i@IIbpXC6v{$O+8{1%hI@K&bDXpg9(oc3
z>bUH!$N-2X5LG-t!6UIQWX(3R?xW
z^)20ju(Y{YmooyK!7lh;WU67yX=*t5T${n5X
zB);31RSSBFpxM!x*VpAYS4m+F~D{fP^mVMy_;_a3mPFGaL5HL
z??N$V0_R*bCff}y>p-G-B%DxzOVT(Q0S;I=FAd-~L!?w0pyRlRrHt%pYrDN|2`Gv4
zP(!ei7DxaO79eQ~?7*v7sGhc*_1Lp`A`1DHLk$;bayj>TbM8ZZ30fnf(*~$uq_B7)
zBt6ttZQcz`K6?Udx#7e*2*BE>%MvE@4R-PsszWuv;ysh0L+eMA`^3Qs5=Mg@Kj~ck
zY@QKf=tv!S{|(ROy(^3@TrCFfoZ-mkgNcygu%iz_(59*0z90gf)<0F(rr
zMxIz#V^5}qxWKFW<=F;wPc2MzOH$ETIEaV=S+u2v3_`C=L{x-IOeGYv
zOv!=lw2TlyoUZKr<9x2XglZc($Od_n3Oh-tPiH}MXO2}g9d{G;_xuU-j?6akR_BS2
zR&0rsb^&t`ibtt_j(FtX=CQ(oki`)}A}~G%I3fm?w%Dj0SnEatU9NR9^>7AZz$z|)
z;7&u!0ky1Vw=Td&!kYv?!D@76fZf5)^jcz1ZI((Kjb&Q#)(vmx0heUOI%3Y8!#e=%
zh8UH`foWP~Xp2H{^q74d75C$jjBoHCSU!#J*@K5*|xnL=SdvG5QJ;{Rwr4eYY
z=G%+miBN1(ErJ!<8VdC4K+3+=&hUbjadosouydS#b}i(3KRx^_by6C7Zow}j20Tv%
zPCSp=38budmH0mIKJlv?!%l>>!7lFjhR1+KndduvL0+5^!Z>6z2Z#qC$|W==JTrs^
zpAiG-pLRPHaZS^vO|9l-(sZ~Fy@iJuogAUy^V~3K4-brhX!k(k{aIkG%<~J)MUWN)
zLd9o`7W+-mNfPi#+af;oZpwqZ3IsFS7A3*{m?40_dj;|}!Fh|fJsRIWVG6Y2Zgyr{
zuSlON2RI(EKxas(mf@V@w!rcoxGWxY{_ctADXlBE5AQXplpWAv0tC=8CMp#Zq{#w3
z)dw#pUwj>Tww-nDWp8?y3sS?Xy{nD8sV#rI!uE$R)Rtx2p;?dxl8xzbo12i3rgQMf
zaV;q+N$VuVI+a&A~$g=Jyl-)+PR4CavWa<&@nqgErcOxae}XMi4H8!oUX%
zfQ`j1gMgD~M9gz?Z+xq>PyTV{6uW68cranZ;?jYBZt*<}b&i-kW$8XIaIF359f3er
zZ@s7s!1)T0$b`vnKCRAHUTJ))Q8@y<0)rT`-kUX#;(JAYOU!-YIKu*UlB~yjeGtl4s|&bErzL&T!4LUQN6`QxnsrJIgmedW{#UO&aOJ0;HM%Mg(
zKMwpOVfF3Cd4S^>4rMZ4#Zl_V$fTIZfsE{WzD^;L*Bh%c@teu86aN68`{D~sv1{`S#l<`UF%w_l&Wug^PjsbBi>aexnc^Ni{Tt#
z^8Aj`-2>21@XH2Ux`qT)-2n^6izp(nB5>&H#|b{~K3CsDUowg^xC#di!a-YchAXk8
zdN1$1{7J<(oqn30SY{NGZK*_ps1jf=
z^@cm>#Bo0#hDKakqsPs`<>b5CCtj-W4Ly8-_$Fr*pl4{h1G{lZLgLSQ1>Be&Jcp_Q
zYqFpeToOUJ#eD8Eicx5cfk@-w21>vUcQ8)>@74xEaPs4?)#z*3P-Je*)f^Wni?
zFK4CnErMlP5M!6o`M1~Bq`~KRxIY1Delp-#!n>}vcMn#PediVo(h1cn?AM;=OKNLEp
zZ_zO^14VOKnJH>aCPHjQB}85UWuD-pICPKR6;4pQU!DAC)BY=d{TobT2uSn9V-9+j`?exYoR)3o
zw67m447&UKtB)cn_GVQ800~6$H<2{Il+DEI9R~Pc?eg<~!NLLXgDBS
zJOIfle*Jd$*R`G#+VM<@^-IOeAu4F~rHr2UGHpR_3kG-R#0
z1t46z%!yiSZb<{^GrEQg&NN*}f54}FgM!nCCtC#ff~jWWaD?uA4fASSNe#JY;Xfue
z9j+M2NG8~Rk?zOxQk7mLcUbp}U;<_z4Sm|4#5q6R;+gz
z_XrT;zuTE#4lPu;4YMMQihU2Tz!b{FCmaD1QG6x%WHeU8}X!
z#hatqGDS*eHnO>@GPd$%s^Bbz>+h?P)QtHUqYoaeb?w2rC0wEJtEMwIYH&ZFDQSfW
z#6G>JpNvb#y~PE{=~&A(4H~?w4Jd^35pZO9+rBy2=#yNRi#bng^TG4O|A_I+R8;1C
ze00lap2O9Crm`A|)7uiz+boIEv~#U@xaIMxXdvL;hQp(%zK*xDvVtZ(*gd!7{hJK*
zkLd^9-Uo0-HB_~2-z*(Ey)@|^QWm0{0VjUs!x$Fr2b+aQw5
z4TN-)G>G;~lr3`ck36o4Qaby({%l{|xZ~Xu=g&LRhN2EWINHb*8W-6r)SB&UQh%9y
zzzC5j*@g3p4LyOmhwn@W(Bz-?1aTIA5hh+T`2g}zb}n$Ud*$G$8Bj*0zxNW_XVv-_
zo_T5zc1@$|UTDEt_?1>^YlTI3cE-C|eNRt8Z~wPZ+=h|OjSapBPa7}ZGTf%;D)
z9;*5-TR`f~{LqPb@>w(z-=DWLZhiFp*SWAK=PwoF>k5ykYM61#E)PlFH6CscE%@~X
zK?Gp>1hCM~N&GNW(RdwY6;-JF&K%6wf`Q)x1)yIgKYc=vX;t~nd4So#TT7>Iq(_JM
zbHgnHI6QmIgcQc%cwz?FG$32n*yVg+6I9O7N1L;6LqvB|fUhd%*2Q}O7nRtLv`_96
z(w)e5&IlAp3Ca-Kqwrs&S#nmknQy@qDFF<2;X`(QF#b}w5jLIYpc||`Ei3X^wi;Eh
zZ*4d`QLulNfHR!P_V%mc@N+o9*MNiDiKp|CHb=3~X~P$*UGoKz
zcYXV5-tXIa@OZH7DfcIVEK}Z2rKhR_(pkUc!NU3aW(XsCc1TY(r%4=0kOV47>gt#L
zOv+O0VnBuWtHnMOhP0265sP(!;*YwlwMQp1kAG95c>Ue~>TkOiglbc`!aWe0N*Y{o
zmFA+!P5GSK)uvy}dvIza~dS%%wcpj1D~yqLpz%EX}LL92;d*!-wH5INenv$Zi@^3=e`4JZdCn
zlc93r6IA>+I4Zcu%IJ#g-=RvsXfn`f$gIw_bAU)aX5@3aY0P?QZ%DpOrWm&pvh4=?
zMYHrXWQ!KX*6XTUdWWqE(Hj)viy+ZD0DfSwSj}ayPpjTi%2LmXEc7i8fL&k?9X3=N
zi0}*IP8W=b^nBn85(SYpqZmr1#0*`5e!*x4Q7rCKuDXY5;}eo8whIsTyJ^Nvn9i5#
z`^;$s4dY#Rfl2z|;Yl=ePJaRgd!Nj8OC^AVh^v)o`B5u?*^q2unF&@hr~#BI@l<5G
z?8WV7<>5f_0%8N7%#Wv0u_QrkHy5_Am&;h+6b1GQKZ8}_F-G%qtnOP$cP!;cyy)%p
zW?F0h8S>>mJKw*a4npI}C>Ii#PiK16E>}9|=$)s6_ZSM`GTyA8$~D4giT#;pJ5k#;
z?x<(~z$Cp$wA5)clqZd?{^d#BqwF`)0!
z3x|CAe)112CfWz&uke#8hI%!Mx#P;E5JwQ%r|0cOIBtY`dY
zoPq7uK6y;$J?VO-KVcab%kyX}R=S2oEL6UyPw>rGUit?TRKllQNN~kM`?YD|xkYn{
z)bez)U<6{}trat2EI;iO1gaAA!`I3p({S6gFsV{j;D=89#e*9@rtpo&$vs};qu8)B
zY+sa+V-x}=zA2z&E`H=lLeHSd=`~lJkoSA47~I6}a?VTLY^A3~{_k_tyLz!XP1g(4
z80eH83c3^+(xDHN4=m@+u_P0yJnl2iAj5QaJw}s`@jJG>?TdQ&t($MREkBM<;fZDq
z>K8a(4G!WQec?pugc&(R9KW-er~oGzl~bb9esQc4Puq<=im&>f=Du_;9cJAM)Co&KRNLNduP^AkLOQPbW2oh
z-dO|GH!fI3Bj(li)9(D=IzKwjbcUXASsvxMX2$0SAg#scbkVKw!3SzQgKvuhG4*>R
zCcJ;>!VX+d>GVBfSFL1Ao3uH~Pi&{b|H+Br9qk6*%x7V%f4)oU!PF;EPJ0yYN6pv$
zY~(E&>4Eh7ku6~CJawfbL;Tkz!5(-bi|VFCbq$9sZ#?dnxskd9dmc|_fHU2I%#(!H
z6&6_=mlaFM^qQBC-EYu-_V~^Np{=)>t~hF_epZqVT#8>NbUrf(2MgGzD&rrpZ6HS(
z(7-mt@!=MOZ7_rH&~TCJvu90;J()2*StpaV<%vq?m~ejLr^_jZiXz-vVzm0jz+B5=K`~U%#WvJ-dLSuNf2XPCdHQLe?i89rz>bpe
zFeJ$g5o42ShM^30fP)EHDf9up>3qc|vNjTa6M_gVZn9^fM`%S+0yH
zd1hcoB*HkpKYG)Oeg)8DLXUu{y4$>h#a${aup)-Sbpt&2vR`dc@vxz$DFqy=e`JY~
zzmB08?r~k%%%^LBFTTi^*_Z9^&!26}KMv)4{#p3KO-gHfKII|TKqrLVG;nY@9(<@L
z^Xt2(1$#DzR{+1B8t-9>EQFc#E?<@b4Qi{G;+69chsUA0xe3rTHY~1ocm>JrHJ=&s
zbND*+nc&-Iz3qoi^Dr##vCR^f*fzx5C(~6Q{QL`VR21Be0p<0`!W2DoGRQi&`|NgQ
zRx4UkZrGyjx82%^?J|vFN6b_ec+SXp-V=5GFIPDMus^wwI#uhEX)dvOm^UMRH~Z0w
z56Dr!;csP$1R3%kFGs?Qp*q6LB?&pwi)R+k%bw>;R%W^mAHKKuV%$Lr>jU#6!TgXJ
zpZO|g_qn2Y<(AtjFQIIfY?^iCBqgqnZlMZhp`$CCj_*vIN82bcu2I9b-a^t(&<6kVs_dLeG
z29JMB82_$4zEd*(qi+0X*Z8l2@!!+q|7?!`*&9EAvH(FANS+1OVL>ceP)`;tg!T9B
zM^IVFQWmP-<@Z%^)PB}Qb{1MbOFj8H2W$c(*sRRX5(hwb00_fIsO`J(CQb-<%PKNi
z7hNVU0$Gwo2kgv*M2NI>Ht2%Ogba;by*?qMGpP^<#%*PZn@lPsx_!5G6XbfRSU;&U
zcwK2LOSyYeZOd(^*G=uiBwo;c`-hu?#FUoh)T;8-fp*B0F04u?aZ10`{T6x3pnJ*)
zR>a1+3OZUFf1NrylP_03Aqqphgif31yYuO|YS>H&M!Q;2r!`6^&?_!tCIvQw9(sb4
z0*({HJ;M&L8Er@KH}I5;&WyX|jECopXUL4#->n~Y#;0`VSpAG|_ssFZ8NZns|E-x5
zUuRCjW{HBc0rInfI?anPG>IOaxTGhE-_>-DRJ&BbuPJdE~S1hwR`T|;9T0w-1)7!3t#6h!sbbW
z^JMvXiq3qx<$Q+cJT+uKGjTqPI!`N|s?8$myQX9h}dbna|&vFZeoN2wR{FE?kmd
zDAHLdwp=LjTqq4$xSY69MqMZ`U8tyExYE6Fb#S3_X5re_Le##+J;9|A>VvWvX
zt>t2!=VE=xVngC$BXzNcX%l($i1D?x+
zA|x;zeBVF|8G$gfQ5tV~(1Ona`(gsjXa
zuFO$a=1W%=>Q@%KSC$4>mS+-8FbyhblS2sOZw?bB5C9b}v
zu5OpEzNue*+r9d3aP|Gn>W8h>k6%|m!Pa&J*FMXy?dq(3v0U5pT>Bcb_APPkJ9TZp
zbnQp|+RyH_UxRDEXV(7NTKn^L?EuCGgxDYjHdvPpv0_8L*sxGGJc*6SWFs%LQ4MU4
zJ8aIUZ1gOf>*F%%zc_j$U_B5B_^a(9p@1+H`k(UN|J?Y$lw|3z;L8bs|JDi(TvHPH
z>vJ(FNC^19NzFI_D8K;g|6SbyApF-hbIJaHXPb$lTKt;%{?E4AJ^B_Qoiq8r*k+Ma
zihpghTDxn1Z8OgsPNm`o;QptxH;BTsO#x3G=Kl}dEWW;1HuL9Rd$2_=?;X!C^iJK^
z>Z?yk5KhVE&U!v*rup(@{5RBUnx~glG@h{uV
zH{4WSmxy;mKbyQXF5}d)C_fUo?g~w`C(|iTYvt&92
zv~p$QmP?W9`Qq!x5EZH$R>C~@2rxXmYpTHHGczbx$gYQNt@oLsHL93weEFHa)naYE
zK{W8gCD>~i%x$uzJ%tqe(43BzpQ6VXXeVPgDGKugQ``oVd0v)WSArwPjlT?QAuLeO
z_zEuVYg6d-`*eOD{nE<#HFNXiAbYi`O7}7K(6-)QwQNy=V=Awk0%xI4WCob#B(J->@-MyM7+Aj+yl>FsGim3R)CMfH$r9B
z6Q=AAv8TU`1bG2&wnnqY;A~m{^4A!?V81CRt$Lh$flhK=Gq?VI4^6HkkBwiMsqEaD
zd|~p^xV=%Z#DlL;CrkV#t(zNkHM6I8vv%859jZ01T?d#u87&TjTZ
zxWN9B8#dq=KPry79r0fQ8s#&m=q~db7x0*i-x$CpsWp1FU{y
zSX$itLdKU|iI-}81uDrsc(~Ea@Cg`SU_!d%eLDw`RccbQh%3jya&3-~r+zi8IO57d
ztusdTRc}pzlB75FqJ39z
zUv9&D?B?B%spfbl38lawfk&b_SA6r7;04=u>i#l!9K;iqC!b41-5g^noE&Pln0>--
z2g`h&o;((8u^Ldd$>*ePp}UrFDVAJaX+rpWJo2o*gxx7A3(uoV#
zPUg{R8x_6CRD7mV0R!sY13rTxS4q$r>9I3??pIlp#@isJb4}(v@ojz?2k$XmRGZTz
z4pgNni9r1tt6(tff-u>hD2UIHiT!$Nc|-j&gDB3;Q`tVA3T$)9sjRyTeaYLbDFcVl
z`cnk+YQ;ZyYEfNqLQ8L`Vsc6{e%WB@Y^BU^X|Mk`-*H;}7rwjPPyQF)aT)zP-qF^#
zt1R$}9T`flD2Ifa|B83F_RCd$gF(l&X~_@w@e0|o!69-u4Zf`kf#8jN@13W!kvc=6
zhFUs_#=~B#46O6}KKPx;2`OqQ)K#FC>ygF@ae;NT-X@pK=h!Tx?e;dKm|*>C;8CAIX@gh7l>cV(y(Ede)+3jTxVM<|I%w#mg|L)
zVlrm9-I^myI`VgWun31(9_x~q;sto~UsWTMmm3BRCp{nvLh0GJH@UrU?d0)(#Bh3N
zm^RAdt>i*Kb4q-6l~#9w>w3J+=orrCc*QO%Tik(W5TwN+FT%_gX*?d=ac6viaQ+4F
zp2NN+f}d|x@+HwTQ-iH?_s^Zs?^-uLnw&0HU)d)>nJ7qYGSe@usWe@E>O7btoj6r)
zh4pwl`P5M8(1bSf-${?V*B)2Q;N~=J3~Z%L`kZ@3!vYb38`7swaL8ms96zRpeqtk?+Wy%3p#vY-DCTOxn7b!
zj~Z&jLw(r%8VghqEMSJjcK#hJX%q_mN|JVNG8Lt?JH_D4YXd#EID}jalIgV~!C0$+
z-h|xb`F`;}qVGTh0j(-wYyW<`h%-2kJAo+6!w$dAC(}ub9;{Fr(d@fX++ne2_xQ6~9Y?DZt(kYy)hJo?>Bo4A;4Nt%
zlV3mQv|^uMjA_5-9bBzd@wL{xfPf16tt5k*wA6yCNpj=BQV;1tts*j#UQy|GOzyiq
zjxtdpj{+L5&BM@LqJ7aS1Mu@PgPK;c8Kq{yW1~2je9{f>iTMXIuYnRK;z;4e2T+u@
z!amcn<3ol;1K$h!?cO2B&pB&F@@FO>iUijM{hQ}l&rZ;|lGZs4OSYQO2gk??i~*+@
zAjqJ$z~)q#)0f%EL^+$Ldt)M!4pg7BqO~@agIP`q*}*XQ4JJ%Ah8KA+BG{thjtzyc
zW-g!h6W1*h<4{tTSV;?eYsvFqUL-C7>p%qKw@I@@W8dyPI4^hX-k4J*k#>LSpO4Ao
z4_3dAeSiGxpHC#7hirs%vN`Mf2bx}+v&;>CMWx@L>4gtB6`g-PJN{?4eBAgI&-MMW
z2MM23p4x1iIRBjV7T?R$d-T@(dh+yxKVRDlAH5HA{zYjpnS?eQo39QPi7yZYqyhjRyir+-fl_J^D=
zd=zf@y?y(<+2r`2Zx0Us*(p5wZRF#hkMCChbe=8!@cHojKf?zH0G0&SCr!;^J`G*G
zd-pn5l7y~Z31AibNT7}+El)5xmHyv>c!+D5WDEyH@BVs$n{
ze?P+oOO@-PWKLw*&0nPQW+)@mYZOzP?oti+sa9vHZa$fz$(h@V8J@#r0qIQt?ex>-
z=Xu&QqlU?E!?G?3rb_jM8K!4m*v}$0osZlnofxM61IiFZX4!75SRr{64u)wMrE~jp
z=N-bc&SPoi<=IzgX~%77Ry`Ss*lcVWUrspfdO0ygKTC^7ZEnnpEzj}j$?nk4?J6e)
zZ|7Jh=N9_#)(W3P+GM9SrQhhuZ1u^`!sfO6@4*+`LE0J`6DF0ZA;Bn>}T(6U#tnwY1Pd6
znr!-DpQd`O#L9pa*F($2mb~B2c(qLuODS;InXYFip;
za9PEsXuX{7kX@=Fk`-E!?kkc;JeCt|P?j)~cB-N{VuYHxP7(c4Jm#HmSV7h~R+e$B
zeCTXhW<`02j
zY@%%1iCl!Jd5`7cOD_lWB>pp6qmUN_l|?eACX?Rg5@20grZXb!H{FR(DM5P+=9
z)Vbp?02T@QxZs|i`038bue&=|w>g(Zb!kvr!CQ*ozX@jheEv7aar?_SckUgnbrQ5{
z5oq?M-zRv{Hc`#i0bg8XsQLb4x&~9TtoYZemQo8n+Gsn{W4k9UleTbZb9Nigqer
zh8WE8`o(DsfV6g_?y$In@ZKwARHR~n>DLuScRpKfp{J3YDeI1!{iOqpbZJl8n)7=q
zl0S22WbRV7v(7A9|1QXeFQ!ienyL4sDVasPU~)I8kYDH?kf&iO|1WsjT``rU><3#D
zax&^1l<${W4Z-@8GW1A8aLj3vF^fE;xNT+xJH)yvwxN>Cq;n=#(*=>gK;&buiZytYZtB0!&g9E|Qoay48TJNEio%AJzS%}uFq9CaA=*iz1$
z3H;8+EAQQCn4ZZ9#i}Q}ZyHTV962p5fkhDtr)|Rf9E|sx(j8I>J|L$)<4fU)`*}WR8xpzYU)XOQPso*;^Cu6opoYOhZ
zWa_Z)fz!yPct6p-5QD94HxAuzdmick4p}l0T%}!Fj)(3ixbw&bOcMsBOG*xc#&qma
z8&Z9Tj`m)^czT8oUmG2eRj%J6)!NIy*xbmC*I#t)m)`W#ihnuyrlY!(_-@k9AOv!}
z)3C_rvw
z9Qu6_A382GBF|hre*5pV5MZVc)2yPlH^BFGo3B)u^Z3t`&r_gfU$>+^sO8{hXdn5w
z+^`yPAbp6z&Oakib3SX@KYgIzHg>oj8IDBgd4=0psB}o02G%OCVXg0a>=r>xPjndv
zA#gO1_j`PQQ
zvGda0Y6;|m6{ZzZIIf;tvRl&ZCvW~6Qy>`-{=dhc2V-$n?&vq@b%7+POsTRQY@jDg
zrK`NA>NKU&(qsuA}4_BL5G9?kUOALE>@Ty(OY=RSr>GrpLCiac*;57Y#b7RTw
z3wPeA1UF~N-lRcQh_zzAY!`2|b?s-N0c1un#)#r^I66*6Y(>dhSmerOMe)DGRJKd%
zA=`l+WX9Do%a$Y8`ayvhS0?oIX>I7s6KYS-KJJObXUMVK
zsn1G#bL!;p4*ia*Oa10(-n=+gkmC>i!8pT1nV?^qU1iZzLThqIO)!_QMQGD9t!D#d
zB0sc!?Sk+nVD45~EI1an?3VuUSIbDdM(-Rp%_HX2!8_=3
zeLJ+7t6WhoHJQ=2_M*>%jl<4&_uJ)mw#0XYaP!N~)jr&*^j3?oyPMBfmNa~`(DmJh
znxXxQO#l2#X&!52-F&H)Zo`|NH%?1GZ=1bZ*0JeDXqL;2Ty=zJUI#6P#}IkHVXW>*
ze?}fS@JZ`^(DAvb_LREMZheR|muHeabsrk7ez|oPi{7HfK+9xgT5l2(Ox{;+28eCp
z&Wa^y6qpzd-RJtzNx;j6|42qJ3Lltt)y?j|$7yj)LYmdQ5t_W$MaPD+Qov2l|EvLs
z<74p91i$yBanB?}Sjq>z=Qm=l?q*(kITqa-U+FaK!6Wvle5>&kUJgM5{Ry3G9-_nuOwV>V7v*H=UBQ#|S#5~KBMp_blAK8J$!jxxw!I1;
ztMi@WcSrA4zHdzmwrjGMRg~0}KkzYD5&tB$|GoFg<3hQswWv(l_wKBIf*0#-r$X=d
zHh*9T(HHG>=+!$@_}T2zV^;@*o#0O6E*h`sn&1hzGlAEr?VPC@X1&p2^K1%g9sG>M
z6^gUvuO*?IF7j+UtpjQUdYIExo+f-8cRg%X54N?1JY51k9;(DUeC|SKD%8|@k#M}&
z^B9fA{c0!Ky9=l_<>)l!xXhM%`#SL6oal=-t|0BL0ff7e&_F~DFZY#6(fcHB-iGjRB(D1m
z&Mh`~EyK|3wVzH5q5goux#Pl(3%=0P!S6dvH6cTF7@TjJh@W3Io?&?I6F5dm%1#Nq
zOmhw=cb*nD=5i19%?6dIlo=eJIR+v%B9U=gV)+}~*Jf~UGtYj2hZe<~ePwg%7GYeJ
zs2<@|(+wJ3DH|SwITXY77N;AJjoh$w9;Bg%kT~@nb*2jkbBEI;7-L9>Z{oR!SscQ$
z+}fSAwX;bpc#gVl#gUjp&-JOE`&Jxe=?FHWgn<4=Zk_yjCTz!r
zQ_>wWa6k(C;39}J%dUW=3tUFq5a7Fk@ay~XvoUDrAnv~rlAP_dylwD(GoD9avl_WV
zZjB=OY&9i=lHJLuydt;}gnN`wbp12t7C+Ay0yk`fU&gjbA-l+kQKV|iqy9#qM=6-#
z!og-98f75*cDOuQ
zxpje~dm(t6D%oE5d`#NuA?sy6K2LfcTB8bFcMrlCVc)o?j}jgz_3o4SMWXy9UbFm%LLK>{u3e8
zxro-g`3k(Lm-wS!@1w#Oxo=!TD0s^d`BqUMXxI=OdG4rhL%3btAqHx8GMNpa-(0GR
zFl3!Sq}rm%!zi|=m2BeywpJ!?k84lK
zzCC_P1z#u^K50oQRK~Ndhr@Sop6IlFzp?nHkNV;viDy&tdf!l2FGcV%9QOA!^fJ52
zV!xW)2oS{*E4hw$#jMr{dpcUu*gVCFnh>|9qb=u_uV|5(`PGHWw+Hll3Tvuul57fs
z#p^S33Jdv8&`j%crS8YPGRr5w#yxI}sQKJFp>2QCrc~KHEkZxAwy6SFPZG*FX)EA{
z-R@eQ?{{cFQ*EtnIJCre+o~TrO#f6If_FP8R7fXkBX`WVaH7f}dMWB?dD`P+eZ?<_
zU#p~!d8?HRoi3jeOq1<{s=m~2a_-C0Jrxs@mgrX7C>%3UhyTM(?F~r!dF#-`Q+}8*
z-S1e5HoHFH*#;GDPc@8rmn?K@R^fhW?Af^oL2sT*KKr=odoKuh;L3Vyi;DWGS
z89nd;mB)7Nd-Fe>jz3Px)BQ)%j3=m@3++7mWn;&O`r;WW@T>oF`5+)U_8IRLPzu?r
z#a@opY5O$6Y@4Js{qh}sD-l~eSn%b0W`dggiAi-xi5#{~f+!2fyiVMF$_mxe$SBkz
zsX#{tBUbUe*WI3Wm9?zdUp9!{57?MeaH=#v*Jf8Rz$fqbG0Zqwt3GR>pl_hIb+Guv
zrTs_E?8^$|GVm?ddp1eTjRLk!VxVrp9WIgaR{<
zd}&?Z^JzPcADT6(Fe~63QwbL<*NrI|ngYC^L
z@%I1LDT%XBN#xfl-Q8QkhxKuH2JiI=EMT-d6)7x2PRCXCr}3$AU)GRMRgT_f6eriK
zWeh;=FF)f>&k@2!e5#7pzbE5heC9;QTfEBw&*DOq`=U#2?XbmF{Mnb^0s^`x3gLRl
z-cL3Zmvmum4#}p#ZeCTP-$MRKaX3CJNlM7PLs`wZ9X{h#aP8!cMk_h%>(3%Y`4%vd
z)|K3L1(O*|>{SXCKyK_I
z@Mq%TA4S0b#@7*6*=KWml}Y^Wd07Fu{aU`VqI}P8nHhfk
zxR|jzbSH-PQdmXcE_>cWU#TpluaUFC7qXRh~uo5HYThN!LEyLd%=5^P<
zqi*qViT=m1io6f5OMa4
zAm=E>>M2RC*21Cn#~bM(}a!mCE$8T@|7O1R#7-_+QC8
z#4aFeOa{y5w!NKO$umvR49GuJ7hf;E*q8E1>*~_>Gtdu@Lsv42+(L@ZJedRb>pWp%
zsPIND7hBb_XR?n3*+NUM!P2^!q{)NPT`S+es%6tvHG-$uAqLzRyuKqjW||1opVjKI
z5(Y;Ha!7=o7a!GU7CCckyvu%Hd)5Y`nR|P5pi$-8O?&X0Fj}T
z&&NYlZ+38#t@RdLC%N_hNPHh$6Gskx-3@!w$MQK-w{*1vqOYEcUC~SC%w1dOPmA9f
zp0XCUjAP|!iJA=$7di#8Yhv+U9!w#kO3i{UG##^D4Kvo+lnm>`A_v4Hy{LUc#}QGY
zhBT+gK|%?L+f2Dmz$HD;D0MI0xu|Y*jk1Uzv4@a8Hd(OntgUD0s#xoqUK2fOMe}G_
z7}RjWt{IS8#=UBudWCnluY0IxqasJU@^a+ZXg#VJaGd
z`)t*766h&>3kJ-myH-h@of)b+W9Oc);g!q;%8{wo*;fU=%}R*Y<$|;5={VkQOI!Rh
z|Lb#vS;nDmjdFg>Z`TfKTo1f)Dlv;k2yXefj-hZAdmvLK`-<*&PR7O;wX}InL{@va
zq{jXLA31-DXpgCNiheiOSC?g>0_33n%y#Z2Cya3YMFY{jh`HEfyv#c8&E@T)1}$sl
zEca9uy-I@^|;({s&yE2}$2xoOw%4KJv_5;)Op&WCu^}`|O0-
z8sa>V-3~C>!W4{O5>r!*KWylyDSjyo^P(q!H?q~&`E*?J9~~vbF;xOQgK7-pxqq
zd`|MIMq6q=n%L!yzu=OWh(M=LE^*o-d!^dg8#va+Q+MVnO|HW@X(AC0Ve8349ZH9c
zTKlOip;vkx3@hWl=42t;2;ir;Rwke3AA8y6pJBcAfAMam7dpr#>Hi0I3*&7;eXNgXV9uAtWs^m-Um7tR|s4Qt)L(H+}+
zn0nJy#%Y~C;E}S9^ByhiTC@BeR0r7S5-cRi#G`%;pY!%eE9n%w!i}_RH{IriAXyA6
z)wno2wI_o@E5RJfwi7uCWIy_V0jGYHdUn3sjd;|9^z@a`%y_M8fm&R;;iIog7TGBj
z2(X!AbDc-+2#c%Vsd@i0zgUt|eBbb?h_t(5#VqOpu{!2puILuT)-|Ww?T)+6yl+mE
z{OR4Oo2r4b_9$L&X7|V@prtgjCC6$%03p1=gN)hYiF_7^4m8men9Szn{S1^QoM7-G
zcCw&yn28g3O*M~^m-e!?njLx;JkH%Ol1A%_Bh_!~7PnNl3I
z;c(DPGx&_;kfZuG+C%__me_OfYma^H&eOI|<2B6Nx{_BS1Gmx9SqVbPw!vz!_I@4<
zUSzeD^l=7}KEadrQTN%34!FMM$adwp1jf}m)ga0@4aY7`GxX9)gET}qtZx7CuAnt{
z`3V@{L?ZfRXSz0`k~7H8yyw!eP@PL;qkTp{l(iPMufFkzgJYcb_4P%`I^^;|9%798nB!Zys6%y$0;`lVE7b`
zLXRFCeA42LCI_A8EN)1zyqgw#&Hu+!2Zt_uZxip=fNH6kV6XEc%{f_q{!(j13*l_y
zhnB}d&D>dIjol7K5?CS<2Tn~qjT6|yf~XTLgNk@7m}tFwcVIgRImPjcg<(w@aKe{sNd2bkdFHR1Y;%#Gc@fsAh0{
zTUNLsr0lIJ_Iwb-UbY;4ult3>z2$`GoaSW_`95vIj1@ySCnQb)HSx%}YP?m82x)(*
z;K*GMRF4$<4=T1Bi*P#>M&e+ND<}JlaeRnq;YTbinShJhP=cAYWJ!&clVk&02!T5XoIM`3h4X5!sWVZkZTSvS2Bs
zaa`f-q}lM693n{fBM{1>A|l3Z{jAf1C~1%+NE(r6VO*(Y*NIZ%mUhxz!`nSWx*HR}
zq}olhtlomX5<*~f{AvZ_-DEjtiM$Lv*9{tK2;ff|=luafyc;+5C1sQ|GYCQ)ZbY7S
zYUYh`9$2mW*eXxVAc58%P#tG~6$Bf@2Og*+x)6>sl_H0OGiKKLQDz9*1g}RwZvsh+
zLgINh&bvf_d;4pL$itWLe4o<;;7dNMDZEb^j=9x2?nm+O=;$&qB0ZXSj!x>)Lz31*
z5ejZA?zyiT4$!T+w-k|)EKn9YcQrWoC{B4cE+5tG0ot}6t%BM!Fw?Hc*Dnp8Rp|&0
zipVNk6lmM#3Kp;nw|-kLGOzXIGV*oe4OJ=eg$DNujphqYz7*p36`AQ5S+Eli@kQ2U
zMYi{f?B#HD`6)d0z68B;!A?dN|(;bi;43WFMPR3+E@D27sgIJq@9A9$d>w*;j`yU
zxr$0Nxk~=;G6dX!TmS>aHk8@7^Lt#T<5Kd!$z@)^GMzK_*7X0G%Z#imWOJE6FPr@b
zm)Rl2RiNTq`YXWv>vn!|_|CQ2TZG=sI~zh9zXHq^UtI!+|NEqMU2kPjr^V)y0Ks
z>%510Y%Y`iM91n)^QdW#R^(Xz#bw&vyZv&cR5SE1E|W_&=&jzb$8hug%R09$H;SD&lZoXJ)03^W{KSy<<;Fbh?E9(CP|#Mkx^^I2VNE_1FW^u?q5oquzg
z@h`_~T@L=@GM}W{eX$mO5G{he{b4D|+;EZ2Wh!|QyM*(-2>RDg`b1Cv?_8#}ZwjAP
z11lYUd)8cF=Z|^}JPlPPBd^l7zKqFhZC-S+k;6lNH8iD>*eR2+#d{ungAEb&UC)D+km%y*Yc24t{GWhjJziz8f4D^^EcF|2e0
z+|*b_+*~bqnB;O{F$Z>na!23HXxcX~@Ij~ngXfqFP
z4YMtD%55&4P^MNSaY8u;#1Xtn>KIx(cJ?LfB1nDnZA+eh-}*|Pk?(5eBErC^R5S(?
zhfD(PsF1ojfema;+RlO>05=GWfUEZ+{f9BWZ|iSGs;pB@ISyPG?cq2UG`l5wu9VOt
z4We(U(x3Ct(;vP#;-wyoAyY{l?gUdZP`XnkLI`wy9@Y8osn-dSQrsj6D2YVxjC~vSlBg0lEW;4d6E8aHi!hyFX
z75w?@r(w(XQbjBnw^fMv>A8gnPyTGOdX{I4vTI(P3mObmJf1!b6c8UoDSi1TaI@D-
zaJ1s{?j$wSkg^p7a5sOwo(hDy4uOv@lOT@t`-+VKFtQL>=j@w_E2Sdut`mgwz$6VS
zLLWNU!zV|(y22y@lL;WsAl$)CCczIhhreLd4}MkAY{=ed9J};6ov#niK{29@S}6d;
zHBNF^Xf1#IQ`szFE1U9r%BI(QwzBCOQ?jRQnuh#`vZ)+lbmE)mT<*Uro3IH3<5CC9
zCiJCuT5rWB)_V?grlbhr{Dlq~@iMA>;i+F*)t~kYlpIDiN-0_IzTrK6wEk*p7M9WCXvXuEevJxE@C@R^iDyox
z;&fnTRRkxe1P-cmWdadU`s8|d@PIBV-&uTq-72jUo(D_4B(0wd6=_d3P&VyGx-O?b
zqxatK=mp6YB?}k@D@G9UkL3;8yt_u0;)mTie?L7TG`yO`4ZtIg6M>a|)=EfcQ-p5~
zT;H*$>U*wvO7>s0=IxyU(C#l=lT<|YcaqW4(7$v4^qJWIY-RmK{GoW9hp`@Z(Jh)kJsT)Bgq}W+#+bx72x7b*z
zKn9zk!bk))
zpg6l`%1uyaBxr0B%CQ6x)B*rGNr2O*a4;S@8gNWoj*88k@EIa4zDv6+x5@*sApn6;
zYX+AL8o*m~9RMTI6IDq-3lIs(W6c;|A|HVn7Q{xFLXpkKYB>2;jL!pSp_w$%_I;XvgqY;uIU*!)x29k3u0E!{i!GYX$0EjO@
zo`OeA)O(0H8rD)h6pO$MM?EURa!Gww9)3U*G?X6+E{8(CSz2=WIZM;fN)5@_2GIUF
zKuuM20KnLo7OjnQQfp8zPPNvjMpX5I%#eP^CXHoWJT(6TG<^X$-(ze*6X!RTq9+A*
zMoodSONYi=uyve(WB?~i7%bOdrNBJS6O_t_WEh1Ihr3y`cSMS$au9(sU`Y%NlQ
z@NXYpp^Tz!nsM>>ky-bV@cShA+EhTxIPhpuoFp0qA;jN8dyG1rEJK3|E^?WI;mlHS
zZMwC(qs?gmKFR`+G!7TUDc%%?w*$b&ByeB5_M9ojXDTqsY0uDnu#@zZr69VBj98ZZ
z>~#KZD`=R;9ohow+d>r9VN;+7vZ$xxdw5dm*we*F=7f$tsMOix(T`Zs)Y0}vXgNH1(fD&{d-VewIG|j;A0?pK*}DVxur40G79|@
z&;*p5eN+!V1qvA=VQA`*HmN;8b16)J>CnSF`V}qt*D3HL13_kg0-9*8zW_~I)?9uZ
zj=*Ki1~l;jK55yrW!WPQsPcNw0!u&{BVlltA6%fz#Zi=V4Ujv_FXm(JoOKiuXf8K1
zEtxrkJh~;Bnu_Q}tL-r1lLBB25xz|09xihB6hp*o@ceAdP12Bv3Y1vrF}u$)gJ_^M
zS5c||5H?A{I*pY-mku|;ym)&~;V*)Dg@OFYU&1CStTx497I^5Nh0Tz7l&x|y6KDw0
zMD$X*t7BTZMH5czzvjdV(ZI_Z(7BI|EB6GrZilpvMhhN9dmS?_7FXT)W+0>$6l_
zI~T=)1}M9n=>}htrva=eC;-WOkItm2v*}Ek4`3jp#0d@bjE0gu8;ZF&*mUMwsPoC)
zR_DL$Ok(}8b}7lPHn|41M`t=Vu<6VoKj%hst596&bds{2k`XP#;34Qx;1W=676Q1-
z`NOroFta{k!$E_T63!h47WR`53a@XM)F4**QmcG8Y$caCZc(9OVqkTKCL7SKn7%xp
zbdU{b{{APRi4TYl;(8zkXX*im#kBSSOESbhsV}wU^GzFZX?sp`$(s-!ja;{r;lRZ!@nxmI`su#x{1gILTwA_zG%+To?IL|pe
zxAlPJnP^$Ygi=nA8fgYQlVn}E4>Z5T_j$=Q^(D{OT{PbVh*UgXIdL}MmJ9?D**{be
zVRZG*dvNszip_00FMDIy+$KprYC23?vqpsu6NEuLLmlmGL+oV0!?J~TL_k;r%8XE&
z6c75G3JAM&28%mOwkST;*>)NZWat2nZ*e|Oz;eYWP2s;7%}+&E=RDifFSRS+ESH4ZQ#zx>1+VMVoz-ehja2m@0Qi_K
zpp6NeHl_T-(7e~4Rk%AI@F${~cd=?Pyke#N%0yOnX9SlA9w&4Y40uPMsFr+jR4vo=V5
z&)mcZu+7be6ac?ttCeA!&M2A|hteF+iIk}|#vMF7Xn6v8`g=Q8VvakEkvA$(LsCAH
z6csB%YJd?@!^zTJ(6XTvR45jD`ZDxJ#wJwHVx(DgClU<^1l~
z`qRoVIdCSHO5qX!`2&0t%B0IwuVXD8>Y7f}+Li`iw<6gbnulf<{81L(dI=p@R;KUI
zHhwR_?}lpyoY^6=VNL8B;9wfmHHvwoOa-x?OddUsVZ)l^(j6I@G{3$XeXd(u9FjOV
zEdX_7(q`ef%{AgJ*R|2J1T%l3o07!rr$hH?TUu;Yb5TD~6L4il%-}<2Tr-_p(9u%)
z6Ued(<#300nly;$*so9EkB~_mQ3vyf!qOP*9rv8WR79?0f+E>En1(P7ki-CB%gFPP
zjNUo|k9nA6@_SmNLS~O>-sU%G-m#^2=PRZthu($SxuOn^N}aaB*-~(
zni$S^57xw~cB!G@tyXYKA0Bu6-RHQ!?4ORIz#Z(K}n4JyH{=x@7?TZRQ~d
z-_>O;gy)yApsVuO_;oT00iN$Ht#XF|a|eu;bvsbt7R$+sjm_RtuAqFyM3po7!1z
zVjqrem>rb>#p7ZJ1R*~3&Y_93*q>)EbQp_3=l^0f@v)A_I$hE)asdjA05pF*8MI_1
zPfymQvj1FHSD$ET?WU~4Xsa;BDz9$DD~ng{)Zrt@E)dcVhL
z!t4+3F`DByzL!fbVdLMPyUAuW?IKTJyj#&wpD^|(qq+5HzD!bPob&fB-gmQdGDyx|
zd-(9pw>gZSQ76czH9yC<#j$+QFr7A8hhQf)(;c~H0O!RzQRXy6Wt-yS7EcfI-4j+1
z4%^F7Ma`;~c*-hW7FxnT`$cWy>KD|-_a)}@n1hdCSz-hLUQMdbnZtTS7n17XbIF=!
ziG#@AE>DG+5&^DHy%9LSD{rdO<)X2&KDrFx(fj_q#N+Z?;43`u)vAHA4k`=6eqOs9
zwIOt%){F)$4XS@K9YOTHatW}SW{|yLY*}o5l({WW1(F*hQt#ix0M7c1M<9s5JzQ?x
zeggT~vlf2I>AN1lH}b_-tS`H^UM&;ezIr{Got)WA%b5egjLMtRXP)Q9l45VoH8nW
zwTG@_w;(qNu5;m^pe5ieALqLb$v2j
z@A#_9Fj*ylcv;=w-y+G|=8=$Ce!WA6g>1*ermqcSE}hnNjcI-=8FFHJxF$>vbXb^lc_1OZc7`khg1N
z>M?(~oCnqGOT^lksNNTk@1Z?a{!nkeuE>cyXBO`}e8}BgK<&SxW*A-|$CdEWzHWy&
zxf1gInpvh@_M%h5)D%{Lmuv8p5HTsFZfPv&)-yR#k{>UQ>W
zT=HDQ{Z7{?RI0Kw{(!A=IJnF9Hu0RBm6Sp<1Gj6w;X3Yn*FcR8Zt5RPRFzHEQBj)G
zwE0@_@J&fogN0O7J8Z&2cQHTBSlk+r1QBN}^KsQ!+;Ka8w*M&8S}}0KIXS{JV`h2T
zY;)Qx?@n}@%s@cfJr$5;o3dnSykM7Z=eM+?Lk~000!`u2S;sE}H*Ptg)>HDlW)erU
zEH#`bQsBPTwZ|U5F#zB3e*L$+sr1avhh13KqsOjP==!EhdpD;mm)GjuQ&~3uOlyi;
z*_^6xr^X)KnGVgnAN2}6YeFge1#!-w4orX-#2Y+~U^E3)#J1gedC`AOKe+4&cgu3-
zwQ-GgAI+}9$HpTNA;*@wpc}g(jH;tRs%MXqd+@D?9EM?(mggOz&5un4A5`U>Y0G>x
zzW?UJS)SU3@vqFy!jSu^xG@5o<6N<7=Tz(>_SSwgB)0IKe0E!g>c6nAY;Zbp^$*(%
zd?%ugK95&S72o4B?|<~#ns7K7vdO$u@qMT6>YC=p!E^3_XP-A7sr|)eMykG!{V%!9
ze7*~NT&Br6*0GZk2AdB*Exs=pRD77YQ-}YH%lz~r{osW!uj(IO`1bqY!QXy-diC(P
z-QEA-GRH}rm&HD*#F#*IsOP!K1cj3Z9Me=*%uM0*DD~!g5l^%Jg3^tr9O_QAB6ZJQ}rxlytiO!>*mr}Ux=DmC1sDx
ztkttrahu3AB%3PevA7&?J!#ZVKe2v2D~*zg?32$bc?b-OT5^xTA%Lv-%RjlySc__Y
z&vCeYl#GZP(M)(t&)Q66lA3N&15-p&?4y>2FE%8j1@&!g_qa@y98qjL)9NFT!-^`W
zV5x8GTrydt83t7P+KvzlW)R`W)>j>*s5MnSJJlDY6KkF1zSAsvhFGQ3uWuhwqM4$T*ekL}{*_Ku{7yb;
zbSV;|O-Rlv9j`R{jY81Hb%J
zI$dapV`{mn*maO7oneXYz`54xJZTJruR#<5DZK6P8;|v?xb^PSH;;O9%}3ipyT!om
zc3qUzNy|{rSD2aGZBK5@BK|3N``_l-{$CtG0|x&8-TM1?6F9c22KObLR=HAD=rfYTlo~0Q`hpo3r
zBhz^GOTL%*OhRonLZ5q)>HhS*gDG>d@hzw1D?T-y37yYiOkENk*KAGr9h0Es)BC84
zG(Gl$@tdIWbT89v3ZA-Jnk<*`;nm~RC8bAOTD>ytWx~g2`u+%CwN>+!;rpFv^krk|
z@o#xIF5F-H*fDnP#SMtbkFOtIFQoJMU_Spd^tE@{1lb<|%#0l_bv0w-RVgrj5v@`>
zD>)S+sY7nC@H5NgS2O8r;IAIclEO$K_C?6rWGXoPdKuy20_9`8iThk?%}U_^R+#mF
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
+
-
+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.
+
+
+
+
+
+
+-------
+
+## 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.
--------
+
-## 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
+
+
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
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
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
-
-
@@ -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.
@@ -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 | | Free |
+| GitHub Actions | past | ![](https://user-images.githubusercontent.com/66209958/115380652-6b56b680-a1f0-11eb-8eff-eda079b33120.png) | Free |
+| Digital Ocean | past + live | | $5 |
+| Gitpod | past + live | | Free |
+| Windows/Mac/LInux/Android | past + live | | Free |
+| Docker | past + live | | Free |
+| Google Cloud | past + live | | 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 | | Free |
-| GitHub Actions | past | ![](https://user-images.githubusercontent.com/66209958/115380652-6b56b680-a1f0-11eb-8eff-eda079b33120.png) | Free |
-| Digital Ocean | past + live | | $5 |
-| Gitpod | past + live | | Free |
-| Windows/Mac/LInux/Android | past + live | | Free |
-| Docker | past + live | | Free |
-| Google Cloud | past + live | | 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 ? |
+| :------------------------------------------------------------: |
+| |
+| |
+| |
+| |
-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
-
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 ? |
-| :------------------------------------------------------------: |
-| |
-| |
-| |
-| |
+
-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) | |
+| [Digital Ocean](https://github.com/aahnik/tgcf/wiki/Deploy-to-Digital-Ocean) | |
+| [Google Cloud](https://github.com/aahnik/tgcf/wiki/Run-on-Google-Cloud) | |
+| [Gitpod](https://github.com/aahnik/tgcf/wiki/Run-for-free-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"