8 Commits

Author SHA1 Message Date
Jordan Gillard
ac84e34d38 Extend --help text to include info for acquiring Telegram API ID/hash (#20)
* Fix Poetry mismatch b/w pyproject.toml/poetry.lock

* docs: poetry run command to use pyproject script

* Update .gitignore

* Add show_default kwarg to --output option

* Add envvar kwarg to options and move load_dotenv()

* Add info on creating/storing api credentials

* Bump version

---------

Co-authored-by: Galen Reich <54807169+GalenReich@users.noreply.github.com>
2024-03-05 09:57:06 +00:00
omstaendlig
1284bd1ae7 Updated docstrings and added type hints (#19) 2024-02-21 17:17:43 +00:00
Galen Reich
b9380e001a Fix missing info (#17)
* Use the user object from user deletion instead of user import
* Bump version
2024-02-13 15:23:52 +00:00
Galen Reich
fe57ce1443 Match accounts by id instead of username (#15)
* Replace account matching by (optional) username with (mandatory) id field
* Use get instead of direct key retrieval
* Bump version
2024-02-12 15:03:12 +00:00
msramalho
4011e916c8 removes unused imports 2024-02-08 17:50:41 +00:00
msramalho
2a503856c2 fix poetry build default env file is empty 2024-02-08 15:08:35 +00:00
msramalho
d7f5415a4e fixes .env not working from CLI 2024-02-08 12:52:50 +00:00
msramalho
9ed496d690 remove whitespace in phone numbers 2024-01-30 22:37:00 +00:00
5 changed files with 90 additions and 42 deletions

9
.gitignore vendored
View File

@@ -1,4 +1,11 @@
.env
*.session
*.json
dist/
dist/
# JetBrains IDEs
.idea/
# Byte-compiled / optimized / DLL files
__pycache__/
**/*.py[cod]

View File

@@ -13,7 +13,7 @@ You can install this tool directly from the [official pypi release](https://pypi
pip install telegram-phone-number-checker
```
You can also install it and run it directly from github as a script.
You can also install it and run it directly from GitHub as a script.
```bash
git clone https://github.com/bellingcat/telegram-phone-number-checker
cd telegram-phone-number-checker
@@ -57,7 +57,7 @@ The result will be written to the console but also written as JSON to a `results
For each phone number, you can expect the following possible responses:
1. If available, you will receive the Telegram Username,Name, and ID that are connected with this number.
1. If available, you will receive the Telegram Username, Name, and ID that are connected with this number.
2. 'no username detected'. This means that it looks like the number was used to create a Telegram account but the user did not choose a Telegram Username. It is optional to create a Username on Telegram.
3. 'ERROR: no response, the user does not exist or has blocked contact adding.': There can be several reasons for this response. Either the phone number has not been used to create a Telegram account. Or: The phone number is connected to a Telegram account but the user has restricted the option to find him/her via the phone number.
4. Or: another error occurred.
@@ -77,6 +77,9 @@ cd telegram-phone-number-checker
This project uses [poetry](https://python-poetry.org/) to manage dependencies. You can install dependencies via poetry, or use the up-to-date [requirements.txt](requirements.txt) file.
```bash
# install poetry if you haven't already
pip install poetry
# with poetry
poetry install
@@ -87,7 +90,7 @@ pip install -r requirements.txt
You can then run it with any of these:
```bash
# with poetry
poetry run python3 telegram_phone_number_checker/main.py
poetry run telegram-phone-number-checker
# with pip installation
python3 telegram_phone_number_checker/main.py

6
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "anyio"
@@ -162,5 +162,5 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "1cf8b9ab78b1cf2860f38fc9e50e373b857e6d10ed6a397cb4f29cc556e14c95"
python-versions = "^3.9"
content-hash = "926cb8d034d6b5554d1a45ea61091fb4267210f203eefbf4da7604c3c19cbeb0"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "telegram-phone-number-checker"
version = "1.0.2"
version = "1.0.8"
description = "Check if phone numbers are connected to Telegram accounts."
authors = ["Bellingcat"]
license = "MIT"

View File

@@ -1,37 +1,45 @@
import os, json
from telethon.sync import TelegramClient, errors
import os, json, re
from telethon.sync import TelegramClient, errors, functions
from telethon.tl.types import InputPhoneContact
from telethon import functions
from dotenv import load_dotenv
from getpass import getpass
import click
load_dotenv()
def get_names(client, phone_number):
def get_names(client: TelegramClient, phone_number: str) -> dict:
"""
This function takes in a phone number and returns the username first name and the last name of the user if the user exists. It does so by first adding the user's phones to the contact list, retrieving the information, and then deleting the user from the contact list.
Takes 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 information, and then deleting the user from the contact list.
"""
result = {}
print(f'Checking: {phone_number=} ...', end="", flush=True)
try:
# Create a contact
contact = 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]))
username = contacts.to_dict()['users'][0]['username']
if not username:
result.update({"error": f'ERROR: no username detected'})
del_usr = client(functions.contacts.DeleteContactsRequest(id=[username]))
else:
result.update({"username": username})
del_usr = client(functions.contacts.DeleteContactsRequest(id=[username]))
# getting more information about the user
id = del_usr.to_dict()['users'][0]['id']
first_name = del_usr.to_dict()['users'][0]['first_name']
last_name = del_usr.to_dict()['users'][0]['last_name']
result.update({"first_name": first_name, "last_name": last_name, "id": id})
except IndexError as e:
result.update({"error": f'ERROR: no response, the user does not exist or has blocked contact adding.'})
users = contacts.to_dict().get('users', [])
number_of_matches = len(users)
if number_of_matches == 0:
result.update({"error": f'No response, the phone number is not on Telegram or has blocked contact adding.'})
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(functions.contacts.DeleteContactsRequest(id=[users[0].get('id')]))
user = del_user.to_dict().get('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')
})
else:
result.update({"error": f'This phone number matched multiple Telegram accounts, which is unexpected. Please contact the developer: contact-tech@bellingcat.com'})
except TypeError as e:
result.update({"error": f"TypeError: {e}. --> The error might have occurred due to the inability to delete the {phone_number=} from the contact list."})
except Exception as e:
@@ -41,14 +49,14 @@ def get_names(client, phone_number):
return result
def validate_users(client, phone_numbers):
'''
The function uses the get_api_response function to first check if the user exists and if it does, then it returns the first user name and the last user name.
'''
def validate_users(client: TelegramClient, phone_numbers: str) -> dict:
"""
Takes in a string of comma separated phone numbers and tries to get the user information associated with each phone number.
"""
if not phone_numbers or not len(phone_numbers):
phone_numbers = input('Enter the phone numbers to check, separated by commas: ')
result = {}
phones = [p.strip() for p in phone_numbers.split(",")]
phones = [re.sub(r"\s+", "", p, flags=re.UNICODE) for p in phone_numbers.split(",")]
try:
for phone in phones:
if phone not in result:
@@ -59,7 +67,7 @@ def validate_users(client, phone_numbers):
return result
def login(api_id, api_hash, phone_number):
def login(api_id: str | None, api_hash: str | None, phone_number: str | None) -> TelegramClient:
"""Create a telethon session or reuse existing one"""
print('Logging in...', end="", flush=True)
API_ID = api_id or os.getenv('API_ID') or input('Enter your API ID: ')
@@ -77,22 +85,52 @@ def login(api_id, api_hash, phone_number):
print("Done.")
return client
def show_results(output, res):
def show_results(output: str, res: dict) -> None:
print(json.dumps(res, indent=4))
with open(output, 'w') as f:
json.dump(res, f, indent=4)
print(f"Results saved to {output}")
@click.command()
@click.command(epilog='Check out the docs at github.com/bellingcat/telegram-phone-number-checker for more information.')
@click.option('--phone-numbers', '-p', help='List of phone numbers to check, separated by commas', type=str)
@click.option('--api-id', help='Your API_ID', type=str)
@click.option('--api-hash', help='Your API_HASH', type=str)
@click.option('--api-phone-number', help='Your phone_number', type=str)
@click.option('--output', help='results filename, default to results.json', default="results.json", type=str)
def main_entrypoint(phone_numbers, api_id, api_hash, api_phone_number, output):
"""Check to see if one or more phone numbers belong to a valid Telegram account"""
@click.option('--api-id', help='Your Telegram app api_id', type=str, prompt="Enter your Telegram App app_id", envvar='API_ID', show_envvar=True)
@click.option('--api-hash', help='Your Telegram app api_hash', type=str, prompt="Enter your Telegram App api_hash", hide_input=True, envvar='API_HASH', show_envvar=True)
@click.option('--api-phone-number', help='Your phone number', type=str, prompt="Enter the number associated with your Telegram account", envvar='PHONE_NUMBER', show_envvar=True)
@click.option('--output', help='Filename to store results', default="results.json", show_default=True, type=str)
def main_entrypoint(phone_numbers: str, api_id: str, api_hash: str, api_phone_number: str, output: str) -> None:
"""
Check to see if one or more phone numbers belong to a valid Telegram account.
\b
Prerequisites:
1. A Telegram account with an active phone number
2. A Telegram App api_id and App api_hash, which you can get by creating
a Telegram App @ https://my.telegram.org/apps
\b
Note:
If you do not want to enter the API ID, API hash, or phone number associated with
your Telegram account on the command line, you can store these values in a `.env`
file located within the same directory you run this command from.
\b
// .env file example:
API_ID=12345678
API_HASH=1234abcd5678efgh1234abcd567
PHONE_NUMBER=+15555555555
See the official Telegram docs at https://core.telegram.org/api/obtaining_api_id for more information on obtaining an API ID.
\b
Recommendations:
Telegram recommends entering phone numbers in international format
+(country code)(city or carrier code)(your number)
i.e. +491234567891
"""
client = login(api_id, api_hash, api_phone_number)
res = validate_users(client, phone_numbers)
res = validate_users(client, phone_numbers)
show_results(output, res)