Merge pull request #25 from jordan-gillard/improve-user-was-online

Improve user last online, introduce pytest to CI, and use asyncio
This commit is contained in:
Miguel Sozinho Ramalho
2024-04-15 12:39:59 +01:00
committed by GitHub
8 changed files with 408 additions and 53 deletions

29
.github/workflows/run-pytest.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Run Pytest
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.12
uses: actions/setup-python@v3
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Test with pytest
run: |
pytest

View File

@@ -94,4 +94,24 @@ poetry run telegram-phone-number-checker
# with pip installation
python3 telegram_phone_number_checker/main.py
```
```
### Generating `requirements.txt` & `requirements-dev.txt`
Poetry is used to generate both of these files. `requirements.txt` contains only those dependencies necessary for
running the CLI. `requirements-dev.txt` contains all dependencies including those used for running tests, linters, etc.
To generate `requirements.txt`:
```shell
poetry export --output=requirements.txt --without-urls
```
To generate `requirements-dev.txt`:
```shell
poetry export --output=requirements-dev.txt --without-urls --with=dev
```
💡 `--without-urls` is for users who install from their own private package repository
instead of pypi.org

171
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "anyio"
@@ -22,6 +22,52 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"]
[[package]]
name = "black"
version = "24.3.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"},
{file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"},
{file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"},
{file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"},
{file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"},
{file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"},
{file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"},
{file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"},
{file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"},
{file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"},
{file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"},
{file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"},
{file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"},
{file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"},
{file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"},
{file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"},
{file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"},
{file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"},
{file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"},
{file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"},
{file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"},
{file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "click"
version = "8.1.7"
@@ -72,6 +118,94 @@ files = [
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "isort"
version = "5.13.2"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
]
[package.extras]
colors = ["colorama (>=0.4.6)"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "24.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "platformdirs"
version = "4.2.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"},
{file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
[[package]]
name = "pluggy"
version = "1.4.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pyaes"
version = "1.6.1"
@@ -93,6 +227,28 @@ files = [
{file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"},
]
[[package]]
name = "pytest"
version = "8.1.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.4,<2.0"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "python-dotenv"
version = "1.0.1"
@@ -149,6 +305,17 @@ rsa = "*"
[package.extras]
cryptg = ["cryptg"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "typing-extensions"
version = "4.9.0"
@@ -163,4 +330,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "926cb8d034d6b5554d1a45ea61091fb4267210f203eefbf4da7604c3c19cbeb0"
content-hash = "e2ac4216301383f37ab71c523d371ed38b7c3a282b676277c8fb155219d2de42"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "telegram-phone-number-checker"
version = "1.1.0"
version = "1.2.0"
description = "Check if phone numbers are connected to Telegram accounts."
authors = ["Bellingcat"]
license = "MIT"
@@ -28,6 +28,16 @@ telethon = "1.33.1"
typing-extensions = "4.9.0"
[tool.poetry.group.dev.dependencies]
pytest = "^8.1.1"
black = "24.3.0"
isort = "^5.13.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
pythonpath = [
"."
]

49
requirements-dev.txt Normal file
View File

@@ -0,0 +1,49 @@
anyio==4.2.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee \
--hash=sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f
click==8.1.7 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" and (platform_system == "Windows" or sys_platform == "win32") \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
exceptiongroup==1.2.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
--hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68
idna==3.6 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
iniconfig==2.0.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
--hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
packaging==24.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
pluggy==1.4.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \
--hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be
pyaes==1.6.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
pyasn1==0.5.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58 \
--hash=sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c
pytest==8.1.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \
--hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044
python-dotenv==1.0.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
rsa==4.9 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
--hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
sniffio==1.3.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101 \
--hash=sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384
telethon==1.33.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:377107d77fd95f8c2bd7fce77f9d78da84c1ff58023f59e8d06035a4548bd36b
tomli==2.0.1 ; python_version >= "3.9" and python_version < "3.11" \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
typing-extensions==4.9.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \
--hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd

View File

@@ -1,11 +1,34 @@
anyio==4.2.0
click==8.1.7
exceptiongroup==1.2.0
idna==3.6
pyaes==1.6.1
pyasn1==0.5.1
python-dotenv==1.0.1
rsa==4.9
sniffio==1.3.0
Telethon==1.33.1
typing_extensions==4.9.0
anyio==4.2.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee \
--hash=sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f
click==8.1.7 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" and platform_system == "Windows" \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
exceptiongroup==1.2.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
--hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68
idna==3.6 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
pyaes==1.6.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
pyasn1==0.5.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58 \
--hash=sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c
python-dotenv==1.0.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
rsa==4.9 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
--hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
sniffio==1.3.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101 \
--hash=sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384
telethon==1.33.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:377107d77fd95f8c2bd7fce77f9d78da84c1ff58023f59e8d06035a4548bd36b
typing-extensions==4.9.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \
--hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd

View File

@@ -1,17 +1,34 @@
import os
import asyncio
import json
import os
import re
from telethon.sync import TelegramClient, errors, functions
from telethon.tl.types import InputPhoneContact
from dotenv import load_dotenv
from getpass import getpass
import click
import click
from dotenv import load_dotenv
from telethon.sync import TelegramClient, errors, functions
from telethon.tl import types
load_dotenv()
def get_names(client: TelegramClient, phone_number: str) -> dict:
def get_human_readable_user_status(status: types.TypeUserStatus):
match status:
case types.UserStatusOnline():
return "Currently online"
case types.UserStatusOffline():
return status.was_online.strftime("%Y-%m-%d %H:%M:%S %Z")
case types.UserStatusRecently():
return "Last seen recently"
case types.UserStatusLastWeek():
return "Last seen last week"
case types.UserStatusLastMonth():
return "Last seen last month"
case _:
return "Unknown"
async def get_names(client: TelegramClient, phone_number: str) -> dict:
"""Take in a phone number and returns the associated user information if the user exists.
It does so by first adding the user's phones to the contact list, retrieving the
@@ -21,11 +38,11 @@ def get_names(client: TelegramClient, phone_number: str) -> dict:
print(f"Checking: {phone_number=} ...", end="", flush=True)
try:
# Create a contact
contact = InputPhoneContact(
contact = types.InputPhoneContact(
client_id=0, phone=phone_number, first_name="", last_name=""
)
# Attempt to add the contact from the address book
contacts = client(functions.contacts.ImportContactsRequest([contact]))
contacts = await client(functions.contacts.ImportContactsRequest([contact]))
users = contacts.to_dict().get("users", [])
number_of_matches = len(users)
@@ -39,31 +56,28 @@ def get_names(client: TelegramClient, phone_number: str) -> dict:
elif number_of_matches == 1:
# Attempt to remove the contact from the address book.
# The response from DeleteContactsRequest contains more information than from ImportContactsRequest
del_user = client(
updates_response: types.Updates = await client(
functions.contacts.DeleteContactsRequest(id=[users[0].get("id")])
)
user = del_user.to_dict().get("users")[0]
user_was_online = user.get("status", {}).get("was_online")
user = updates_response.users[0]
# getting more information about the user
result.update(
{
"id": user.get("id"),
"username": user.get("username"),
"first_name": user.get("first_name"),
"last_name": user.get("last_name"),
"fake": user.get("fake"),
"verified": user.get("verified"),
"premium": user.get("premium"),
"mutual_contact": user.get("mutual_contact"),
"bot": user.get("bot"),
"bot_chat_history": user.get("bot_chat_history"),
"restricted": user.get("restricted"),
"restriction_reason": user.get("restriction_reason"),
"user_was_online": (
user_was_online.strftime("%Y-%m-%d %H:%M:%S %Z")
if user_was_online
else None
),
"id": user.id,
"username": user.username,
"usernames": user.usernames,
"first_name": user.first_name,
"last_name": user.last_name,
"fake": user.fake,
"verified": user.verified,
"premium": user.premium,
"mutual_contact": user.mutual_contact,
"bot": user.bot,
"bot_chat_history": user.bot_chat_history,
"restricted": user.restricted,
"restriction_reason": user.restriction_reason,
"user_was_online": get_human_readable_user_status(user.status),
"phone": user.phone,
}
)
else:
@@ -87,7 +101,7 @@ def get_names(client: TelegramClient, phone_number: str) -> dict:
return result
def validate_users(client: TelegramClient, phone_numbers: str) -> dict:
async def validate_users(client: TelegramClient, phone_numbers: str) -> dict:
"""
Take in a string of comma separated phone numbers and try to get the user information associated with each phone number.
"""
@@ -98,14 +112,14 @@ def validate_users(client: TelegramClient, phone_numbers: str) -> dict:
try:
for phone in phones:
if phone not in result:
result[phone] = get_names(client, phone)
result[phone] = await get_names(client, phone)
except Exception as e:
print(e)
raise
return result
def login(
async def login(
api_id: str | None, api_hash: str | None, phone_number: str | None
) -> TelegramClient:
"""Create a telethon session or reuse existing one"""
@@ -116,16 +130,18 @@ def login(
phone_number or os.getenv("PHONE_NUMBER") or input("Enter your phone number: ")
)
client = TelegramClient(PHONE_NUMBER, API_ID, API_HASH)
client.connect()
if not client.is_user_authorized():
client.send_code_request(PHONE_NUMBER)
await client.connect()
if not await client.is_user_authorized():
await client.send_code_request(PHONE_NUMBER)
try:
client.sign_in(PHONE_NUMBER, input("Enter the code (sent on telegram): "))
await client.sign_in(
PHONE_NUMBER, input("Enter the code (sent on telegram): ")
)
except errors.SessionPasswordNeededError:
pw = getpass(
"Two-Step Verification enabled. Please enter your account password: "
)
client.sign_in(password=pw)
await client.sign_in(password=pw)
print("Done.")
return client
@@ -212,9 +228,24 @@ def main_entrypoint(
i.e. +491234567891
"""
client = login(api_id, api_hash, api_phone_number)
res = validate_users(client, phone_numbers)
asyncio.run(
run_program(
phone_numbers,
api_id,
api_hash,
api_phone_number,
output,
)
)
async def run_program(
phone_numbers: str, api_id: str, api_hash: str, api_phone_number: str, output: str
):
client = await login(api_id, api_hash, api_phone_number)
res = await validate_users(client, phone_numbers)
show_results(output, res)
client.disconnect()
if __name__ == "__main__":

View File

@@ -0,0 +1,26 @@
from datetime import datetime, timezone
import pytest
from telethon.tl import types
from telegram_phone_number_checker import main
@pytest.mark.parametrize(
"user_status, readable_string",
[
(types.UserStatusEmpty(), "Unknown"),
(types.UserStatusOnline(expires=None), "Currently online"),
(
types.UserStatusOffline(
was_online=datetime(2024, 4, 6, 12, 30, 1, tzinfo=timezone.utc)
),
"2024-04-06 12:30:01 UTC",
),
(types.UserStatusRecently(), "Last seen recently"),
(types.UserStatusLastWeek(), "Last seen last week"),
(types.UserStatusLastMonth(), "Last seen last month"),
],
)
def test_should_return_correct_status_string(user_status, readable_string):
assert main.get_human_readable_user_status(user_status) == readable_string