Skip to content

Commit

Permalink
Major improvements:
Browse files Browse the repository at this point in the history
- Added parallelism when calling Telegram APIs
- Added -o option to output in json or yaml
- Added automated tests
- Updated README.md
- Added Dockerfile
- Refactored tosint.py to keep code clean and easy to maintain
  • Loading branch information
biagiopietro committed Sep 29, 2024
1 parent a9a56f2 commit 5a4531c
Show file tree
Hide file tree
Showing 5 changed files with 401 additions and 81 deletions.
77 changes: 74 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ Tosint (Telegram OSINT) is a tool designed to extract valuable information from
It is ideal for security researchers, investigators, and anyone interested in gathering insights from Telegram entities.
Using OSINT techniques, Tosint can uncover essential details about bots and associated channels, offering a deeper understanding of their structure and activity.

## Table of Contents

- [Features](#features)
- [User Cases](#use-cases)
- [Who Uses Tosint?](#who-uses-tosint)
- [Example Usage](#example-usage)
- [Example Output](#example-output)
- [Human format](#human-format)
- [Json or Yaml formats](#json-or-yaml-formats)
- [Installation](#installation)
- [Use via docker](#use-via-docker)
- [Contributing and Supporting the Project](#contributing-and-supporting-the-project)
- [License](#license)

### Features

Tosint allows you to extract the following information:
Expand Down Expand Up @@ -33,6 +47,7 @@ I'm proud to mention that Tosint is also used by law enforcement agencies for in
To use Tosint, you can either provide the `Telegram Token` and `Chat ID` interactively, or pass them as command-line arguments.

**Interactive Mode**:

```
$ python3 tosint.py
Telegram Token (bot1xxx): 562ZZZZ900:XXXXNj7_wIEi74GXXX90CIxACBIX_YYYYwI
Expand All @@ -48,12 +63,10 @@ Both approaches will provide you with detailed information about the bot and cha

### Example Output

#### Human format
After running the tool, the following is an example of the output you can expect:

```
Analysis of token: 562ZZZZ900:XXXXNj7_wIEi74GXXX90CIxACBIX_YYYYwI and chat id: -1001XXXXXX196
Bot First Name: Over Security Bot
Bot Username: over_security_bot
Bot User ID: 56XXXXXX00
Expand All @@ -73,6 +86,46 @@ Administrators in the chat:
{'id': 20XXXX39, 'is_bot': False, 'first_name': 'Andrea', 'last_name': 'Draghetti', 'username': 'AndreaDraghetti'}
```

#### Json or Yaml formats

By specifying the flag `-o <format>` is possible to have `json` or `yaml` outputs:

1. `json`
```
$ python3 tosint.py -t 75XXXXXX67:AXXQi_iKxxxE_mNDxxxxxxxxxzZ8t6QIHak -c -11xxxxx2 -o json
{
"bot_info": {
"first_name": "test",
"username": "fancybot",
"id": 75XXXXXX67,
"can_read_all_group_messages": false
},
"chat_administrators": null,
"created_chat_invite_link": null,
"exported_chat_invite_link": null,
"chat_member_count": null,
"chat_info": null,
"bot_chat_status": "N/A"
}
```

2. `yaml`

```
$ python3 tosint.py -t 75XXXXXX67:AXXQi_iKxxxE_mNDxxxxxxxxxzZ8t6QIHak -c -11xxxxx2 -o yaml
bot_chat_status: N/A
bot_info:
can_read_all_group_messages: false
first_name: test
id: 75XXXXXX67
username: fancybot
chat_administrators: null
chat_info: null
chat_member_count: null
created_chat_invite_link: null
exported_chat_invite_link: null
```

### Installation

1. Clone the repository:
Expand All @@ -90,6 +143,24 @@ pip install -r requirements.txt
python3 tosint.py
```

#### Use via docker

1. Clone the repository:
```
git clone https://github.com/drego85/tosint.git
```

2. Build docker image:
```
docker build -t tosint .
```

3. Run application

```
docker run -it tosint
```

Make sure you have Python 3.x installed.

### Contributing and Supporting the Project
Expand Down
23 changes: 23 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from .tosint import (
get_bot_info,
get_chat_member_info,
get_chat_info,
export_chat_invite_link,
create_chat_invite_link,
get_chat_member_count,
get_chat_administrators,
format_output,
format_human_output,
)

__all__ = [
"get_bot_info",
"get_chat_member_info",
"get_chat_info",
"export_chat_invite_link",
"create_chat_invite_link",
"get_chat_member_count",
"get_chat_administrators",
"format_output",
"format_human_output",
]
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
requests
requests==2.32.3
pyyaml==6.0.2
urllib3==2.2.3
requests-mock==1.12.1
174 changes: 174 additions & 0 deletions test_tosint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import unittest
import requests_mock
import json
from tosint import (
get_bot_info, get_chat_member_info, get_chat_info, export_chat_invite_link,
create_chat_invite_link, get_chat_member_count, get_chat_administrators,
format_output, format_human_output
)

TELEGRAM_API_URL = "https://api.telegram.org"

MOCK_TELEGRAM_TOKEN = "123456:ABCDEF"
MOCK_CHAT_ID = "-100123456789"
MOCK_BOT_INFO = {
"ok": True,
"result": {
"id": 123456789,
"is_bot": True,
"first_name": "TestBot",
"username": "testbot"
}
}
MOCK_CHAT_INFO = {
"ok": True,
"result": {
"id": MOCK_CHAT_ID,
"type": "supergroup",
"title": "Test Chat",
"has_linked_chat": True,
"username": "testchat" # Added for completeness
}
}
MOCK_CHAT_MEMBER_INFO = {
"ok": True,
"result": {
"status": "administrator"
}
}
MOCK_CHAT_MEMBER_COUNT = {
"ok": True,
"result": 100
}
MOCK_ADMINISTRATORS = {
"ok": True,
"result": [{"user": {"id": 123456789, "first_name": "Admin"}}]
}
MOCK_INVITE_LINK = {
"ok": True,
"result": "https://t.me/joinchat/abc123"
}


class TestTelegramBotScript(unittest.TestCase):
@requests_mock.Mocker()
def test_get_bot_info(self, mock_request):
url = f"{TELEGRAM_API_URL}/bot{MOCK_TELEGRAM_TOKEN}/getMe"
mock_request.get(url, json=MOCK_BOT_INFO)

bot_info = get_bot_info(MOCK_TELEGRAM_TOKEN)
self.assertEqual(bot_info['username'], "testbot")
self.assertEqual(bot_info['id'], 123456789)

@requests_mock.Mocker()
def test_get_chat_member_info(self, mock_request):
url = f"{TELEGRAM_API_URL}/bot{MOCK_TELEGRAM_TOKEN}/getChatMember?chat_id={MOCK_CHAT_ID}&user_id=123456789"
mock_request.get(url, json=MOCK_CHAT_MEMBER_INFO)

member_info = get_chat_member_info(MOCK_TELEGRAM_TOKEN, MOCK_CHAT_ID, 123456789)
self.assertEqual(member_info['result']['status'], "administrator")

@requests_mock.Mocker()
def test_get_chat_info(self, mock_request):
url = f"{TELEGRAM_API_URL}/bot{MOCK_TELEGRAM_TOKEN}/getChat?chat_id={MOCK_CHAT_ID}"
mock_request.get(url, json=MOCK_CHAT_INFO)

chat_info = get_chat_info(MOCK_TELEGRAM_TOKEN, MOCK_CHAT_ID)
self.assertEqual(chat_info['title'], "Test Chat")
self.assertEqual(chat_info['id'], MOCK_CHAT_ID)

@requests_mock.Mocker()
def test_export_chat_invite_link(self, mock_request):
url = f"{TELEGRAM_API_URL}/bot{MOCK_TELEGRAM_TOKEN}/exportChatInviteLink?chat_id={MOCK_CHAT_ID}"
mock_request.get(url, json=MOCK_INVITE_LINK)

invite_link = export_chat_invite_link(MOCK_TELEGRAM_TOKEN, MOCK_CHAT_ID)
self.assertEqual(invite_link, "https://t.me/joinchat/abc123")

@requests_mock.Mocker()
def test_create_chat_invite_link(self, mock_request):
url = f"{TELEGRAM_API_URL}/bot{MOCK_TELEGRAM_TOKEN}/createChatInviteLink?chat_id={MOCK_CHAT_ID}"
mock_request.get(url, json=MOCK_INVITE_LINK)

invite_link = create_chat_invite_link(MOCK_TELEGRAM_TOKEN, MOCK_CHAT_ID)
self.assertEqual(invite_link, "https://t.me/joinchat/abc123")

@requests_mock.Mocker()
def test_get_chat_member_count(self, mock_request):
url = f"{TELEGRAM_API_URL}/bot{MOCK_TELEGRAM_TOKEN}/getChatMemberCount?chat_id={MOCK_CHAT_ID}"
mock_request.get(url, json=MOCK_CHAT_MEMBER_COUNT)

member_count = get_chat_member_count(MOCK_TELEGRAM_TOKEN, MOCK_CHAT_ID)
self.assertEqual(member_count, 100)

@requests_mock.Mocker()
def test_get_chat_administrators(self, mock_request):
url = f"{TELEGRAM_API_URL}/bot{MOCK_TELEGRAM_TOKEN}/getChatAdministrators?chat_id={MOCK_CHAT_ID}"
mock_request.get(url, json=MOCK_ADMINISTRATORS)

administrators = get_chat_administrators(MOCK_TELEGRAM_TOKEN, MOCK_CHAT_ID)
self.assertEqual(administrators[0]['user']['first_name'], "Admin")

def test_format_output(self):
data = {"key": "value"}

# Test JSON output
json_output = format_output(data, 'json')
self.assertEqual(json.loads(json_output), data)

# Test YAML output
yaml_output = format_output(data, 'yaml')
self.assertTrue('key: value' in yaml_output)

def test_invalid_format_output(self):
data = {"key": "value"}
with self.assertRaises(ValueError):
format_output(data, 'invalid_format')

def test_format_human_output(self):
output_data = {
'bot_info': {
'first_name': 'TestBot',
'username': 'testbot',
'id': 123456789,
'can_read_all_group_messages': False
},
'bot_chat_status': 'administrator',
'chat_info': {
'title': 'Test Chat',
'type': 'supergroup',
'id': MOCK_CHAT_ID,
'has_linked_chat': True,
'username': 'testchat',
'invite_link': "https://t.me/joinchat/abc123"
},
'exported_chat_invite_link': "https://t.me/joinchat/abc123",
'created_chat_invite_link': "https://t.me/joinchat/abc123",
'chat_member_count': 100,
'chat_administrators': [{'user': {'id': 123456789, 'first_name': 'Admin'}}]
}

expected_output = (
"Bot First Name: TestBot\n"
"Bot Username: testbot\n"
"Bot User ID: 123456789\n"
"Bot Can Read Group Messages: False\n"
"Bot In The Chat Is An: administrator\n"
"Chat Title: Test Chat\n"
"Chat Type: supergroup\n"
"Chat ID: -100123456789\n"
"Chat has Visible History: True\n"
"Chat Username: testchat\n"
"Chat Invite Link: https://t.me/joinchat/abc123\n"
"Chat Invite Link (exported): https://t.me/joinchat/abc123\n"
"Chat Invite Link (created): https://t.me/joinchat/abc123\n"
"Number of users in the chat: 100\n"
"Administrators in the chat: [{'user': {'id': 123456789, 'first_name': 'Admin'}}]"
)

human_output = format_human_output(output_data)
self.assertEqual(human_output, expected_output)


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 5a4531c

Please sign in to comment.