🤬
  • Fixed install error that came with refactoring

    Version skipped to 2.2.50 to deal with PyPi issues.
  • Loading...
  • Jordan Wildon committed with GitHub 2 years ago
    c99f9d09
    1 parent 6ffee469
  • ■ ■ ■ ■ ■ ■
    CHANGES.txt
     1 +v2.2.50 16.10.2022 -- Major update! Code refactoring, reply retrieval,
     2 + translated descriptions, speedier decryption of media, support for
     3 + analyzing private groups, bug fixes and much more!
     4 + 
     5 + 
     6 +v2.1.10 25.08.2022 -- Bugs busted, solved initialization error, efficiency tweaks,
     7 + added automatic translations, fixed issue with media archiving,
     8 + multiple targets now work with user and location flags.
     9 + Added a flag for exporting list of chats the account is a
     10 + participant of.
     11 + 
     12 +v2.1.9, 16.08.2022 -- Added JSON export flag.
     13 + 
     14 +v2.1.4/8, 09.08.2022 -- Added support for multiple clients. Bug fixes.
     15 + 
     16 +v2.1.0/3, 08.08.2022 -- Added user lookup, location lookup, support for multiple
     17 + targets, log file, bug fixes, and improved encoding.
     18 + 
     19 +v2.0.0, 28.07.2022 -- Major update! Everything is simpler, more organized
     20 + and complies with updates to Telegram's API. Automated
     21 + statistics are included, as well as a few more toys,
     22 + with more to come!
     23 + 
     24 +v1.1.15, 25.04.2022 -- Fixed locations module, added phone number retrieval to
     25 + the participants module, improved documentation.
     26 + 
     27 +v1.1.13/14, 21.04.2022 -- Bug fixes.
     28 + 
     29 +v1.1.12, 21.04.2022 -- Fixed file management, updated advanced tools.
     30 + 
     31 +v1.1.4/11, 16.04.2022 -- Bug fixes.
     32 + 
     33 +v1.1.3, 16.04.2022 -- Added support for multiple -n flags.
     34 + 
     35 +v1.1.2, 15.04.2022 -- Bug fixes.
     36 + 
     37 +v1.1.1, 15.04.2022 -- Updated directory handling.
     38 + 
     39 +v1.1.0, 15.04.2022 -- Initial release.
     40 + 
  • ■ ■ ■ ■ ■ ■
    README.md
     1 + 
     2 + 
     3 +Telepathy: An OSINT toolkit for investigating Telegram chats. Developed by Jordan Wildon. Version 2.2.22.
     4 + 
     5 + 
     6 +## Installation
     7 + 
     8 +### Pip install (recommended)
     9 + 
     10 +```
     11 +$ pip3 install telepathy
     12 +```
     13 + 
     14 +### Install from source
     15 + 
     16 +```
     17 +$ git clone https://github.com/jordanwildon/Telepathy.git
     18 +$ cd Telepathy
     19 +$ pip install -r requirements.txt
     20 +```
     21 + 
     22 +## Setup
     23 + 
     24 +On first use, Telepathy will ask for your Telegram API details (obtained from my.telegram.org). Once those are set up, it will prompt you to enter your phone number again and then send an authorization code to your Telegram account. If you have two-factor authentication enabled, you'll be asked to input your Telegram password.
     25 + 
     26 +OPTIONAL: Installing cryptg ($ pip3 install cryptg) may improve Telepathy's speed. The package hand decryption by Python over to C, making media downloads in particular quicker and more efficient.
     27 + 
     28 + 
     29 +## Usage:
     30 + 
     31 +```
     32 +telepathy [OPTIONS]
     33 +```
     34 + 
     35 +Options:
     36 +- **'--target', '-t' [CHAT]**
     37 + 
     38 +this option will identify the target of the scan. The specified chat must be public. To get the chat name, look for the 't.me/chatname' link, and subtract the 't.me/'.
     39 + 
     40 +For example:
     41 + 
     42 +```
     43 +$ telepathy -t durov
     44 +```
     45 + 
     46 +The default is a basic scan which will find the title, description, number of participants, username, URL, chat type, chat ID, access hash, first post date and any applicable restrictions to the chat. For group chats, Telepathy will also generate a memberlist (up to 5,000 members).
     47 + 
     48 + 
     49 +- **'--comprehensive', '-c'**
     50 + 
     51 +A comprehensive scan will offer the same information as the basic scan, but will also archive a chat's message history.
     52 + 
     53 +For example:
     54 + 
     55 +```
     56 +$ telepathy -t durov -c
     57 +```
     58 + 
     59 + 
     60 +- **'--forwards', '-f'**
     61 + 
     62 +This flag will create an edgelist based on messages forwarded into a chat. It can be used alongside either a default or comprehensive scan.
     63 + 
     64 +For example:
     65 + 
     66 +```
     67 +$ telepathy -t durov -f
     68 +```
     69 + 
     70 + 
     71 +- **'--media', '-m'**
     72 + 
     73 +Use this flag to include media archiving alongside a comprehensive scan. This makes the process take significantly longer and should also be used with caution: you'll download all media content from the target chat, and it's up to you to not store illegal files on your system.
     74 + 
     75 +Since 2.2.0, downloading all media files will also generate a CSV file listing the files' metadata.
     76 + 
     77 +For example, this will run a comprehensive scan, including media archiving:
     78 + 
     79 +```
     80 +$ telepathy -t durov -c -m
     81 +```
     82 + 
     83 + 
     84 +- **'--user', '-u' [USER]**
     85 + 
     86 +Looks up a specified user ID. This will only work if your account has "encountered" the user before (for example, after archiving a group).
     87 + 
     88 +```
     89 +$ telepathy -t 0123456789 -u
     90 +```
     91 + 
     92 + 
     93 +- **'--location', '-l' [COORDINATES]**
     94 + 
     95 +Finds users near to specified coordinates. Input should be longitude followed by latitude, seperated by a comma. This feature only works if your Telegram account has a profile image which is set to publicly viewable.
     96 + 
     97 +```
     98 +$ telepathy -t 51.5032973,-0.1217424 -l
     99 +```
     100 + 
     101 +- **'--alt', '-a'**
     102 + 
     103 +Flag for running Telepathy from an alternative number. You can use the same API key and Hash but authenticate with a different phone number. Allows for running multiple scans at once.
     104 + 
     105 +```
     106 +$ telepathy -t Durov -c -a
     107 +```
     108 + 
     109 +- **'--export', '-e'**
     110 + 
     111 +Exports all chats your account is part of to a CSV file. In a future release, this may assist with setting up multiple accounts following the same groups.
     112 + 
     113 +```
     114 +$ telepathy -e
     115 +```
     116 +
     117 +- **'--reply', '-r'**
     118 + 
     119 +Flag for enable the reply in the channel, it will map users who replied in the channel and it will dump the full conversation chain
     120 + 
     121 +```
     122 +$ telepathy -t [CHANNEL] -c -r
     123 +```
     124 + 
     125 + 
     126 +## A note on how Telegram works
     127 + 
     128 +Telegram chats are organised into three key types: Channels, Megagroups/Supergroups and Gigagroups. Each module works slightly differently depending on the chat type. Channels can have seemingly unlimited subscribers and are where an admin will broadcast messages to an audience, Megagroups can have up to 200,000 members, each of whom can participate (if not restricted), and Gigagroups sit somewhere between the two.
     129 + 
     130 + 
     131 +## Upcoming changes
     132 +In some environments (particularly Windows), Telepathy struggles to effectively manage files and can sometimes produce errors. Fixes for these errors will come in due course.
     133 + 
     134 +Upcoming features include:
     135 + 
     136 + - [ ] Adding a time specification flag to set archiving for specific period.
     137 + - [x] The ability to archive comments on messages to channels.
     138 + - [ ] The ability to gather the number of reactions to messages, including statistics on engagement rate.
     139 + - [ ] Finding a method to once again gather complete memberlists (currently restricted by the API).
     140 + - [x] Introducing the ability to scan multiple targets at once.
     141 + - [ ] Improved statistics: including timestamp analysis for channels.
     142 + - [ ] Generating an entirely automated complete report, including visualisation for some statistics.
     143 + - [x] Making it easier to scan private groups which your account is a member of.
     144 + - [ ] Hate speech analytics.
     145 + - [x] Clean code, efficiency tweaks.
     146 + - [x] Add user lookup.
     147 + - [x] Add location lookup.
     148 + - [ ] Maximise compatibility of edgelists with Gephi.
     149 + - [ ] Include sockpuppet account provisioning (creation of accounts from previous exported lists).
     150 + - [ ] Listing who has admin rights in memberlists.
     151 + - [ ] Media downloaded in the background to increase efficiency.
     152 + - [ ] When media archiving is flagged, the location of downloaded content will be added to the archive file.
     153 + - [ ] Adding direct link to posts in the chat archive file
     154 + 
     155 + 
     156 +## feedback
     157 + 
     158 +Please send feedback to @jordanwildon on Twitter. You can follow Telepathy updates at @TelepathyDB.
     159 + 
     160 + 
     161 +## Usage terms
     162 + 
     163 +You may use Telepathy however you like, but your usecase is your responsibility. Be safe and respectful.
     164 + 
     165 + 
     166 +## Credits
     167 + 
     168 +All tools created by Jordan Wildon (@jordanwildon). Special thanks go to [Giacomo Giallombardo](https://github.com/aaarghhh) for adding additional features and code refactoring, and Alex Newhouse (@AlexBNewhouse) for his help with Telepathy v1.
     169 + 
     170 +Where possible, credit for the use of this tool in published research is desired, but not required. This can either come in the form of crediting the author, or crediting Telepathy itself (preferably with a link).
     171 + 
  • ■ ■ ■ ■ ■ ■
    build/lib/telepathy/const.py
     1 +__author__ = "Jordan Wildon (@jordanwildon)"
     2 +__license__ = "MIT License"
     3 +__version__ = "2.2.50"
     4 +__maintainer__ = "Jordan Wildon"
     5 +__email__ = "[email protected]"
     6 +__status__ = "Development"
     7 + 
     8 +user_agent = [
     9 + # Chrome
     10 + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
     11 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
     12 + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
     13 + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
     14 + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
     15 + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
     16 + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
     17 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
     18 + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
     19 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
     20 + # Firefox
     21 + "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)",
     22 + "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
     23 + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)",
     24 + "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko",
     25 + "Mozilla/5.0 (Windows NT 6.2; WOW64; Trident/7.0; rv:11.0) like Gecko",
     26 + "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
     27 + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)",
     28 + "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko",
     29 + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
     30 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; Trident/7.0; rv:11.0) like Gecko",
     31 + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)",
     32 + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)",
     33 + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
     34 + # Safari
     35 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) RockMelt/0.9.50.549 Chrome/10.0.648.205 Safari/534.16"
     36 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.18"
     37 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.19 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.19"
     38 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21"
     39 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.682.0 Safari/534.21"
     40 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3"
     41 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3"
     42 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.53 Safari/534.3"
     43 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Iron/6.0.475 Safari/534"
     44 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.724.100 Safari/534.30"
     45 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.500.0 Safari/534.6"
     46 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7"
     47 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7 ChromePlus/1.5.0.0 ChromePlus/1.5.0.0"
     48 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7 ChromePlus/1.5.0.0alpha1"
     49 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Flock/3.5.2.4599 Chrome/7.0.517.442 Safari/534.7"
     50 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Iron/7.0.520.0 Chrome/7.0.520.0 Safari/534.7"
     51 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Iron/7.0.520.1 Chrome/7.0.520.1 Safari/534.7"
     52 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Iron/7.0.520.1 Safari/534.7"
     53 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) RockMelt/0.8.36.116 Chrome/7.0.517.44 Safari/534.7"
     54 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) RockMelt/0.8.36.128 Chrome/7.0.517.44 Safari/534.7"
     55 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.9 (KHTML, like Gecko) Chrome/7.0.531.0 Safari/534.9"
     56 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Iron/0.2.152.0 Safari/13657880.525",
     57 +]
  • ■ ■ ■ ■ ■ ■
    build/lib/telepathy/telepathy.py
     1 +#!/usr/bin/python3
     2 + 
     3 + 
     4 +"""Telepathy cli interface:
     5 + An OSINT toolkit for investigating Telegram chats.
     6 +"""
     7 + 
     8 +from tokenize import group
     9 +import pandas as pd
     10 +import datetime
     11 +import requests
     12 +import json
     13 +import random
     14 +import glob
     15 +import csv
     16 +import os
     17 +import getpass
     18 +import click
     19 +import re
     20 +import textwrap
     21 +import time
     22 +import pprint
     23 + 
     24 +from telepathy.utils import (
     25 + print_banner,
     26 + color_print_green,
     27 + populate_user,
     28 + process_message,
     29 + process_description,
     30 + parse_tg_date,
     31 + parse_html_page
     32 +)
     33 +import telepathy.const as const
     34 + 
     35 +from colorama import Fore, Back, Style
     36 + 
     37 +from telethon.errors import SessionPasswordNeededError, ChannelPrivateError
     38 +from telethon.tl.types import (
     39 + InputPeerEmpty,
     40 + PeerUser,
     41 + PeerChat,
     42 + PeerChannel,
     43 + PeerLocated,
     44 + ChannelParticipantCreator,
     45 + ChannelParticipantAdmin,
     46 +)
     47 +from telethon.tl.functions.messages import GetDialogsRequest
     48 +from telethon import TelegramClient, functions, types, utils
     49 +from telethon.utils import get_display_name, get_message_id
     50 +from alive_progress import alive_bar
     51 +from bs4 import BeautifulSoup
     52 + 
     53 + 
     54 +@click.command()
     55 +@click.option(
     56 + "--target",
     57 + "-t",
     58 + #default="",
     59 + multiple=True,
     60 + help="Specifies a chat to investigate.",
     61 +)
     62 +@click.option(
     63 + "--comprehensive",
     64 + "-c",
     65 + is_flag=True,
     66 + help="Comprehensive scan, includes archiving.",
     67 +)
     68 +@click.option(
     69 + "--media", "-m", is_flag=True, help="Archives media in the specified chat."
     70 +)
     71 +@click.option("--forwards", "-f", is_flag=True, help="Scrapes forwarded messages.")
     72 +@click.option("--user", "-u", is_flag=True, help="Looks up a specified user ID.")
     73 +@click.option(
     74 + "--location", "-l", is_flag=True, help="Finds users near to specified coordinates."
     75 +)
     76 +@click.option(
     77 + "--alt", "-a", is_flag=True, default=False, help="Uses an alternative login."
     78 +)
     79 +@click.option("--json", "-j", is_flag=True, default=False, help="Export to JSON.")
     80 +@click.option(
     81 + "--export",
     82 + "-e",
     83 + is_flag=True,
     84 + default=False,
     85 + help="Export a list of chats your account is part of.",
     86 +)
     87 +@click.option(
     88 + "--replies",
     89 + "-r",
     90 + is_flag=True,
     91 + default=False,
     92 + help="Enable replies analysis in channels.",
     93 +)
     94 +def cli(
     95 + target, comprehensive, media, forwards, user, location, alt, json, export, replies
     96 +):
     97 + print_banner()
     98 + telepathy_file = "./telepathy_files/"
     99 + try:
     100 + os.makedirs(telepathy_file)
     101 + except FileExistsError:
     102 + pass
     103 + 
     104 + # Defining default values
     105 + basic = False
     106 + comp_check = False
     107 + media_archive = False
     108 + forwards_check = False
     109 + forward_verify = False
     110 + reply_analysis = False
     111 + user_check = False
     112 + location_check = False
     113 + last_date = None
     114 + chunk_size = 1000
     115 + filetime = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M")
     116 + filetime_clean = str(filetime)
     117 + 
     118 + # Will add more languages later
     119 + user_language = "en"
     120 + 
     121 + if target:
     122 + basic = True
     123 + if replies:
     124 + reply_analysis = True
     125 + if forwards:
     126 + forwards_check = True
     127 + if user:
     128 + user_check = True
     129 + basic = False
     130 + if location:
     131 + location_check = True
     132 + basic = False
     133 + if comprehensive:
     134 + comp_check = True
     135 + if media:
     136 + media_archive = True
     137 + if export:
     138 + t = " "
     139 + if alt:
     140 + alt_check = True
     141 + else:
     142 + alt_check = False
     143 + 
     144 + if json:
     145 + json_check = True
     146 + json_file = telepathy_file + "json_files/"
     147 + try:
     148 + os.makedirs(json_file)
     149 + except FileExistsError:
     150 + pass
     151 + else:
     152 + json_check = False
     153 + 
     154 + if alt_check == True:
     155 + login = telepathy_file + "login_alt.txt"
     156 + 
     157 + if os.path.isfile(login) == False:
     158 + api_id = input(" Please enter your API ID:\n")
     159 + api_hash = input(" Please enter your API Hash:\n")
     160 + phone_number = input(" Please enter your phone number:\n")
     161 + with open(login, "w+", encoding="utf-8") as f:
     162 + f.write(api_id + "," + api_hash + "," + phone_number)
     163 + else:
     164 + with open(login, encoding="utf-8") as f:
     165 + details = f.read()
     166 + api_id, api_hash, phone_number = details.split(sep=",")
     167 + else:
     168 + login = telepathy_file + "login.txt"
     169 + 
     170 + if os.path.isfile(login) == False:
     171 + api_id = input(" Please enter your API ID:\n")
     172 + api_hash = input(" Please enter your API Hash:\n")
     173 + phone_number = input(" Please enter your phone number:\n")
     174 + with open(login, "w+", encoding="utf-8") as f:
     175 + f.write(api_id + "," + api_hash + "," + phone_number)
     176 + else:
     177 + with open(login, encoding="utf-8") as f:
     178 + details = f.read()
     179 + api_id, api_hash, phone_number = details.split(sep=",")
     180 + 
     181 + client = TelegramClient(phone_number, api_id, api_hash)
     182 + 
     183 + async def main():
     184 + 
     185 + await client.connect()
     186 + if not await client.is_user_authorized():
     187 + await client.send_code_request(phone_number)
     188 + await client.sign_in(phone_number)
     189 + try:
     190 + await client.sign_in(code=input(" Enter code: "))
     191 + except SessionPasswordNeededError:
     192 + await client.sign_in(
     193 + password=getpass.getpass(prompt="Password: ", stream=None)
     194 + )
     195 + result = client(
     196 + GetDialogsRequest(
     197 + offset_date=last_date,
     198 + offset_id=0,
     199 + offset_peer=InputPeerEmpty(),
     200 + limit=chunk_size,
     201 + hash=0,
     202 + )
     203 + )
     204 + else:
     205 + 
     206 + if export == True:
     207 + export_file = telepathy_file + "export.csv"
     208 + exports = []
     209 + 
     210 + print("Exporting...")
     211 + 
     212 + # progress bar
     213 + 
     214 + for Dialog in await client.get_dialogs():
     215 + try:
     216 + if Dialog.entity.username:
     217 + group_url = "http://t.me/" + Dialog.entity.username
     218 + group_username = Dialog.entity.username
     219 + 
     220 + web_req = parse_html_page(group_url)
     221 + group_description = web_req["group_description"]
     222 + total_participants = web_req["total_participants"]
     223 + 
     224 + _desc = process_description(
     225 + group_description, user_language
     226 + )
     227 + description_text = _desc["group_description"]
     228 + original_language = _mess[
     229 + "original_language"
     230 + ]
     231 + translated_description = _desc["translated_text"]
     232 + 
     233 + if Dialog.entity.broadcast is True:
     234 + chat_type = "Channel"
     235 + elif Dialog.entity.megagroup is True:
     236 + chat_type = "Megagroup"
     237 + elif Dialog.entity.gigagroup is True:
     238 + chat_type = "Gigagroup"
     239 + else:
     240 + chat_type = "Chat"
     241 + 
     242 + if Dialog.entity.restriction_reason is not None:
     243 + ios_restriction = Dialog.entity.restriction_reason[
     244 + 0
     245 + ]
     246 + if 1 in Dialog.entity.restriction_reason:
     247 + android_restriction = (
     248 + Dialog.entity.restriction_reason[1]
     249 + )
     250 + group_status = (
     251 + str(ios_restriction)
     252 + + ", "
     253 + + str(android_restriction)
     254 + )
     255 + else:
     256 + group_status = str(ios_restriction)
     257 + else:
     258 + group_status = "None"
     259 + 
     260 + exports.append(
     261 + [
     262 + filetime,
     263 + Dialog.entity.title,
     264 + group_description,
     265 + translated_description,
     266 + total_participants,
     267 + group_username,
     268 + group_url,
     269 + chat_type,
     270 + Dialog.entity.id,
     271 + Dialog.entity.access_hash,
     272 + group_status,
     273 + ]
     274 + )
     275 + 
     276 + export_df = pd.DataFrame(
     277 + exports,
     278 + columns=[
     279 + "Access Date",
     280 + "Title",
     281 + "Description",
     282 + "Translated description",
     283 + "Total participants",
     284 + "Username",
     285 + "URL",
     286 + "Chat type",
     287 + "Chat ID",
     288 + "Access hash",
     289 + "Restrictions",
     290 + ],
     291 + )
     292 + 
     293 + if not os.path.isfile(export_file):
     294 + export_df.to_csv(export_file, sep=";", index=False)
     295 + else:
     296 + export_df.to_csv(
     297 + export_file, sep=";", mode="w", index=False
     298 + )
     299 + 
     300 + except AttributeError:
     301 + pass
     302 + 
     303 + else:
     304 + 
     305 + for t in target:
     306 + target_clean = t
     307 + alphanumeric = ""
     308 +
     309 + 
     310 + for character in target_clean:
     311 + if character.isalnum():
     312 + alphanumeric += character
     313 + 
     314 + if "https://t.me/+" in t:
     315 + t = t.replace('https://t.me/+', 'https://t.me/joinchat/')
     316 + 
     317 + if basic is True or comp_check is True:
     318 + save_directory = telepathy_file + alphanumeric
     319 + try:
     320 + os.makedirs(save_directory)
     321 + except FileExistsError:
     322 + pass
     323 + 
     324 + # Creating logfile
     325 + log_file = telepathy_file + "log.csv"
     326 + 
     327 + if media_archive:
     328 + media_directory = save_directory + "/media"
     329 + try:
     330 + os.makedirs(media_directory)
     331 + except FileExistsError:
     332 + pass
     333 + 
     334 + if basic == True and comp_check == False:
     335 + color_print_green(" [!] ", "Performing basic scan")
     336 + elif comp_check == True:
     337 + color_print_green(" [!] ", "Performing comprehensive scan")
     338 + file_archive = (
     339 + save_directory
     340 + + "/"
     341 + + alphanumeric
     342 + + "_"
     343 + + filetime_clean
     344 + + "_archive.csv"
     345 + )
     346 + reply_file_archive = (
     347 + save_directory
     348 + + "/"
     349 + + alphanumeric
     350 + + "_"
     351 + + filetime_clean
     352 + + "_reply_archive.csv"
     353 + )
     354 + 
     355 + if forwards_check == True:
     356 + color_print_green(" [!] ", "Forwards will be fetched")
     357 + file_forwards = (
     358 + save_directory
     359 + + "/edgelists/"
     360 + + alphanumeric
     361 + + "_"
     362 + + filetime_clean
     363 + + "_edgelist.csv"
     364 + )
     365 + forward_directory = save_directory + "/edgelists/"
     366 + 
     367 + try:
     368 + os.makedirs(forward_directory)
     369 + except FileExistsError:
     370 + pass
     371 + 
     372 + edgelist_file = (
     373 + forward_directory + "/" + alphanumeric + "_edgelist.csv"
     374 + )
     375 + 
     376 + if basic is True or comp_check is True:
     377 + 
     378 + color_print_green(" [-] ", "Fetching details for " + t + "...")
     379 + memberlist_directory = save_directory + "/memberlists"
     380 + 
     381 + try:
     382 + os.makedirs(memberlist_directory)
     383 + except FileExistsError:
     384 + pass
     385 + 
     386 + memberlist_filename = (
     387 + memberlist_directory + "/" + alphanumeric + "_members.csv"
     388 + )
     389 + reply_memberlist_filename = (
     390 + memberlist_directory
     391 + + "/"
     392 + + alphanumeric
     393 + + "_active_members.csv"
     394 + )
     395 + 
     396 + entity = await client.get_entity(t)
     397 + first_post = "Not found"
     398 + 
     399 + async for message in client.iter_messages(t, reverse=True):
     400 + datepost = parse_tg_date(message.date)
     401 + date = datepost["date"]
     402 + mtime = datepost["mtime"]
     403 + first_post = datepost["timestamp"]
     404 + break
     405 + 
     406 + if entity.username:
     407 + name = entity.title
     408 + group_url = "http://t.me/" + entity.username
     409 + group_username = entity.username
     410 + web_req = parse_html_page(group_url)
     411 + elif "https://t.me/" in t:
     412 + group_url = t
     413 + web_req = parse_html_page(group_url)
     414 + group_username = "Private group"
     415 + else:
     416 + group_url = "Private group"
     417 + group_username = "Private group"
     418 + 
     419 + 
     420 + group_description = web_req["group_description"]
     421 + total_participants = web_req["total_participants"]
     422 + 
     423 + _desc = process_description(
     424 + group_description, user_language
     425 + )
     426 + description_text = _desc["description_text"]
     427 + original_language = _desc[
     428 + "original_language"
     429 + ]
     430 + 
     431 + translated_description = _desc["translated_text"]
     432 + 
     433 + preferredWidth = 70
     434 + descript = Fore.GREEN + "Description: " + Style.RESET_ALL
     435 + prefix = descript
     436 + wrapper_d = textwrap.TextWrapper(
     437 + initial_indent=prefix,
     438 + width=preferredWidth,
     439 + subsequent_indent=" ",
     440 + )
     441 + 
     442 + trans_descript = Fore.GREEN + "Translated: " + Style.RESET_ALL
     443 + prefix = trans_descript
     444 + wrapper_td = textwrap.TextWrapper(
     445 + initial_indent=prefix,
     446 + width=preferredWidth,
     447 + subsequent_indent=" ",
     448 + )
     449 + 
     450 + group_description = ('"' + group_description + '"')
     451 + 
     452 + if entity.broadcast is True:
     453 + chat_type = "Channel"
     454 + elif entity.megagroup is True:
     455 + chat_type = "Megagroup"
     456 + elif entity.gigagroup is True:
     457 + chat_type = "Gigagroup"
     458 + else:
     459 + chat_type = "Chat"
     460 + 
     461 + if entity.restriction_reason is not None:
     462 + ios_restriction = entity.restriction_reason[0]
     463 + if 1 in entity.restriction_reason:
     464 + android_restriction = entity.restriction_reason[1]
     465 + group_status = (
     466 + str(ios_restriction) + ", " + str(android_restriction)
     467 + )
     468 + else:
     469 + group_status = str(ios_restriction)
     470 + else:
     471 + group_status = "None"
     472 + 
     473 + restrict = Fore.GREEN + "Restrictions:" + Style.RESET_ALL
     474 + prefix = restrict + " "
     475 + preferredWidth = 70
     476 + wrapper_r = textwrap.TextWrapper(
     477 + initial_indent=prefix,
     478 + width=preferredWidth,
     479 + subsequent_indent=" ",
     480 + )
     481 + 
     482 + if chat_type != "Channel":
     483 + members = []
     484 + all_participants = []
     485 + all_participants = await client.get_participants(t, limit=5000)
     486 + 
     487 + members_df = None
     488 + for user in all_participants:
     489 + members_df = pd.DataFrame(
     490 + members,
     491 + columns=[
     492 + "Username",
     493 + "Full name",
     494 + "User ID",
     495 + "Phone number",
     496 + "Group name",
     497 + ],
     498 + )
     499 + members.append(populate_user(user, t))
     500 + 
     501 + if members_df is not None:
     502 + with open(
     503 + memberlist_filename, "w+", encoding="utf-8"
     504 + ) as save_members:
     505 + members_df.to_csv(save_members, sep=";")
     506 + 
     507 + if json_check == True:
     508 + members_df.to_json(
     509 + json_file + alphanumeric + "_memberlist.json",
     510 + orient="records",
     511 + compression="infer",
     512 + lines=True,
     513 + index=True,
     514 + )
     515 + else:
     516 + pass
     517 + 
     518 + found_participants = len(all_participants)
     519 + found_participants = int(found_participants)
     520 + found_percentage = (
     521 + int(found_participants) / int(total_participants) * 100
     522 + )
     523 + 
     524 + log = []
     525 + 
     526 + if chat_type != "Channel":
     527 + print("\n")
     528 + color_print_green(" [+] Memberlist fetched", "")
     529 + else:
     530 + pass
     531 +
     532 + color_print_green(" ┬ Chat details", "")
     533 + color_print_green(" ├ Title: ", str(entity.title))
     534 + color_print_green(" ├ ", wrapper_d.fill(group_description))
     535 + if translated_description != group_description:
     536 + color_print_green(" ├ ", wrapper_td.fill(translated_description))
     537 + color_print_green(
     538 + " ├ Total participants: ", str(total_participants)
     539 + )
     540 + 
     541 + if chat_type != "Channel":
     542 + color_print_green(
     543 + " ├ Participants found: ",
     544 + str(found_participants)
     545 + + " ("
     546 + + str(format(found_percentage, ".2f"))
     547 + + "%)",
     548 + )
     549 + else:
     550 + found_participants = "N/A"
     551 + 
     552 + color_print_green(" ├ Username: ", str(group_username))
     553 + color_print_green(" ├ URL: ", str(group_url))
     554 + color_print_green(" ├ Chat type: ", str(chat_type))
     555 + color_print_green(" ├ Chat id: ", str(entity.id))
     556 + color_print_green(" ├ Access hash: ", str(entity.access_hash))
     557 + 
     558 + if chat_type == "Channel":
     559 + scam_status = str(entity.scam)
     560 + color_print_green(" ├ Scam: ", str(scam_status))
     561 + else:
     562 + scam_status = "N/A"
     563 + 
     564 + color_print_green(" ├ First post date: ", str(first_post))
     565 + 
     566 + if chat_type != "Channel":
     567 + color_print_green(
     568 + " ├ Memberlist saved to: ", memberlist_filename
     569 + )
     570 + 
     571 + color_print_green(
     572 + " â”” ", wrapper_r.fill(group_status)
     573 + )
     574 + #print("\n")
     575 + 
     576 + log.append(
     577 + [
     578 + filetime,
     579 + entity.title,
     580 + group_description,
     581 + translated_description,
     582 + total_participants,
     583 + found_participants,
     584 + group_username,
     585 + group_url,
     586 + chat_type,
     587 + entity.id,
     588 + entity.access_hash,
     589 + scam_status,
     590 + date,
     591 + mtime,
     592 + group_status,
     593 + ]
     594 + )
     595 + 
     596 + log_df = pd.DataFrame(
     597 + log,
     598 + columns=[
     599 + "Access Date",
     600 + "Title",
     601 + "Description",
     602 + "Translated description",
     603 + "Total participants",
     604 + "Participants found",
     605 + "Username",
     606 + "URL",
     607 + "Chat type",
     608 + "Chat ID",
     609 + "Access hash",
     610 + "Scam",
     611 + "First post date",
     612 + "First post time (UTC)",
     613 + "Restrictions",
     614 + ],
     615 + )
     616 + 
     617 + if not os.path.isfile(log_file):
     618 + log_df.to_csv(log_file, sep=";", index=False)
     619 + else:
     620 + log_df.to_csv(
     621 + log_file, sep=";", mode="a", index=False, header=False
     622 + )
     623 + 
     624 + if forwards_check is True and comp_check is False:
     625 + color_print_green(
     626 + " [-] ", "Calculating number of forwarded messages..."
     627 + )
     628 + forwards_list = []
     629 + forward_count = 0
     630 + private_count = 0
     631 + to_ent = await client.get_entity(t)
     632 + to_title = to_ent.title
     633 + 
     634 + forwards_df = pd.DataFrame(
     635 + forwards_list,
     636 + columns=[
     637 + "To",
     638 + "To_title",
     639 + "From",
     640 + "From_ID",
     641 + "Username",
     642 + "Timestamp",
     643 + ],
     644 + )
     645 + 
     646 + async for message in client.iter_messages(t):
     647 + if message.forward is not None:
     648 + forward_count += 1
     649 + 
     650 + #print("\n")
     651 + color_print_green(" [-] ", "Fetching forwarded messages...")
     652 + 
     653 + progress_bar = (
     654 + Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
     655 + )
     656 + 
     657 + with alive_bar(
     658 + forward_count, dual_line=True, title=progress_bar, length=20
     659 + ) as bar:
     660 + 
     661 + async for message in client.iter_messages(t):
     662 + if message.forward is not None:
     663 + try:
     664 + f_from_id = message.forward.original_fwd.from_id
     665 + if f_from_id is not None:
     666 + ent = await client.get_entity(f_from_id)
     667 + username = ent.username
     668 + timestamp = parse_tg_date(message.date)[
     669 + "timestamp"
     670 + ]
     671 + 
     672 + substring = "PeerUser"
     673 + string = str(f_from_id)
     674 + if substring in string:
     675 + user_id = re.sub("[^0-9]", "", string)
     676 + user_id = await client.get_entity(
     677 + PeerUser(int(user_id))
     678 + )
     679 + user_id = str(user_id)
     680 + result = (
     681 + "User: "
     682 + + str(ent.first_name)
     683 + + " / ID: "
     684 + + str(user_id.id)
     685 + )
     686 + else:
     687 + result = str(ent.title)
     688 + 
     689 + forwards_df = pd.DataFrame(
     690 + forwards_list,
     691 + columns=[
     692 + "To username",
     693 + "To name",
     694 + "From",
     695 + "From ID",
     696 + "From_username",
     697 + "Timestamp",
     698 + ],
     699 + )
     700 + 
     701 + forwards_list.append(
     702 + [
     703 + t,
     704 + to_title,
     705 + result,
     706 + f_from_id,
     707 + username,
     708 + timestamp,
     709 + ]
     710 + )
     711 + 
     712 + except Exception as e:
     713 + if e is ChannelPrivateError:
     714 + print("Private channel")
     715 + continue
     716 + 
     717 + time.sleep(0.5)
     718 + bar()
     719 + 
     720 + with open(
     721 + edgelist_file, "w+", encoding="utf-8"
     722 + ) as save_forwards:
     723 + forwards_df.to_csv(save_forwards, sep=";")
     724 + 
     725 + if json_check == True:
     726 + forwards_df.to_json(
     727 + json_file + alphanumeric + "_edgelist.json",
     728 + orient="records",
     729 + compression="infer",
     730 + lines=True,
     731 + index=True,
     732 + )
     733 + else:
     734 + pass
     735 + 
     736 + if forward_count >= 15:
     737 + forwards_found = forwards_df.From.count()
     738 + value_count = forwards_df["From"].value_counts()
     739 + df01 = value_count.rename_axis("unique_values").reset_index(
     740 + name="counts"
     741 + )
     742 + 
     743 + top_forward_one = df01.iloc[0]["unique_values"]
     744 + top_value_one = df01.iloc[0]["counts"]
     745 + top_forward_two = df01.iloc[1]["unique_values"]
     746 + top_value_two = df01.iloc[1]["counts"]
     747 + top_forward_three = df01.iloc[2]["unique_values"]
     748 + top_value_three = df01.iloc[2]["counts"]
     749 + top_forward_four = df01.iloc[3]["unique_values"]
     750 + top_value_four = df01.iloc[3]["counts"]
     751 + top_forward_five = df01.iloc[4]["unique_values"]
     752 + top_value_five = df01.iloc[4]["counts"]
     753 + 
     754 + forward_one = (
     755 + str(top_forward_one)
     756 + + ", "
     757 + + str(top_value_one)
     758 + + " forwarded messages"
     759 + )
     760 + forward_two = (
     761 + str(top_forward_two)
     762 + + ", "
     763 + + str(top_value_two)
     764 + + " forwarded messages"
     765 + )
     766 + forward_three = (
     767 + str(top_forward_three)
     768 + + ", "
     769 + + str(top_value_three)
     770 + + " forwarded messages"
     771 + )
     772 + forward_four = (
     773 + str(top_forward_four)
     774 + + ", "
     775 + + str(top_value_four)
     776 + + " forwarded messages"
     777 + )
     778 + forward_five = (
     779 + str(top_forward_five)
     780 + + ", "
     781 + + str(top_value_five)
     782 + + " forwarded messages"
     783 + )
     784 + 
     785 + df02 = forwards_df.From.unique()
     786 + unique_forwards = len(df02)
     787 + 
     788 + #print("\n")
     789 + color_print_green(" [+] Forward scrape complete", "")
     790 + color_print_green(" ┬ Statistics", "")
     791 + color_print_green(
     792 + " ├ Forwarded messages found: ", str(forward_count)
     793 + )
     794 + color_print_green(
     795 + " ├ Forwards from active public chats: ",
     796 + str(forwards_found),
     797 + )
     798 + color_print_green(
     799 + " ├ Unique forward sources: ", str(unique_forwards)
     800 + )
     801 + color_print_green(
     802 + " ├ Top forward source 1: ", str(forward_one)
     803 + )
     804 + color_print_green(
     805 + " ├ Top forward source 2: ", str(forward_two)
     806 + )
     807 + color_print_green(
     808 + " ├ Top forward source 3: ", str(forward_three)
     809 + )
     810 + color_print_green(
     811 + " ├ Top forward source 4: ", str(forward_four)
     812 + )
     813 + color_print_green(
     814 + " ├ Top forward source 5: ", str(forward_five)
     815 + )
     816 + color_print_green(" â”” Edgelist saved to: ", edgelist_file)
     817 + #print("\n")
     818 + 
     819 + else:
     820 + print(
     821 + "\n"
     822 + + Fore.GREEN
     823 + + " [!] Insufficient forwarded messages found"
     824 + + Style.RESET_ALL
     825 + )
     826 + 
     827 + else:
     828 + 
     829 + if comp_check is True:
     830 + 
     831 + messages = client.iter_messages(t)
     832 + 
     833 + message_list = []
     834 + forwards_list = []
     835 + 
     836 + user_reaction_list = []
     837 + 
     838 + replies_list = []
     839 + user_replier_list = []
     840 + 
     841 + timecount = []
     842 + 
     843 + forward_count = 0
     844 + private_count = 0
     845 + 
     846 + if media_archive is True:
     847 + files = []
     848 + print("\n")
     849 + color_print_green(
     850 + " [!] ", "Media content will be archived"
     851 + )
     852 + 
     853 + color_print_green(
     854 + " [!] ", "Calculating number of messages..."
     855 + )
     856 + 
     857 + message_count = 0
     858 + 
     859 + async for message in messages:
     860 + if message is not None:
     861 + message_count += 1
     862 + 
     863 + print("\n")
     864 + color_print_green(" [-] ", "Fetching message archive...")
     865 + progress_bar = (
     866 + Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
     867 + )
     868 + 
     869 + with alive_bar(
     870 + message_count,
     871 + dual_line=True,
     872 + title=progress_bar,
     873 + length=20,
     874 + ) as bar:
     875 + 
     876 + to_ent = await client.get_entity(t)
     877 + 
     878 + async for message in client.iter_messages(
     879 + t, limit=None
     880 + ):
     881 + if message is not None:
     882 + 
     883 + try:
     884 + 
     885 + c_archive = pd.DataFrame(
     886 + message_list,
     887 + columns=[
     888 + "To",
     889 + "Message ID",
     890 + "Display_name",
     891 + "ID",
     892 + "Message_text",
     893 + "Original_language",
     894 + "Translated_text",
     895 + "Translation_confidence",
     896 + "Timestamp",
     897 + "Reply",
     898 + "Views",
     899 + ],
     900 + )
     901 + 
     902 + c_forwards = pd.DataFrame(
     903 + forwards_list,
     904 + columns=[
     905 + "To",
     906 + "To_title",
     907 + "From",
     908 + "From_ID",
     909 + "Username",
     910 + "Timestamp",
     911 + ],
     912 + )
     913 + 
     914 + #if message.reactions:
     915 + # if message.reactions.can_see_list:
     916 + # c_reactioneer = pd.DataFrame(
     917 + # user_reaction_list,
     918 + # columns=[
     919 + # "Username",
     920 + # "Full name",
     921 + # "User ID",
     922 + # "Phone number",
     923 + # "Group name",
     924 + # ],
     925 + # )
     926 + 
     927 + if (
     928 + message.replies
     929 + and reply_analysis
     930 + and chat_type == "Channel"
     931 + ):
     932 + if message.replies.replies > 0:
     933 + c_repliers = pd.DataFrame(
     934 + user_replier_list,
     935 + columns=[
     936 + "Username",
     937 + "Full name",
     938 + "User ID",
     939 + "Phone number",
     940 + "Group name",
     941 + ],
     942 + )
     943 + c_replies = pd.DataFrame(
     944 + replies_list,
     945 + columns=[
     946 + "To",
     947 + "Message ID",
     948 + "Reply ID",
     949 + "Display_name",
     950 + "ID",
     951 + "Message_text",
     952 + "Original_language",
     953 + "Translated_text",
     954 + "Translation_confidence",
     955 + "Timestamp",
     956 + ],
     957 + )
     958 + 
     959 + if message.replies:
     960 + if message.replies.replies > 0:
     961 + async for repl in client.iter_messages(
     962 + message.chat_id,
     963 + reply_to=message.id,
     964 + ):
     965 + user = await client.get_entity(
     966 + repl.from_id.user_id
     967 + )
     968 + userdet = populate_user(user, t)
     969 + user_replier_list.append(
     970 + userdet
     971 + )
     972 + mss_txt = process_message(
     973 + repl.text, user_language
     974 + )
     975 + replies_list.append(
     976 + [
     977 + t,
     978 + message.id,
     979 + repl.id,
     980 + userdet[1],
     981 + userdet[2],
     982 + mss_txt["message_text"],
     983 + mss_txt[
     984 + "original_language"
     985 + ],
     986 + mss_txt[
     987 + "translated_text"
     988 + ],
     989 + mss_txt[
     990 + "translation_confidence"
     991 + ],
     992 + parse_tg_date(
     993 + repl.date
     994 + )["timestamp"],
     995 + ]
     996 + )
     997 + 
     998 + display_name = get_display_name(
     999 + message.sender
     1000 + )
     1001 + if chat_type != "Channel":
     1002 + substring = "PeerUser"
     1003 + string = str(message.from_id)
     1004 + if substring in string:
     1005 + user_id = re.sub(
     1006 + "[^0-9]", "", string
     1007 + )
     1008 + nameID = str(user_id)
     1009 + else:
     1010 + nameID = str(message.from_id)
     1011 + else:
     1012 + nameID = to_ent.id
     1013 + 
     1014 + timestamp = parse_tg_date(message.date)[
     1015 + "timestamp"
     1016 + ]
     1017 + reply = message.reply_to_msg_id
     1018 + 
     1019 + _mess = process_message(
     1020 + message.text, user_language
     1021 + )
     1022 + message_text = _mess["message_text"]
     1023 + original_language = _mess[
     1024 + "original_language"
     1025 + ]
     1026 + translated_text = _mess["translated_text"]
     1027 + translation_confidence = _mess[
     1028 + "translation_confidence"
     1029 + ]
     1030 + 
     1031 + if message.forwards is not None:
     1032 + forwards = int(message.forwards)
     1033 + else:
     1034 + forwards = "None"
     1035 + 
     1036 + if message.views is not None:
     1037 + views = int(message.views)
     1038 + else:
     1039 + views = "Not found"
     1040 + 
     1041 + #if message.reactions:
     1042 + #if message.reactions.can_see_list:
     1043 + #print(dir(message.reactions.results))
     1044 + #print("#### TODO: REACTIONS")
     1045 + 
     1046 + if media_archive == True:
     1047 + #add a progress bar for each file download
     1048 + if message.media:
     1049 + path = await message.download_media(
     1050 + file=media_directory
     1051 + )
     1052 + files.append(path)
     1053 + else:
     1054 + pass
     1055 + 
     1056 +
     1057 + 
     1058 + message_list.append(
     1059 + [
     1060 + t,
     1061 + message.id,
     1062 + display_name,
     1063 + nameID,
     1064 + message_text,
     1065 + original_language,
     1066 + translated_text,
     1067 + translation_confidence,
     1068 + timestamp,
     1069 + reply,
     1070 + views,
     1071 + ]
     1072 + )
     1073 + 
     1074 + if message.forward is not None:
     1075 + forward_verify = True
     1076 + try:
     1077 + forward_count += 1
     1078 + to_title = to_ent.title
     1079 + f_from_id = (
     1080 + message.forward.original_fwd.from_id
     1081 + )
     1082 + 
     1083 + if f_from_id is not None:
     1084 + ent = await client.get_entity(
     1085 + f_from_id
     1086 + )
     1087 + user_string = "user_id"
     1088 + channel_string = "broadcast"
     1089 + 
     1090 + if user_string in str(ent):
     1091 + ent_type = "User"
     1092 + else:
     1093 + if channel_string in str(
     1094 + ent
     1095 + ):
     1096 + if (
     1097 + ent.broadcast
     1098 + is True
     1099 + ):
     1100 + ent_type = "Channel"
     1101 + elif (
     1102 + ent.megagroup
     1103 + is True
     1104 + ):
     1105 + ent_type = (
     1106 + "Megagroup"
     1107 + )
     1108 + elif (
     1109 + ent.gigagroup
     1110 + is True
     1111 + ):
     1112 + ent_type = (
     1113 + "Gigagroup"
     1114 + )
     1115 + else:
     1116 + ent_type = "Chat"
     1117 + else:
     1118 + continue
     1119 + 
     1120 + if ent.username is not None:
     1121 + username = ent.username
     1122 + else:
     1123 + username = "none"
     1124 + 
     1125 + if ent_type != "Chat":
     1126 + result = str(ent.title)
     1127 + else:
     1128 + result = "none"
     1129 + 
     1130 + if ent_type == "User":
     1131 + substring_1 = "PeerUser"
     1132 + string_1 = str(ent.user_id)
     1133 + if substring_1 in string_1:
     1134 + user_id = re.sub(
     1135 + "[^0-9]",
     1136 + "",
     1137 + string_1,
     1138 + )
     1139 + user_id = await client.get_entity(
     1140 + PeerUser(
     1141 + int(user_id)
     1142 + )
     1143 + )
     1144 + user_id = str(user_id)
     1145 + result = (
     1146 + "User: "
     1147 + + str(
     1148 + ent.first_name
     1149 + )
     1150 + + " / ID: "
     1151 + + str(user_id)
     1152 + )
     1153 + else:
     1154 + result = str(ent.title)
     1155 + else:
     1156 + result = str(ent.title)
     1157 + 
     1158 + forwards_list.append(
     1159 + [
     1160 + t,
     1161 + to_title,
     1162 + result,
     1163 + f_from_id,
     1164 + username,
     1165 + timestamp,
     1166 + ]
     1167 + )
     1168 + 
     1169 +
     1170 + 
     1171 + except ChannelPrivateError:
     1172 + private_count += 1
     1173 + continue
     1174 + 
     1175 + except Exception as e:
     1176 + print("An exception occurred.", e)
     1177 + continue
     1178 + 
     1179 + except Exception as e:
     1180 + print("An exception occurred.", e)
     1181 + 
     1182 + else:
     1183 + message_list.append(
     1184 + [
     1185 + "None",
     1186 + "None",
     1187 + "None",
     1188 + "None",
     1189 + "None",
     1190 + "None",
     1191 + "None",
     1192 + "None",
     1193 + ]
     1194 + )
     1195 + pass
     1196 + 
     1197 + time.sleep(0.5)
     1198 + bar()
     1199 + 
     1200 + if reply_analysis is True:
     1201 + if len(replies_list) > 0:
     1202 + with open(
     1203 + reply_file_archive, "w+", encoding="utf-8"
     1204 + ) as rep_file:
     1205 + c_replies.to_csv(rep_file, sep=";")
     1206 + 
     1207 + if len(user_replier_list) > 0:
     1208 + with open(
     1209 + reply_memberlist_filename, "w+", encoding="utf-8"
     1210 + ) as repliers_file:
     1211 + c_repliers.to_csv(repliers_file, sep=";")
     1212 + 
     1213 + with open(
     1214 + file_archive, "w+", encoding="utf-8"
     1215 + ) as archive_file:
     1216 + c_archive.to_csv(archive_file, sep=";")
     1217 + 
     1218 + if json_check == True:
     1219 + c_archive.to_json(
     1220 + json_file + alphanumeric + "_archive.json",
     1221 + orient="records",
     1222 + compression="infer",
     1223 + lines=True,
     1224 + index=True,
     1225 + )
     1226 + else:
     1227 + pass
     1228 + 
     1229 + if forwards_check is True:
     1230 + with open(
     1231 + file_forwards, "w+", encoding="utf-8"
     1232 + ) as forwards_file:
     1233 + c_forwards.to_csv(forwards_file, sep=";")
     1234 + 
     1235 + if json_check == True:
     1236 + c_forwards.to_json(
     1237 + json_file + alphanumeric + "_edgelist.json",
     1238 + orient="records",
     1239 + compression="infer",
     1240 + lines=True,
     1241 + index=True,
     1242 + )
     1243 + else:
     1244 + pass
     1245 + else:
     1246 + pass
     1247 + 
     1248 + messages_found = int(c_archive.To.count()) - 1
     1249 + message_frequency_count = {}
     1250 + message_text = {}
     1251 + word_count = {}
     1252 + most_used_words = {}
     1253 + most_used_words_filtered = {}
     1254 + # message stats, top words
     1255 + 
     1256 + if chat_type != "Channel":
     1257 + pcount = c_archive.Display_name.count()
     1258 + pvalue_count = c_archive["Display_name"].value_counts()
     1259 + df03 = pvalue_count.rename_axis(
     1260 + "unique_values"
     1261 + ).reset_index(name="counts")
     1262 + 
     1263 + top_poster_one = str(df03.iloc[0]["unique_values"])
     1264 + top_pvalue_one = df03.iloc[0]["counts"]
     1265 + top_poster_two = str(df03.iloc[1]["unique_values"])
     1266 + top_pvalue_two = df03.iloc[1]["counts"]
     1267 + top_poster_three = str(df03.iloc[2]["unique_values"])
     1268 + top_pvalue_three = df03.iloc[2]["counts"]
     1269 + top_poster_four = str(df03.iloc[3]["unique_values"])
     1270 + top_pvalue_four = df03.iloc[3]["counts"]
     1271 + top_poster_five = str(df03.iloc[4]["unique_values"])
     1272 + top_pvalue_five = df03.iloc[4]["counts"]
     1273 + 
     1274 + poster_one = (
     1275 + str(top_poster_one)
     1276 + + ", "
     1277 + + str(top_pvalue_one)
     1278 + + " messages"
     1279 + )
     1280 + poster_two = (
     1281 + str(top_poster_two)
     1282 + + ", "
     1283 + + str(top_pvalue_two)
     1284 + + " messages"
     1285 + )
     1286 + poster_three = (
     1287 + str(top_poster_three)
     1288 + + ", "
     1289 + + str(top_pvalue_three)
     1290 + + " messages"
     1291 + )
     1292 + poster_four = (
     1293 + str(top_poster_four)
     1294 + + ", "
     1295 + + str(top_pvalue_four)
     1296 + + " messages"
     1297 + )
     1298 + poster_five = (
     1299 + str(top_poster_five)
     1300 + + ", "
     1301 + + str(top_pvalue_five)
     1302 + + " messages"
     1303 + )
     1304 + 
     1305 + df04 = c_archive.Display_name.unique()
     1306 + plength = len(df03)
     1307 + unique_active = len(df04)
     1308 + # one day this'll work out sleeping times
     1309 + # print(c_t_stats)
     1310 + 
     1311 + elif reply_analysis is True:
     1312 + if len(replies_list) > 0:
     1313 + replier_count = c_repliers["User id"].count()
     1314 + replier_value_count = c_repliers["User id"].value_counts()
     1315 + replier_df = replier_value_count.rename_axis(
     1316 + "unique_values"
     1317 + ).reset_index(name="counts")
     1318 + 
     1319 + top_replier_one = str(replier_df.iloc[0]["unique_values"])
     1320 + top_replier_value_one = replier_df.iloc[0]["counts"]
     1321 + top_replier_two = str(replier_df.iloc[1]["unique_values"])
     1322 + top_replier_value_two = replier_df.iloc[1]["counts"]
     1323 + top_replier_three = str(replier_df.iloc[2]["unique_values"])
     1324 + top_replier_value_three = replier_df.iloc[2]["counts"]
     1325 + top_replier_four = str(replier_df.iloc[3]["unique_values"])
     1326 + top_replier_value_four = replier_df.iloc[3]["counts"]
     1327 + top_replier_five = str(replier_df.iloc[4]["unique_values"])
     1328 + top_replier_value_five = replier_df.iloc[4]["counts"]
     1329 + 
     1330 + replier_one = (
     1331 + str(top_replier_one)
     1332 + + ", "
     1333 + + str(top_replier_value_one)
     1334 + + " replies"
     1335 + )
     1336 + replier_two = (
     1337 + str(top_replier_two)
     1338 + + ", "
     1339 + + str(top_replier_value_two)
     1340 + + " replies"
     1341 + )
     1342 + replier_three = (
     1343 + str(top_replier_three)
     1344 + + ", "
     1345 + + str(top_replier_value_three)
     1346 + + " replies"
     1347 + )
     1348 + replier_four = (
     1349 + str(top_replier_four)
     1350 + + ", "
     1351 + + str(top_replier_value_four)
     1352 + + " replies"
     1353 + )
     1354 + replier_five = (
     1355 + str(top_replier_five)
     1356 + + ", "
     1357 + + str(top_replier_value_five)
     1358 + + " replies"
     1359 + )
     1360 + 
     1361 + replier_count_df = c_repliers["User id"].unique()
     1362 + replier_length = len(replier_df)
     1363 + replier_unique = len(replier_count_df)
     1364 + 
     1365 + else:
     1366 + pass
     1367 + 
     1368 + #print("\n")
     1369 + color_print_green(" [+] Chat archive saved", "")
     1370 + color_print_green(" ┬ Chat statistics", "")
     1371 + color_print_green(
     1372 + " ├ Number of messages found: ", str(messages_found)
     1373 + )
     1374 + 
     1375 + if chat_type != "Channel":
     1376 + color_print_green(
     1377 + " ├ Top poster 1: ", str(poster_one)
     1378 + )
     1379 + color_print_green(
     1380 + " ├ Top poster 2: ", str(poster_two)
     1381 + )
     1382 + color_print_green(
     1383 + " ├ Top poster 3: ", str(poster_three)
     1384 + )
     1385 + color_print_green(
     1386 + " ├ Top poster 4: ", str(poster_four)
     1387 + )
     1388 + color_print_green(
     1389 + " ├ Top poster 5: ", str(poster_five)
     1390 + )
     1391 + color_print_green(
     1392 + " ├ Total unique posters: ", str(unique_active)
     1393 + )
     1394 +
     1395 + else:
     1396 + pass
     1397 + # timestamp analysis
     1398 + # print(Fore.GREEN
     1399 + # + ' ├ Number of messages: '
     1400 + # + Style.RESET_ALL
     1401 + # + str(message_count))
     1402 + 
     1403 + color_print_green(
     1404 + " â”” Archive saved to: ", str(file_archive)
     1405 + )
     1406 + 
     1407 + if reply_analysis is True:
     1408 + if len(replies_list) > 0:
     1409 + middle_char = "├"
     1410 + if user_replier_list == 0:
     1411 + middle_char = "â””"
     1412 + 
     1413 + #print("\n")
     1414 + color_print_green(" [+] Replies analysis ", "")
     1415 + color_print_green(" ┬ Chat statistics", "")
     1416 + color_print_green(
     1417 + f" {middle_char} Archive of replies saved to: ",
     1418 + str(reply_file_archive),
     1419 + )
     1420 + if len(user_replier_list) > 0:
     1421 + color_print_green(
     1422 + " â”” Active members list who replied to messages, saved to: ",
     1423 + str(reply_memberlist_filename),
     1424 + )
     1425 + 
     1426 + color_print_green(
     1427 + " ├ Top replier 1: ", str(replier_one)
     1428 + )
     1429 + color_print_green(
     1430 + " ├ Top replier 2: ", str(replier_two)
     1431 + )
     1432 + color_print_green(
     1433 + " ├ Top replier 3: ", str(replier_three)
     1434 + )
     1435 + color_print_green(
     1436 + " ├ Top replier 4: ", str(replier_four)
     1437 + )
     1438 + color_print_green(
     1439 + " ├ Top replier 5: ", str(replier_five)
     1440 + )
     1441 + color_print_green(
     1442 + " ├ Total unique repliers: ", str(replier_unique)
     1443 + )
     1444 + # add a figure for unique current posters who are active
     1445 + 
     1446 + if forwards_check is True:
     1447 + if forward_count >= 15:
     1448 + forwards_found = c_forwards.From.count()
     1449 + value_count = c_forwards["From"].value_counts()
     1450 + c_f_stats = value_count.rename_axis(
     1451 + "unique_values"
     1452 + ).reset_index(name="counts")
     1453 + 
     1454 + top_forward_one = c_f_stats.iloc[0]["unique_values"]
     1455 + top_value_one = c_f_stats.iloc[0]["counts"]
     1456 + top_forward_two = c_f_stats.iloc[1]["unique_values"]
     1457 + top_value_two = c_f_stats.iloc[1]["counts"]
     1458 + top_forward_three = c_f_stats.iloc[2][
     1459 + "unique_values"
     1460 + ]
     1461 + top_value_three = c_f_stats.iloc[2]["counts"]
     1462 + top_forward_four = c_f_stats.iloc[3][
     1463 + "unique_values"
     1464 + ]
     1465 + top_value_four = c_f_stats.iloc[3]["counts"]
     1466 + top_forward_five = c_f_stats.iloc[4][
     1467 + "unique_values"
     1468 + ]
     1469 + top_value_five = c_f_stats.iloc[4]["counts"]
     1470 + 
     1471 + forward_one = (
     1472 + str(top_forward_one)
     1473 + + ", "
     1474 + + str(top_value_one)
     1475 + + " forwarded messages"
     1476 + )
     1477 + forward_two = (
     1478 + str(top_forward_two)
     1479 + + ", "
     1480 + + str(top_value_two)
     1481 + + " forwarded messages"
     1482 + )
     1483 + forward_three = (
     1484 + str(top_forward_three)
     1485 + + ", "
     1486 + + str(top_value_three)
     1487 + + " forwarded messages"
     1488 + )
     1489 + forward_four = (
     1490 + str(top_forward_four)
     1491 + + ", "
     1492 + + str(top_value_four)
     1493 + + " forwarded messages"
     1494 + )
     1495 + forward_five = (
     1496 + str(top_forward_five)
     1497 + + ", "
     1498 + + str(top_value_five)
     1499 + + " forwarded messages"
     1500 + )
     1501 + 
     1502 + c_f_unique = c_forwards.From.unique()
     1503 + unique_forwards = len(c_f_unique)
     1504 + 
     1505 + #print("\n")
     1506 + color_print_green(" [+] Edgelist saved", "")
     1507 + color_print_green(
     1508 + " ┬ Forwarded message statistics", ""
     1509 + )
     1510 + color_print_green(
     1511 + " ├ Forwarded messages found: ",
     1512 + str(forward_count),
     1513 + )
     1514 + color_print_green(
     1515 + " ├ Forwards from active public chats: ",
     1516 + str(forwards_found),
     1517 + )
     1518 + color_print_green(
     1519 + " ├ Forwards from private (or now private) chats: ",
     1520 + str(private_count),
     1521 + )
     1522 + color_print_green(
     1523 + " ├ Unique forward sources: ",
     1524 + str(unique_forwards),
     1525 + )
     1526 + color_print_green(
     1527 + " ├ Top forward source 1: ", str(forward_one)
     1528 + )
     1529 + color_print_green(
     1530 + " ├ Top forward source 2: ", str(forward_two)
     1531 + )
     1532 + color_print_green(
     1533 + " ├ Top forward source 3: ",
     1534 + str(forward_three),
     1535 + )
     1536 + color_print_green(
     1537 + " ├ Top forward source 4: ", str(forward_four)
     1538 + )
     1539 + color_print_green(
     1540 + " ├ Top forward source 5: ", str(forward_five)
     1541 + )
     1542 + color_print_green(
     1543 + " â”” Edgelist saved to: ", edgelist_file
     1544 + )
     1545 + #print("\n")
     1546 + 
     1547 + else:
     1548 + #print("\n")
     1549 + color_print_green(
     1550 + " [!] Insufficient forwarded messages found",
     1551 + edgelist_file,
     1552 + )
     1553 + else:
     1554 + pass
     1555 + 
     1556 + if user_check == True:
     1557 + my_user = None
     1558 + try:
     1559 + 
     1560 + user = int(t)
     1561 + my_user = await client.get_entity(PeerUser(int(user)))
     1562 + 
     1563 + user_first_name = my_user.first_name
     1564 + user_last_name = my_user.last_name
     1565 + if user_last_name is not None:
     1566 + user_full_name = (
     1567 + str(user_first_name) + " " + str(user_last_name)
     1568 + )
     1569 + else:
     1570 + user_full_name = str(user_first_name)
     1571 + 
     1572 + if my_user.photo is not None:
     1573 + user_photo = my_user.photo.photo_id
     1574 + else:
     1575 + user_photo = "None"
     1576 + 
     1577 + if my_user.restriction_reason is not None:
     1578 + ios_restriction = entity.restriction_reason[0]
     1579 + if 1 in entity.restriction_reason:
     1580 + android_restriction = entity.restriction_reason[1]
     1581 + user_restrictions = (
     1582 + str(ios_restriction)
     1583 + + ", "
     1584 + + str(android_restriction)
     1585 + )
     1586 + else:
     1587 + user_restrictions = str(ios_restriction)
     1588 + else:
     1589 + user_restrictions = "None"
     1590 + 
     1591 + color_print_green(" [+] ", "User details for " + t)
     1592 + color_print_green(" ├ Username: ", str(my_user.username))
     1593 + color_print_green(" ├ Name: ", str(user_full_name))
     1594 + color_print_green(" ├ Verification: ", str(my_user.verified))
     1595 + color_print_green(" ├ Photo ID: ", str(user_photo))
     1596 + color_print_green(" ├ Phone number: ", str(my_user.phone))
     1597 + color_print_green(
     1598 + " ├ Access hash: ", str(my_user.access_hash)
     1599 + )
     1600 + color_print_green(" ├ Language: ", str(my_user.lang_code))
     1601 + color_print_green(" ├ Bot: ", str(my_user.bot))
     1602 + color_print_green(" ├ Scam: ", str(my_user.scam))
     1603 + color_print_green(" â”” Restrictions: ", str(user_restrictions))
     1604 + 
     1605 + except ValueError:
     1606 + pass
     1607 + if my_user is None:
     1608 + print(
     1609 + Fore.GREEN
     1610 + + " [!] "
     1611 + + Style.RESET_ALL
     1612 + + "User not found, this is likely because Telepathy has not encountered them yet."
     1613 + )
     1614 + 
     1615 + if location_check == True:
     1616 + 
     1617 + location = t
     1618 + 
     1619 + print(
     1620 + Fore.GREEN
     1621 + + " [!] "
     1622 + + Style.RESET_ALL
     1623 + + "Searching for users near "
     1624 + + location
     1625 + + "\n"
     1626 + )
     1627 + latitude, longitude = location.split(sep=",")
     1628 + 
     1629 + locations_file = telepathy_file + "locations/"
     1630 + 
     1631 + try:
     1632 + os.makedirs(locations_file)
     1633 + except FileExistsError:
     1634 + pass
     1635 + 
     1636 + save_file = (
     1637 + locations_file
     1638 + + latitude
     1639 + + "_"
     1640 + + longitude
     1641 + + "_"
     1642 + + "locations_"
     1643 + + filetime_clean
     1644 + + ".csv"
     1645 + )
     1646 + 
     1647 + locations_list = []
     1648 + result = await client(
     1649 + functions.contacts.GetLocatedRequest(
     1650 + geo_point=types.InputGeoPoint(
     1651 + lat=float(latitude),
     1652 + long=float(longitude),
     1653 + accuracy_radius=42,
     1654 + ),
     1655 + self_expires=42,
     1656 + )
     1657 + )
     1658 + 
     1659 + # progress bar?
     1660 + 
     1661 + for user in result.updates[0].peers:
     1662 + try:
     1663 + user_df = pd.DataFrame(
     1664 + locations_list, columns=["User_ID", "Distance"]
     1665 + )
     1666 + if hasattr(user, "peer"):
     1667 + ID = user.peer.user_id
     1668 + else:
     1669 + pass
     1670 + if hasattr(user, "distance"):
     1671 + distance = user.distance
     1672 + else:
     1673 + pass
     1674 + 
     1675 + locations_list.append([ID, distance])
     1676 + 
     1677 + except:
     1678 + pass
     1679 + 
     1680 + d_500 = 0
     1681 + d_1000 = 0
     1682 + d_2000 = 0
     1683 + d_3000 = 0
     1684 + 
     1685 + for account, distance in user_df.itertuples(index=False):
     1686 + account = int(account)
     1687 + my_user = await client.get_entity(PeerUser(account))
     1688 + user_id = my_user.id
     1689 + name = my_user.first_name
     1690 + distance = int(distance)
     1691 + 
     1692 + if distance == 500:
     1693 + d_500 += 1
     1694 + elif distance == 1000:
     1695 + d_1000 += 1
     1696 + elif distance == 2000:
     1697 + d_2000 += 1
     1698 + elif distance == 3000:
     1699 + d_3000 += 1
     1700 + 
     1701 + with open(
     1702 + save_file, "w+", encoding="utf-8"
     1703 + ) as f: # could one day append, including access time to differentiate
     1704 + user_df.to_csv(f, sep=";", index=False)
     1705 + 
     1706 + total = len(locations_list)
     1707 + 
     1708 + color_print_green(" [+] Users located", "")
     1709 + color_print_green(" ├ Users within 500m: ", str(d_500))
     1710 + color_print_green(" ├ Users within 1000m: ", str(d_1000))
     1711 + color_print_green(" ├ Users within 2000m: ", str(d_2000))
     1712 + color_print_green(" ├ Users within 3000m: ", str(d_3000))
     1713 + color_print_green(" ├ Total users found: ", str(total))
     1714 + color_print_green(" â”” Location list saved to: ", save_file)
     1715 + 
     1716 + # can also do the same for channels with similar output file to users
     1717 + # may one day add trilateration to find users closest to exact point
     1718 +
     1719 + with client:
     1720 + client.loop.run_until_complete(main())
     1721 + 
     1722 +if __name__ == "__main__":
     1723 + cli()
     1724 + 
  • ■ ■ ■ ■ ■ ■
    build/lib/telepathy/utils.py
     1 +from colorama import Fore, Back, Style
     2 +from googletrans import Translator, constants
     3 +from .const import __version__, user_agent
     4 +import requests
     5 +import textwrap
     6 +from bs4 import BeautifulSoup
     7 +import random
     8 + 
     9 +def print_banner():
     10 + print(
     11 + Fore.GREEN
     12 + + """
     13 + ______ __ __ __
     14 + /_ __/__ / /__ ____ ____ _/ /_/ /_ __ __
     15 + / / / _ \/ / _ \/ __ \/ __ `/ __/ __ \/ / / /
     16 + / / / __/ / __/ /_/ / /_/ / /_/ / / / /_/ /
     17 + /_/ \___/_/\___/ .___/\__,_/\__/_/ /_/\__, /
     18 + /_/ /____/
     19 + -- An OSINT toolkit for investigating Telegram chats.
     20 + -- Developed by @jordanwildon | Version """ + __version__ + """.
     21 + """ + Style.RESET_ALL
     22 + )
     23 + 
     24 +def parse_tg_date(dd):
     25 + year = str(format(dd.year, "02d"))
     26 + month = str(format(dd.month, "02d"))
     27 + day = str(format(dd.day, "02d"))
     28 + hour = str(format(dd.hour, "02d"))
     29 + minute = str(format(dd.minute, "02d"))
     30 + second = str(format(dd.second, "02d"))
     31 + date = year + "-" + month + "-" + day
     32 + mtime = hour + ":" + minute + ":" + second
     33 + timestamp = date + "T" + mtime + "+00:00"
     34 + return {"timestamp":timestamp, "date":date, "mtime":mtime}
     35 + 
     36 + 
     37 +def populate_user(user, group_or_chat):
     38 + if user.username:
     39 + username = user.username
     40 + else:
     41 + username = "n/a"
     42 + if user.first_name:
     43 + first_name = user.first_name
     44 + else:
     45 + first_name = ""
     46 + if user.last_name:
     47 + last_name = user.last_name
     48 + else:
     49 + last_name = ""
     50 + if user.phone:
     51 + phone = user.phone
     52 + else:
     53 + phone = "n/a"
     54 + full_name = (first_name + " " + last_name).strip()
     55 + return [username, full_name, user.id, phone, group_or_chat]
     56 + 
     57 + 
     58 +def process_message(mess, user_lang):
     59 + 
     60 + if mess is not None:
     61 + mess_txt = '"' + mess + '"'
     62 + else:
     63 + mess_txt = "none"
     64 + 
     65 + if mess_txt != "none":
     66 + translator = Translator()
     67 + detection = translator.detect(mess_txt)
     68 + language_code = detection.lang
     69 + translation_confidence = detection.confidence
     70 + translation = translator.translate(mess_txt, dest=user_lang)
     71 + original_language = translation.src
     72 + translated_text = translation.text
     73 + else:
     74 + original_language = user_lang
     75 + translated_text = "n/a"
     76 + translation_confidence = "n/a"
     77 + 
     78 + return {
     79 + "original_language": original_language,
     80 + "translated_text": translated_text,
     81 + "translation_confidence": translation_confidence,
     82 + "message_text": mess_txt,
     83 + }
     84 + 
     85 +def process_description(desc, user_lang):
     86 + if desc is not None:
     87 + desc_txt = '"' + desc + '"'
     88 + else:
     89 + desc_txt = "none"
     90 + 
     91 + if desc_txt != "none":
     92 + translator = Translator()
     93 + detection = translator.detect(desc_txt)
     94 + language_code = detection.lang
     95 + translation_confidence = detection.confidence
     96 + translation = translator.translate(desc_txt, dest=user_lang)
     97 + original_language = translation.src
     98 + translated_text = translation.text
     99 + else:
     100 + original_language = user_lang
     101 + translated_text = "n/a"
     102 + translation_confidence = "n/a"
     103 + 
     104 + return {
     105 + "original_language": original_language,
     106 + "translated_text": translated_text,
     107 + "translation_confidence": translation_confidence,
     108 + "description_text": desc_txt,
     109 + }
     110 + 
     111 +def color_print_green(first_string,second_string):
     112 + print(
     113 + Fore.GREEN
     114 + + first_string
     115 + + Style.RESET_ALL
     116 + + second_string
     117 + )
     118 + 
     119 +def parse_html_page(url):
     120 + s = requests.Session()
     121 + s.max_redirects = 10
     122 + s.headers["User-Agent"] = random.choice(user_agent)
     123 + URL = s.get(url)
     124 + URL.encoding = "utf-8"
     125 + html_content = URL.text
     126 + soup = BeautifulSoup(html_content, "html.parser")
     127 + name = ""
     128 + group_description = ""
     129 + total_participants = ""
     130 + try:
     131 + name = soup.find(
     132 + "div", {"class": ["tgme_page_title"]}
     133 + ).text
     134 + except:
     135 + name = "Not found"
     136 + try:
     137 + group_description = soup.find(
     138 + "div", {"class": ["tgme_page_description"]}
     139 + ).text
     140 + descript = Fore.GREEN + "Description: " + Style.RESET_ALL+ group_description
     141 + prefix = descript + " "
     142 + except:
     143 + group_description = "None"
     144 + descript = Fore.GREEN + "Description: " + Style.RESET_ALL+ group_description
     145 + prefix = descript + " "
     146 + 
     147 + try:
     148 + group_participants = soup.find(
     149 + "div", {"class": ["tgme_page_extra"]}
     150 + ).text
     151 + sep = "members"
     152 + stripped = group_participants.split(sep, 1)[0]
     153 + total_participants = (
     154 + stripped.replace(" ", "")
     155 + .replace("members", "")
     156 + .replace("subscribers", "")
     157 + .replace("member", "")
     158 + )
     159 + except:
     160 + total_participants = "Not found" # could be due to restriction, might need to mention
     161 + 
     162 + return {"name":name,"group_description":group_description, "total_participants":total_participants}
  • dist/telepathy-2.2.50-py2.py3-none-any.whl
    Binary file.
  • dist/telepathy-2.2.50.tar.gz
    Binary file.
  • ■ ■ ■ ■ ■ ■
    requirements.txt
     1 +alive-progress==2.4.1
     2 +beautifulsoup4==4.11.1
     3 +Click==7.1.2
     4 +colorama==0.4.3
     5 +pandas==1.4.2
     6 +Telethon==1.25.2
     7 +requests==2.28.1
     8 +googletrans==4.0.0rc1
     9 +pprintpp==0.4.0
  • ■ ■ ■ ■ ■ ■
    setup.py
     1 +from setuptools import setup, find_packages
     2 + 
     3 +setup(
     4 + name="telepathy",
     5 + version='2.2.50',
     6 + author='Jordan Wildon',
     7 + author_email='[email protected]',
     8 + packages=['telepathy'],
     9 + package_dir={'':'src'},
     10 + url='https://pypi.python.org/pypi/telepathy/',
     11 + license='LICENSE.txt',
     12 + description='An OSINT toolkit for investigating Telegram chats.',
     13 + long_description=open('README.md').read(),
     14 + long_description_content_type='text/markdown',
     15 + install_requires=[
     16 + 'click ~= 7.1.2',
     17 + 'telethon == 1.25.2',
     18 + 'pandas == 1.4.2',
     19 + 'colorama ~= 0.4.3',
     20 + 'alive_progress == 2.4.1',
     21 + 'beautifulsoup4 == 4.11.1',
     22 + 'requests ~= 2.28.1',
     23 + 'googletrans == 4.0.0rc1',
     24 + 'pprintpp == 0.4.0',
     25 + ],
     26 + entry_points='''
     27 + [console_scripts]
     28 + telepathy=telepathy.telepathy:cli
     29 + ''',
     30 +)
     31 + 
  • ■ ■ ■ ■ ■
    src/__init.py__
     1 + 
  • ■ ■ ■ ■ ■
    src/telepathy/__init.py__
     1 + 
  • ■ ■ ■ ■ ■ ■
    src/telepathy/const.py
     1 +__author__ = "Jordan Wildon (@jordanwildon)"
     2 +__license__ = "MIT License"
     3 +__version__ = "2.2.50"
     4 +__maintainer__ = "Jordan Wildon"
     5 +__email__ = "[email protected]"
     6 +__status__ = "Development"
     7 + 
     8 +user_agent = [
     9 + # Chrome
     10 + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
     11 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
     12 + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
     13 + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
     14 + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
     15 + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
     16 + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
     17 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
     18 + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
     19 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
     20 + # Firefox
     21 + "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)",
     22 + "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
     23 + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)",
     24 + "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko",
     25 + "Mozilla/5.0 (Windows NT 6.2; WOW64; Trident/7.0; rv:11.0) like Gecko",
     26 + "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
     27 + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)",
     28 + "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko",
     29 + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
     30 + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; Trident/7.0; rv:11.0) like Gecko",
     31 + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)",
     32 + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)",
     33 + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
     34 + # Safari
     35 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) RockMelt/0.9.50.549 Chrome/10.0.648.205 Safari/534.16"
     36 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.18"
     37 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.19 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.19"
     38 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21"
     39 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.682.0 Safari/534.21"
     40 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3"
     41 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3"
     42 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.53 Safari/534.3"
     43 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Iron/6.0.475 Safari/534"
     44 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.724.100 Safari/534.30"
     45 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.500.0 Safari/534.6"
     46 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7"
     47 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7 ChromePlus/1.5.0.0 ChromePlus/1.5.0.0"
     48 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7 ChromePlus/1.5.0.0alpha1"
     49 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Flock/3.5.2.4599 Chrome/7.0.517.442 Safari/534.7"
     50 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Iron/7.0.520.0 Chrome/7.0.520.0 Safari/534.7"
     51 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Iron/7.0.520.1 Chrome/7.0.520.1 Safari/534.7"
     52 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Iron/7.0.520.1 Safari/534.7"
     53 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) RockMelt/0.8.36.116 Chrome/7.0.517.44 Safari/534.7"
     54 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) RockMelt/0.8.36.128 Chrome/7.0.517.44 Safari/534.7"
     55 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.9 (KHTML, like Gecko) Chrome/7.0.531.0 Safari/534.9"
     56 + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Iron/0.2.152.0 Safari/13657880.525",
     57 +]
  • ■ ■ ■ ■ ■ ■
    src/telepathy/telepathy.py
     1 +#!/usr/bin/python3
     2 + 
     3 + 
     4 +"""Telepathy cli interface:
     5 + An OSINT toolkit for investigating Telegram chats.
     6 +"""
     7 + 
     8 +from tokenize import group
     9 +import pandas as pd
     10 +import datetime
     11 +import requests
     12 +import json
     13 +import random
     14 +import glob
     15 +import csv
     16 +import os
     17 +import getpass
     18 +import click
     19 +import re
     20 +import textwrap
     21 +import time
     22 +import pprint
     23 + 
     24 +from telepathy.utils import (
     25 + print_banner,
     26 + color_print_green,
     27 + populate_user,
     28 + process_message,
     29 + process_description,
     30 + parse_tg_date,
     31 + parse_html_page
     32 +)
     33 +import telepathy.const as const
     34 + 
     35 +from colorama import Fore, Back, Style
     36 + 
     37 +from telethon.errors import SessionPasswordNeededError, ChannelPrivateError
     38 +from telethon.tl.types import (
     39 + InputPeerEmpty,
     40 + PeerUser,
     41 + PeerChat,
     42 + PeerChannel,
     43 + PeerLocated,
     44 + ChannelParticipantCreator,
     45 + ChannelParticipantAdmin,
     46 +)
     47 +from telethon.tl.functions.messages import GetDialogsRequest
     48 +from telethon import TelegramClient, functions, types, utils
     49 +from telethon.utils import get_display_name, get_message_id
     50 +from alive_progress import alive_bar
     51 +from bs4 import BeautifulSoup
     52 + 
     53 + 
     54 +@click.command()
     55 +@click.option(
     56 + "--target",
     57 + "-t",
     58 + #default="",
     59 + multiple=True,
     60 + help="Specifies a chat to investigate.",
     61 +)
     62 +@click.option(
     63 + "--comprehensive",
     64 + "-c",
     65 + is_flag=True,
     66 + help="Comprehensive scan, includes archiving.",
     67 +)
     68 +@click.option(
     69 + "--media", "-m", is_flag=True, help="Archives media in the specified chat."
     70 +)
     71 +@click.option("--forwards", "-f", is_flag=True, help="Scrapes forwarded messages.")
     72 +@click.option("--user", "-u", is_flag=True, help="Looks up a specified user ID.")
     73 +@click.option(
     74 + "--location", "-l", is_flag=True, help="Finds users near to specified coordinates."
     75 +)
     76 +@click.option(
     77 + "--alt", "-a", is_flag=True, default=False, help="Uses an alternative login."
     78 +)
     79 +@click.option("--json", "-j", is_flag=True, default=False, help="Export to JSON.")
     80 +@click.option(
     81 + "--export",
     82 + "-e",
     83 + is_flag=True,
     84 + default=False,
     85 + help="Export a list of chats your account is part of.",
     86 +)
     87 +@click.option(
     88 + "--replies",
     89 + "-r",
     90 + is_flag=True,
     91 + default=False,
     92 + help="Enable replies analysis in channels.",
     93 +)
     94 +def cli(
     95 + target, comprehensive, media, forwards, user, location, alt, json, export, replies
     96 +):
     97 + print_banner()
     98 + telepathy_file = "./telepathy_files/"
     99 + try:
     100 + os.makedirs(telepathy_file)
     101 + except FileExistsError:
     102 + pass
     103 + 
     104 + # Defining default values
     105 + basic = False
     106 + comp_check = False
     107 + media_archive = False
     108 + forwards_check = False
     109 + forward_verify = False
     110 + reply_analysis = False
     111 + user_check = False
     112 + location_check = False
     113 + last_date = None
     114 + chunk_size = 1000
     115 + filetime = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M")
     116 + filetime_clean = str(filetime)
     117 + 
     118 + # Will add more languages later
     119 + user_language = "en"
     120 + 
     121 + if target:
     122 + basic = True
     123 + if replies:
     124 + reply_analysis = True
     125 + if forwards:
     126 + forwards_check = True
     127 + if user:
     128 + user_check = True
     129 + basic = False
     130 + if location:
     131 + location_check = True
     132 + basic = False
     133 + if comprehensive:
     134 + comp_check = True
     135 + if media:
     136 + media_archive = True
     137 + if export:
     138 + t = " "
     139 + if alt:
     140 + alt_check = True
     141 + else:
     142 + alt_check = False
     143 + 
     144 + if json:
     145 + json_check = True
     146 + json_file = telepathy_file + "json_files/"
     147 + try:
     148 + os.makedirs(json_file)
     149 + except FileExistsError:
     150 + pass
     151 + else:
     152 + json_check = False
     153 + 
     154 + if alt_check == True:
     155 + login = telepathy_file + "login_alt.txt"
     156 + 
     157 + if os.path.isfile(login) == False:
     158 + api_id = input(" Please enter your API ID:\n")
     159 + api_hash = input(" Please enter your API Hash:\n")
     160 + phone_number = input(" Please enter your phone number:\n")
     161 + with open(login, "w+", encoding="utf-8") as f:
     162 + f.write(api_id + "," + api_hash + "," + phone_number)
     163 + else:
     164 + with open(login, encoding="utf-8") as f:
     165 + details = f.read()
     166 + api_id, api_hash, phone_number = details.split(sep=",")
     167 + else:
     168 + login = telepathy_file + "login.txt"
     169 + 
     170 + if os.path.isfile(login) == False:
     171 + api_id = input(" Please enter your API ID:\n")
     172 + api_hash = input(" Please enter your API Hash:\n")
     173 + phone_number = input(" Please enter your phone number:\n")
     174 + with open(login, "w+", encoding="utf-8") as f:
     175 + f.write(api_id + "," + api_hash + "," + phone_number)
     176 + else:
     177 + with open(login, encoding="utf-8") as f:
     178 + details = f.read()
     179 + api_id, api_hash, phone_number = details.split(sep=",")
     180 + 
     181 + client = TelegramClient(phone_number, api_id, api_hash)
     182 + 
     183 + async def main():
     184 + 
     185 + await client.connect()
     186 + if not await client.is_user_authorized():
     187 + await client.send_code_request(phone_number)
     188 + await client.sign_in(phone_number)
     189 + try:
     190 + await client.sign_in(code=input(" Enter code: "))
     191 + except SessionPasswordNeededError:
     192 + await client.sign_in(
     193 + password=getpass.getpass(prompt="Password: ", stream=None)
     194 + )
     195 + result = client(
     196 + GetDialogsRequest(
     197 + offset_date=last_date,
     198 + offset_id=0,
     199 + offset_peer=InputPeerEmpty(),
     200 + limit=chunk_size,
     201 + hash=0,
     202 + )
     203 + )
     204 + else:
     205 + 
     206 + if export == True:
     207 + export_file = telepathy_file + "export.csv"
     208 + exports = []
     209 + 
     210 + print("Exporting...")
     211 + 
     212 + # progress bar
     213 + 
     214 + for Dialog in await client.get_dialogs():
     215 + try:
     216 + if Dialog.entity.username:
     217 + group_url = "http://t.me/" + Dialog.entity.username
     218 + group_username = Dialog.entity.username
     219 + 
     220 + web_req = parse_html_page(group_url)
     221 + group_description = web_req["group_description"]
     222 + total_participants = web_req["total_participants"]
     223 + 
     224 + _desc = process_description(
     225 + group_description, user_language
     226 + )
     227 + description_text = _desc["group_description"]
     228 + original_language = _mess[
     229 + "original_language"
     230 + ]
     231 + translated_description = _desc["translated_text"]
     232 + 
     233 + if Dialog.entity.broadcast is True:
     234 + chat_type = "Channel"
     235 + elif Dialog.entity.megagroup is True:
     236 + chat_type = "Megagroup"
     237 + elif Dialog.entity.gigagroup is True:
     238 + chat_type = "Gigagroup"
     239 + else:
     240 + chat_type = "Chat"
     241 + 
     242 + if Dialog.entity.restriction_reason is not None:
     243 + ios_restriction = Dialog.entity.restriction_reason[
     244 + 0
     245 + ]
     246 + if 1 in Dialog.entity.restriction_reason:
     247 + android_restriction = (
     248 + Dialog.entity.restriction_reason[1]
     249 + )
     250 + group_status = (
     251 + str(ios_restriction)
     252 + + ", "
     253 + + str(android_restriction)
     254 + )
     255 + else:
     256 + group_status = str(ios_restriction)
     257 + else:
     258 + group_status = "None"
     259 + 
     260 + exports.append(
     261 + [
     262 + filetime,
     263 + Dialog.entity.title,
     264 + group_description,
     265 + translated_description,
     266 + total_participants,
     267 + group_username,
     268 + group_url,
     269 + chat_type,
     270 + Dialog.entity.id,
     271 + Dialog.entity.access_hash,
     272 + group_status,
     273 + ]
     274 + )
     275 + 
     276 + export_df = pd.DataFrame(
     277 + exports,
     278 + columns=[
     279 + "Access Date",
     280 + "Title",
     281 + "Description",
     282 + "Translated description",
     283 + "Total participants",
     284 + "Username",
     285 + "URL",
     286 + "Chat type",
     287 + "Chat ID",
     288 + "Access hash",
     289 + "Restrictions",
     290 + ],
     291 + )
     292 + 
     293 + if not os.path.isfile(export_file):
     294 + export_df.to_csv(export_file, sep=";", index=False)
     295 + else:
     296 + export_df.to_csv(
     297 + export_file, sep=";", mode="w", index=False
     298 + )
     299 + 
     300 + except AttributeError:
     301 + pass
     302 + 
     303 + else:
     304 + 
     305 + for t in target:
     306 + target_clean = t
     307 + alphanumeric = ""
     308 +
     309 + 
     310 + for character in target_clean:
     311 + if character.isalnum():
     312 + alphanumeric += character
     313 + 
     314 + if "https://t.me/+" in t:
     315 + t = t.replace('https://t.me/+', 'https://t.me/joinchat/')
     316 + 
     317 + if basic is True or comp_check is True:
     318 + save_directory = telepathy_file + alphanumeric
     319 + try:
     320 + os.makedirs(save_directory)
     321 + except FileExistsError:
     322 + pass
     323 + 
     324 + # Creating logfile
     325 + log_file = telepathy_file + "log.csv"
     326 + 
     327 + if media_archive:
     328 + media_directory = save_directory + "/media"
     329 + try:
     330 + os.makedirs(media_directory)
     331 + except FileExistsError:
     332 + pass
     333 + 
     334 + if basic == True and comp_check == False:
     335 + color_print_green(" [!] ", "Performing basic scan")
     336 + elif comp_check == True:
     337 + color_print_green(" [!] ", "Performing comprehensive scan")
     338 + file_archive = (
     339 + save_directory
     340 + + "/"
     341 + + alphanumeric
     342 + + "_"
     343 + + filetime_clean
     344 + + "_archive.csv"
     345 + )
     346 + reply_file_archive = (
     347 + save_directory
     348 + + "/"
     349 + + alphanumeric
     350 + + "_"
     351 + + filetime_clean
     352 + + "_reply_archive.csv"
     353 + )
     354 + 
     355 + if forwards_check == True:
     356 + color_print_green(" [!] ", "Forwards will be fetched")
     357 + file_forwards = (
     358 + save_directory
     359 + + "/edgelists/"
     360 + + alphanumeric
     361 + + "_"
     362 + + filetime_clean
     363 + + "_edgelist.csv"
     364 + )
     365 + forward_directory = save_directory + "/edgelists/"
     366 + 
     367 + try:
     368 + os.makedirs(forward_directory)
     369 + except FileExistsError:
     370 + pass
     371 + 
     372 + edgelist_file = (
     373 + forward_directory + "/" + alphanumeric + "_edgelist.csv"
     374 + )
     375 + 
     376 + if basic is True or comp_check is True:
     377 + 
     378 + color_print_green(" [-] ", "Fetching details for " + t + "...")
     379 + memberlist_directory = save_directory + "/memberlists"
     380 + 
     381 + try:
     382 + os.makedirs(memberlist_directory)
     383 + except FileExistsError:
     384 + pass
     385 + 
     386 + memberlist_filename = (
     387 + memberlist_directory + "/" + alphanumeric + "_members.csv"
     388 + )
     389 + reply_memberlist_filename = (
     390 + memberlist_directory
     391 + + "/"
     392 + + alphanumeric
     393 + + "_active_members.csv"
     394 + )
     395 + 
     396 + entity = await client.get_entity(t)
     397 + first_post = "Not found"
     398 + 
     399 + async for message in client.iter_messages(t, reverse=True):
     400 + datepost = parse_tg_date(message.date)
     401 + date = datepost["date"]
     402 + mtime = datepost["mtime"]
     403 + first_post = datepost["timestamp"]
     404 + break
     405 + 
     406 + if entity.username:
     407 + name = entity.title
     408 + group_url = "http://t.me/" + entity.username
     409 + group_username = entity.username
     410 + web_req = parse_html_page(group_url)
     411 + elif "https://t.me/" in t:
     412 + group_url = t
     413 + web_req = parse_html_page(group_url)
     414 + group_username = "Private group"
     415 + else:
     416 + group_url = "Private group"
     417 + group_username = "Private group"
     418 + 
     419 + 
     420 + group_description = web_req["group_description"]
     421 + total_participants = web_req["total_participants"]
     422 + 
     423 + _desc = process_description(
     424 + group_description, user_language
     425 + )
     426 + description_text = _desc["description_text"]
     427 + original_language = _desc[
     428 + "original_language"
     429 + ]
     430 + 
     431 + translated_description = _desc["translated_text"]
     432 + 
     433 + preferredWidth = 70
     434 + descript = Fore.GREEN + "Description: " + Style.RESET_ALL
     435 + prefix = descript
     436 + wrapper_d = textwrap.TextWrapper(
     437 + initial_indent=prefix,
     438 + width=preferredWidth,
     439 + subsequent_indent=" ",
     440 + )
     441 + 
     442 + trans_descript = Fore.GREEN + "Translated: " + Style.RESET_ALL
     443 + prefix = trans_descript
     444 + wrapper_td = textwrap.TextWrapper(
     445 + initial_indent=prefix,
     446 + width=preferredWidth,
     447 + subsequent_indent=" ",
     448 + )
     449 + 
     450 + group_description = ('"' + group_description + '"')
     451 + 
     452 + if entity.broadcast is True:
     453 + chat_type = "Channel"
     454 + elif entity.megagroup is True:
     455 + chat_type = "Megagroup"
     456 + elif entity.gigagroup is True:
     457 + chat_type = "Gigagroup"
     458 + else:
     459 + chat_type = "Chat"
     460 + 
     461 + if entity.restriction_reason is not None:
     462 + ios_restriction = entity.restriction_reason[0]
     463 + if 1 in entity.restriction_reason:
     464 + android_restriction = entity.restriction_reason[1]
     465 + group_status = (
     466 + str(ios_restriction) + ", " + str(android_restriction)
     467 + )
     468 + else:
     469 + group_status = str(ios_restriction)
     470 + else:
     471 + group_status = "None"
     472 + 
     473 + restrict = Fore.GREEN + "Restrictions:" + Style.RESET_ALL
     474 + prefix = restrict + " "
     475 + preferredWidth = 70
     476 + wrapper_r = textwrap.TextWrapper(
     477 + initial_indent=prefix,
     478 + width=preferredWidth,
     479 + subsequent_indent=" ",
     480 + )
     481 + 
     482 + if chat_type != "Channel":
     483 + members = []
     484 + all_participants = []
     485 + all_participants = await client.get_participants(t, limit=5000)
     486 + 
     487 + members_df = None
     488 + for user in all_participants:
     489 + members_df = pd.DataFrame(
     490 + members,
     491 + columns=[
     492 + "Username",
     493 + "Full name",
     494 + "User ID",
     495 + "Phone number",
     496 + "Group name",
     497 + ],
     498 + )
     499 + members.append(populate_user(user, t))
     500 + 
     501 + if members_df is not None:
     502 + with open(
     503 + memberlist_filename, "w+", encoding="utf-8"
     504 + ) as save_members:
     505 + members_df.to_csv(save_members, sep=";")
     506 + 
     507 + if json_check == True:
     508 + members_df.to_json(
     509 + json_file + alphanumeric + "_memberlist.json",
     510 + orient="records",
     511 + compression="infer",
     512 + lines=True,
     513 + index=True,
     514 + )
     515 + else:
     516 + pass
     517 + 
     518 + found_participants = len(all_participants)
     519 + found_participants = int(found_participants)
     520 + found_percentage = (
     521 + int(found_participants) / int(total_participants) * 100
     522 + )
     523 + 
     524 + log = []
     525 + 
     526 + if chat_type != "Channel":
     527 + print("\n")
     528 + color_print_green(" [+] Memberlist fetched", "")
     529 + else:
     530 + pass
     531 +
     532 + color_print_green(" ┬ Chat details", "")
     533 + color_print_green(" ├ Title: ", str(entity.title))
     534 + color_print_green(" ├ ", wrapper_d.fill(group_description))
     535 + if translated_description != group_description:
     536 + color_print_green(" ├ ", wrapper_td.fill(translated_description))
     537 + color_print_green(
     538 + " ├ Total participants: ", str(total_participants)
     539 + )
     540 + 
     541 + if chat_type != "Channel":
     542 + color_print_green(
     543 + " ├ Participants found: ",
     544 + str(found_participants)
     545 + + " ("
     546 + + str(format(found_percentage, ".2f"))
     547 + + "%)",
     548 + )
     549 + else:
     550 + found_participants = "N/A"
     551 + 
     552 + color_print_green(" ├ Username: ", str(group_username))
     553 + color_print_green(" ├ URL: ", str(group_url))
     554 + color_print_green(" ├ Chat type: ", str(chat_type))
     555 + color_print_green(" ├ Chat id: ", str(entity.id))
     556 + color_print_green(" ├ Access hash: ", str(entity.access_hash))
     557 + 
     558 + if chat_type == "Channel":
     559 + scam_status = str(entity.scam)
     560 + color_print_green(" ├ Scam: ", str(scam_status))
     561 + else:
     562 + scam_status = "N/A"
     563 + 
     564 + color_print_green(" ├ First post date: ", str(first_post))
     565 + 
     566 + if chat_type != "Channel":
     567 + color_print_green(
     568 + " ├ Memberlist saved to: ", memberlist_filename
     569 + )
     570 + 
     571 + color_print_green(
     572 + " â”” ", wrapper_r.fill(group_status)
     573 + )
     574 + #print("\n")
     575 + 
     576 + log.append(
     577 + [
     578 + filetime,
     579 + entity.title,
     580 + group_description,
     581 + translated_description,
     582 + total_participants,
     583 + found_participants,
     584 + group_username,
     585 + group_url,
     586 + chat_type,
     587 + entity.id,
     588 + entity.access_hash,
     589 + scam_status,
     590 + date,
     591 + mtime,
     592 + group_status,
     593 + ]
     594 + )
     595 + 
     596 + log_df = pd.DataFrame(
     597 + log,
     598 + columns=[
     599 + "Access Date",
     600 + "Title",
     601 + "Description",
     602 + "Translated description",
     603 + "Total participants",
     604 + "Participants found",
     605 + "Username",
     606 + "URL",
     607 + "Chat type",
     608 + "Chat ID",
     609 + "Access hash",
     610 + "Scam",
     611 + "First post date",
     612 + "First post time (UTC)",
     613 + "Restrictions",
     614 + ],
     615 + )
     616 + 
     617 + if not os.path.isfile(log_file):
     618 + log_df.to_csv(log_file, sep=";", index=False)
     619 + else:
     620 + log_df.to_csv(
     621 + log_file, sep=";", mode="a", index=False, header=False
     622 + )
     623 + 
     624 + if forwards_check is True and comp_check is False:
     625 + color_print_green(
     626 + " [-] ", "Calculating number of forwarded messages..."
     627 + )
     628 + forwards_list = []
     629 + forward_count = 0
     630 + private_count = 0
     631 + to_ent = await client.get_entity(t)
     632 + to_title = to_ent.title
     633 + 
     634 + forwards_df = pd.DataFrame(
     635 + forwards_list,
     636 + columns=[
     637 + "To",
     638 + "To_title",
     639 + "From",
     640 + "From_ID",
     641 + "Username",
     642 + "Timestamp",
     643 + ],
     644 + )
     645 + 
     646 + async for message in client.iter_messages(t):
     647 + if message.forward is not None:
     648 + forward_count += 1
     649 + 
     650 + #print("\n")
     651 + color_print_green(" [-] ", "Fetching forwarded messages...")
     652 + 
     653 + progress_bar = (
     654 + Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
     655 + )
     656 + 
     657 + with alive_bar(
     658 + forward_count, dual_line=True, title=progress_bar, length=20
     659 + ) as bar:
     660 + 
     661 + async for message in client.iter_messages(t):
     662 + if message.forward is not None:
     663 + try:
     664 + f_from_id = message.forward.original_fwd.from_id
     665 + if f_from_id is not None:
     666 + ent = await client.get_entity(f_from_id)
     667 + username = ent.username
     668 + timestamp = parse_tg_date(message.date)[
     669 + "timestamp"
     670 + ]
     671 + 
     672 + substring = "PeerUser"
     673 + string = str(f_from_id)
     674 + if substring in string:
     675 + user_id = re.sub("[^0-9]", "", string)
     676 + user_id = await client.get_entity(
     677 + PeerUser(int(user_id))
     678 + )
     679 + user_id = str(user_id)
     680 + result = (
     681 + "User: "
     682 + + str(ent.first_name)
     683 + + " / ID: "
     684 + + str(user_id.id)
     685 + )
     686 + else:
     687 + result = str(ent.title)
     688 + 
     689 + forwards_df = pd.DataFrame(
     690 + forwards_list,
     691 + columns=[
     692 + "To username",
     693 + "To name",
     694 + "From",
     695 + "From ID",
     696 + "From_username",
     697 + "Timestamp",
     698 + ],
     699 + )
     700 + 
     701 + forwards_list.append(
     702 + [
     703 + t,
     704 + to_title,
     705 + result,
     706 + f_from_id,
     707 + username,
     708 + timestamp,
     709 + ]
     710 + )
     711 + 
     712 + except Exception as e:
     713 + if e is ChannelPrivateError:
     714 + print("Private channel")
     715 + continue
     716 + 
     717 + time.sleep(0.5)
     718 + bar()
     719 + 
     720 + with open(
     721 + edgelist_file, "w+", encoding="utf-8"
     722 + ) as save_forwards:
     723 + forwards_df.to_csv(save_forwards, sep=";")
     724 + 
     725 + if json_check == True:
     726 + forwards_df.to_json(
     727 + json_file + alphanumeric + "_edgelist.json",
     728 + orient="records",
     729 + compression="infer",
     730 + lines=True,
     731 + index=True,
     732 + )
     733 + else:
     734 + pass
     735 + 
     736 + if forward_count >= 15:
     737 + forwards_found = forwards_df.From.count()
     738 + value_count = forwards_df["From"].value_counts()
     739 + df01 = value_count.rename_axis("unique_values").reset_index(
     740 + name="counts"
     741 + )
     742 + 
     743 + top_forward_one = df01.iloc[0]["unique_values"]
     744 + top_value_one = df01.iloc[0]["counts"]
     745 + top_forward_two = df01.iloc[1]["unique_values"]
     746 + top_value_two = df01.iloc[1]["counts"]
     747 + top_forward_three = df01.iloc[2]["unique_values"]
     748 + top_value_three = df01.iloc[2]["counts"]
     749 + top_forward_four = df01.iloc[3]["unique_values"]
     750 + top_value_four = df01.iloc[3]["counts"]
     751 + top_forward_five = df01.iloc[4]["unique_values"]
     752 + top_value_five = df01.iloc[4]["counts"]
     753 + 
     754 + forward_one = (
     755 + str(top_forward_one)
     756 + + ", "
     757 + + str(top_value_one)
     758 + + " forwarded messages"
     759 + )
     760 + forward_two = (
     761 + str(top_forward_two)
     762 + + ", "
     763 + + str(top_value_two)
     764 + + " forwarded messages"
     765 + )
     766 + forward_three = (
     767 + str(top_forward_three)
     768 + + ", "
     769 + + str(top_value_three)
     770 + + " forwarded messages"
     771 + )
     772 + forward_four = (
     773 + str(top_forward_four)
     774 + + ", "
     775 + + str(top_value_four)
     776 + + " forwarded messages"
     777 + )
     778 + forward_five = (
     779 + str(top_forward_five)
     780 + + ", "
     781 + + str(top_value_five)
     782 + + " forwarded messages"
     783 + )
     784 + 
     785 + df02 = forwards_df.From.unique()
     786 + unique_forwards = len(df02)
     787 + 
     788 + #print("\n")
     789 + color_print_green(" [+] Forward scrape complete", "")
     790 + color_print_green(" ┬ Statistics", "")
     791 + color_print_green(
     792 + " ├ Forwarded messages found: ", str(forward_count)
     793 + )
     794 + color_print_green(
     795 + " ├ Forwards from active public chats: ",
     796 + str(forwards_found),
     797 + )
     798 + color_print_green(
     799 + " ├ Unique forward sources: ", str(unique_forwards)
     800 + )
     801 + color_print_green(
     802 + " ├ Top forward source 1: ", str(forward_one)
     803 + )
     804 + color_print_green(
     805 + " ├ Top forward source 2: ", str(forward_two)
     806 + )
     807 + color_print_green(
     808 + " ├ Top forward source 3: ", str(forward_three)
     809 + )
     810 + color_print_green(
     811 + " ├ Top forward source 4: ", str(forward_four)
     812 + )
     813 + color_print_green(
     814 + " ├ Top forward source 5: ", str(forward_five)
     815 + )
     816 + color_print_green(" â”” Edgelist saved to: ", edgelist_file)
     817 + #print("\n")
     818 + 
     819 + else:
     820 + print(
     821 + "\n"
     822 + + Fore.GREEN
     823 + + " [!] Insufficient forwarded messages found"
     824 + + Style.RESET_ALL
     825 + )
     826 + 
     827 + else:
     828 + 
     829 + if comp_check is True:
     830 + 
     831 + messages = client.iter_messages(t)
     832 + 
     833 + message_list = []
     834 + forwards_list = []
     835 + 
     836 + user_reaction_list = []
     837 + 
     838 + replies_list = []
     839 + user_replier_list = []
     840 + 
     841 + timecount = []
     842 + 
     843 + forward_count = 0
     844 + private_count = 0
     845 + 
     846 + if media_archive is True:
     847 + files = []
     848 + print("\n")
     849 + color_print_green(
     850 + " [!] ", "Media content will be archived"
     851 + )
     852 + 
     853 + color_print_green(
     854 + " [!] ", "Calculating number of messages..."
     855 + )
     856 + 
     857 + message_count = 0
     858 + 
     859 + async for message in messages:
     860 + if message is not None:
     861 + message_count += 1
     862 + 
     863 + print("\n")
     864 + color_print_green(" [-] ", "Fetching message archive...")
     865 + progress_bar = (
     866 + Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
     867 + )
     868 + 
     869 + with alive_bar(
     870 + message_count,
     871 + dual_line=True,
     872 + title=progress_bar,
     873 + length=20,
     874 + ) as bar:
     875 + 
     876 + to_ent = await client.get_entity(t)
     877 + 
     878 + async for message in client.iter_messages(
     879 + t, limit=None
     880 + ):
     881 + if message is not None:
     882 + 
     883 + try:
     884 + 
     885 + c_archive = pd.DataFrame(
     886 + message_list,
     887 + columns=[
     888 + "To",
     889 + "Message ID",
     890 + "Display_name",
     891 + "ID",
     892 + "Message_text",
     893 + "Original_language",
     894 + "Translated_text",
     895 + "Translation_confidence",
     896 + "Timestamp",
     897 + "Reply",
     898 + "Views",
     899 + ],
     900 + )
     901 + 
     902 + c_forwards = pd.DataFrame(
     903 + forwards_list,
     904 + columns=[
     905 + "To",
     906 + "To_title",
     907 + "From",
     908 + "From_ID",
     909 + "Username",
     910 + "Timestamp",
     911 + ],
     912 + )
     913 + 
     914 + #if message.reactions:
     915 + # if message.reactions.can_see_list:
     916 + # c_reactioneer = pd.DataFrame(
     917 + # user_reaction_list,
     918 + # columns=[
     919 + # "Username",
     920 + # "Full name",
     921 + # "User ID",
     922 + # "Phone number",
     923 + # "Group name",
     924 + # ],
     925 + # )
     926 + 
     927 + if (
     928 + message.replies
     929 + and reply_analysis
     930 + and chat_type == "Channel"
     931 + ):
     932 + if message.replies.replies > 0:
     933 + c_repliers = pd.DataFrame(
     934 + user_replier_list,
     935 + columns=[
     936 + "Username",
     937 + "Full name",
     938 + "User ID",
     939 + "Phone number",
     940 + "Group name",
     941 + ],
     942 + )
     943 + c_replies = pd.DataFrame(
     944 + replies_list,
     945 + columns=[
     946 + "To",
     947 + "Message ID",
     948 + "Reply ID",
     949 + "Display_name",
     950 + "ID",
     951 + "Message_text",
     952 + "Original_language",
     953 + "Translated_text",
     954 + "Translation_confidence",
     955 + "Timestamp",
     956 + ],
     957 + )
     958 + 
     959 + if message.replies:
     960 + if message.replies.replies > 0:
     961 + async for repl in client.iter_messages(
     962 + message.chat_id,
     963 + reply_to=message.id,
     964 + ):
     965 + user = await client.get_entity(
     966 + repl.from_id.user_id
     967 + )
     968 + userdet = populate_user(user, t)
     969 + user_replier_list.append(
     970 + userdet
     971 + )
     972 + mss_txt = process_message(
     973 + repl.text, user_language
     974 + )
     975 + replies_list.append(
     976 + [
     977 + t,
     978 + message.id,
     979 + repl.id,
     980 + userdet[1],
     981 + userdet[2],
     982 + mss_txt["message_text"],
     983 + mss_txt[
     984 + "original_language"
     985 + ],
     986 + mss_txt[
     987 + "translated_text"
     988 + ],
     989 + mss_txt[
     990 + "translation_confidence"
     991 + ],
     992 + parse_tg_date(
     993 + repl.date
     994 + )["timestamp"],
     995 + ]
     996 + )
     997 + 
     998 + display_name = get_display_name(
     999 + message.sender
     1000 + )
     1001 + if chat_type != "Channel":
     1002 + substring = "PeerUser"
     1003 + string = str(message.from_id)
     1004 + if substring in string:
     1005 + user_id = re.sub(
     1006 + "[^0-9]", "", string
     1007 + )
     1008 + nameID = str(user_id)
     1009 + else:
     1010 + nameID = str(message.from_id)
     1011 + else:
     1012 + nameID = to_ent.id
     1013 + 
     1014 + timestamp = parse_tg_date(message.date)[
     1015 + "timestamp"
     1016 + ]
     1017 + reply = message.reply_to_msg_id
     1018 + 
     1019 + _mess = process_message(
     1020 + message.text, user_language
     1021 + )
     1022 + message_text = _mess["message_text"]
     1023 + original_language = _mess[
     1024 + "original_language"
     1025 + ]
     1026 + translated_text = _mess["translated_text"]
     1027 + translation_confidence = _mess[
     1028 + "translation_confidence"
     1029 + ]
     1030 + 
     1031 + if message.forwards is not None:
     1032 + forwards = int(message.forwards)
     1033 + else:
     1034 + forwards = "None"
     1035 + 
     1036 + if message.views is not None:
     1037 + views = int(message.views)
     1038 + else:
     1039 + views = "Not found"
     1040 + 
     1041 + #if message.reactions:
     1042 + #if message.reactions.can_see_list:
     1043 + #print(dir(message.reactions.results))
     1044 + #print("#### TODO: REACTIONS")
     1045 + 
     1046 + if media_archive == True:
     1047 + #add a progress bar for each file download
     1048 + if message.media:
     1049 + path = await message.download_media(
     1050 + file=media_directory
     1051 + )
     1052 + files.append(path)
     1053 + else:
     1054 + pass
     1055 + 
     1056 +
     1057 + 
     1058 + message_list.append(
     1059 + [
     1060 + t,
     1061 + message.id,
     1062 + display_name,
     1063 + nameID,
     1064 + message_text,
     1065 + original_language,
     1066 + translated_text,
     1067 + translation_confidence,
     1068 + timestamp,
     1069 + reply,
     1070 + views,
     1071 + ]
     1072 + )
     1073 + 
     1074 + if message.forward is not None:
     1075 + forward_verify = True
     1076 + try:
     1077 + forward_count += 1
     1078 + to_title = to_ent.title
     1079 + f_from_id = (
     1080 + message.forward.original_fwd.from_id
     1081 + )
     1082 + 
     1083 + if f_from_id is not None:
     1084 + ent = await client.get_entity(
     1085 + f_from_id
     1086 + )
     1087 + user_string = "user_id"
     1088 + channel_string = "broadcast"
     1089 + 
     1090 + if user_string in str(ent):
     1091 + ent_type = "User"
     1092 + else:
     1093 + if channel_string in str(
     1094 + ent
     1095 + ):
     1096 + if (
     1097 + ent.broadcast
     1098 + is True
     1099 + ):
     1100 + ent_type = "Channel"
     1101 + elif (
     1102 + ent.megagroup
     1103 + is True
     1104 + ):
     1105 + ent_type = (
     1106 + "Megagroup"
     1107 + )
     1108 + elif (
     1109 + ent.gigagroup
     1110 + is True
     1111 + ):
     1112 + ent_type = (
     1113 + "Gigagroup"
     1114 + )
     1115 + else:
     1116 + ent_type = "Chat"
     1117 + else:
     1118 + continue
     1119 + 
     1120 + if ent.username is not None:
     1121 + username = ent.username
     1122 + else:
     1123 + username = "none"
     1124 + 
     1125 + if ent_type != "Chat":
     1126 + result = str(ent.title)
     1127 + else:
     1128 + result = "none"
     1129 + 
     1130 + if ent_type == "User":
     1131 + substring_1 = "PeerUser"
     1132 + string_1 = str(ent.user_id)
     1133 + if substring_1 in string_1:
     1134 + user_id = re.sub(
     1135 + "[^0-9]",
     1136 + "",
     1137 + string_1,
     1138 + )
     1139 + user_id = await client.get_entity(
     1140 + PeerUser(
     1141 + int(user_id)
     1142 + )
     1143 + )
     1144 + user_id = str(user_id)
     1145 + result = (
     1146 + "User: "
     1147 + + str(
     1148 + ent.first_name
     1149 + )
     1150 + + " / ID: "
     1151 + + str(user_id)
     1152 + )
     1153 + else:
     1154 + result = str(ent.title)
     1155 + else:
     1156 + result = str(ent.title)
     1157 + 
     1158 + forwards_list.append(
     1159 + [
     1160 + t,
     1161 + to_title,
     1162 + result,
     1163 + f_from_id,
     1164 + username,
     1165 + timestamp,
     1166 + ]
     1167 + )
     1168 + 
     1169 +
     1170 + 
     1171 + except ChannelPrivateError:
     1172 + private_count += 1
     1173 + continue
     1174 + 
     1175 + except Exception as e:
     1176 + print("An exception occurred.", e)
     1177 + continue
     1178 + 
     1179 + except Exception as e:
     1180 + print("An exception occurred.", e)
     1181 + 
     1182 + else:
     1183 + message_list.append(
     1184 + [
     1185 + "None",
     1186 + "None",
     1187 + "None",
     1188 + "None",
     1189 + "None",
     1190 + "None",
     1191 + "None",
     1192 + "None",
     1193 + ]
     1194 + )
     1195 + pass
     1196 + 
     1197 + time.sleep(0.5)
     1198 + bar()
     1199 + 
     1200 + if reply_analysis is True:
     1201 + if len(replies_list) > 0:
     1202 + with open(
     1203 + reply_file_archive, "w+", encoding="utf-8"
     1204 + ) as rep_file:
     1205 + c_replies.to_csv(rep_file, sep=";")
     1206 + 
     1207 + if len(user_replier_list) > 0:
     1208 + with open(
     1209 + reply_memberlist_filename, "w+", encoding="utf-8"
     1210 + ) as repliers_file:
     1211 + c_repliers.to_csv(repliers_file, sep=";")
     1212 + 
     1213 + with open(
     1214 + file_archive, "w+", encoding="utf-8"
     1215 + ) as archive_file:
     1216 + c_archive.to_csv(archive_file, sep=";")
     1217 + 
     1218 + if json_check == True:
     1219 + c_archive.to_json(
     1220 + json_file + alphanumeric + "_archive.json",
     1221 + orient="records",
     1222 + compression="infer",
     1223 + lines=True,
     1224 + index=True,
     1225 + )
     1226 + else:
     1227 + pass
     1228 + 
     1229 + if forwards_check is True:
     1230 + with open(
     1231 + file_forwards, "w+", encoding="utf-8"
     1232 + ) as forwards_file:
     1233 + c_forwards.to_csv(forwards_file, sep=";")
     1234 + 
     1235 + if json_check == True:
     1236 + c_forwards.to_json(
     1237 + json_file + alphanumeric + "_edgelist.json",
     1238 + orient="records",
     1239 + compression="infer",
     1240 + lines=True,
     1241 + index=True,
     1242 + )
     1243 + else:
     1244 + pass
     1245 + else:
     1246 + pass
     1247 + 
     1248 + messages_found = int(c_archive.To.count()) - 1
     1249 + message_frequency_count = {}
     1250 + message_text = {}
     1251 + word_count = {}
     1252 + most_used_words = {}
     1253 + most_used_words_filtered = {}
     1254 + # message stats, top words
     1255 + 
     1256 + if chat_type != "Channel":
     1257 + pcount = c_archive.Display_name.count()
     1258 + pvalue_count = c_archive["Display_name"].value_counts()
     1259 + df03 = pvalue_count.rename_axis(
     1260 + "unique_values"
     1261 + ).reset_index(name="counts")
     1262 + 
     1263 + top_poster_one = str(df03.iloc[0]["unique_values"])
     1264 + top_pvalue_one = df03.iloc[0]["counts"]
     1265 + top_poster_two = str(df03.iloc[1]["unique_values"])
     1266 + top_pvalue_two = df03.iloc[1]["counts"]
     1267 + top_poster_three = str(df03.iloc[2]["unique_values"])
     1268 + top_pvalue_three = df03.iloc[2]["counts"]
     1269 + top_poster_four = str(df03.iloc[3]["unique_values"])
     1270 + top_pvalue_four = df03.iloc[3]["counts"]
     1271 + top_poster_five = str(df03.iloc[4]["unique_values"])
     1272 + top_pvalue_five = df03.iloc[4]["counts"]
     1273 + 
     1274 + poster_one = (
     1275 + str(top_poster_one)
     1276 + + ", "
     1277 + + str(top_pvalue_one)
     1278 + + " messages"
     1279 + )
     1280 + poster_two = (
     1281 + str(top_poster_two)
     1282 + + ", "
     1283 + + str(top_pvalue_two)
     1284 + + " messages"
     1285 + )
     1286 + poster_three = (
     1287 + str(top_poster_three)
     1288 + + ", "
     1289 + + str(top_pvalue_three)
     1290 + + " messages"
     1291 + )
     1292 + poster_four = (
     1293 + str(top_poster_four)
     1294 + + ", "
     1295 + + str(top_pvalue_four)
     1296 + + " messages"
     1297 + )
     1298 + poster_five = (
     1299 + str(top_poster_five)
     1300 + + ", "
     1301 + + str(top_pvalue_five)
     1302 + + " messages"
     1303 + )
     1304 + 
     1305 + df04 = c_archive.Display_name.unique()
     1306 + plength = len(df03)
     1307 + unique_active = len(df04)
     1308 + # one day this'll work out sleeping times
     1309 + # print(c_t_stats)
     1310 + 
     1311 + elif reply_analysis is True:
     1312 + if len(replies_list) > 0:
     1313 + replier_count = c_repliers["User id"].count()
     1314 + replier_value_count = c_repliers["User id"].value_counts()
     1315 + replier_df = replier_value_count.rename_axis(
     1316 + "unique_values"
     1317 + ).reset_index(name="counts")
     1318 + 
     1319 + top_replier_one = str(replier_df.iloc[0]["unique_values"])
     1320 + top_replier_value_one = replier_df.iloc[0]["counts"]
     1321 + top_replier_two = str(replier_df.iloc[1]["unique_values"])
     1322 + top_replier_value_two = replier_df.iloc[1]["counts"]
     1323 + top_replier_three = str(replier_df.iloc[2]["unique_values"])
     1324 + top_replier_value_three = replier_df.iloc[2]["counts"]
     1325 + top_replier_four = str(replier_df.iloc[3]["unique_values"])
     1326 + top_replier_value_four = replier_df.iloc[3]["counts"]
     1327 + top_replier_five = str(replier_df.iloc[4]["unique_values"])
     1328 + top_replier_value_five = replier_df.iloc[4]["counts"]
     1329 + 
     1330 + replier_one = (
     1331 + str(top_replier_one)
     1332 + + ", "
     1333 + + str(top_replier_value_one)
     1334 + + " replies"
     1335 + )
     1336 + replier_two = (
     1337 + str(top_replier_two)
     1338 + + ", "
     1339 + + str(top_replier_value_two)
     1340 + + " replies"
     1341 + )
     1342 + replier_three = (
     1343 + str(top_replier_three)
     1344 + + ", "
     1345 + + str(top_replier_value_three)
     1346 + + " replies"
     1347 + )
     1348 + replier_four = (
     1349 + str(top_replier_four)
     1350 + + ", "
     1351 + + str(top_replier_value_four)
     1352 + + " replies"
     1353 + )
     1354 + replier_five = (
     1355 + str(top_replier_five)
     1356 + + ", "
     1357 + + str(top_replier_value_five)
     1358 + + " replies"
     1359 + )
     1360 + 
     1361 + replier_count_df = c_repliers["User id"].unique()
     1362 + replier_length = len(replier_df)
     1363 + replier_unique = len(replier_count_df)
     1364 + 
     1365 + else:
     1366 + pass
     1367 + 
     1368 + #print("\n")
     1369 + color_print_green(" [+] Chat archive saved", "")
     1370 + color_print_green(" ┬ Chat statistics", "")
     1371 + color_print_green(
     1372 + " ├ Number of messages found: ", str(messages_found)
     1373 + )
     1374 + 
     1375 + if chat_type != "Channel":
     1376 + color_print_green(
     1377 + " ├ Top poster 1: ", str(poster_one)
     1378 + )
     1379 + color_print_green(
     1380 + " ├ Top poster 2: ", str(poster_two)
     1381 + )
     1382 + color_print_green(
     1383 + " ├ Top poster 3: ", str(poster_three)
     1384 + )
     1385 + color_print_green(
     1386 + " ├ Top poster 4: ", str(poster_four)
     1387 + )
     1388 + color_print_green(
     1389 + " ├ Top poster 5: ", str(poster_five)
     1390 + )
     1391 + color_print_green(
     1392 + " ├ Total unique posters: ", str(unique_active)
     1393 + )
     1394 +
     1395 + else:
     1396 + pass
     1397 + # timestamp analysis
     1398 + # print(Fore.GREEN
     1399 + # + ' ├ Number of messages: '
     1400 + # + Style.RESET_ALL
     1401 + # + str(message_count))
     1402 + 
     1403 + color_print_green(
     1404 + " â”” Archive saved to: ", str(file_archive)
     1405 + )
     1406 + 
     1407 + if reply_analysis is True:
     1408 + if len(replies_list) > 0:
     1409 + middle_char = "├"
     1410 + if user_replier_list == 0:
     1411 + middle_char = "â””"
     1412 + 
     1413 + #print("\n")
     1414 + color_print_green(" [+] Replies analysis ", "")
     1415 + color_print_green(" ┬ Chat statistics", "")
     1416 + color_print_green(
     1417 + f" {middle_char} Archive of replies saved to: ",
     1418 + str(reply_file_archive),
     1419 + )
     1420 + if len(user_replier_list) > 0:
     1421 + color_print_green(
     1422 + " â”” Active members list who replied to messages, saved to: ",
     1423 + str(reply_memberlist_filename),
     1424 + )
     1425 + 
     1426 + color_print_green(
     1427 + " ├ Top replier 1: ", str(replier_one)
     1428 + )
     1429 + color_print_green(
     1430 + " ├ Top replier 2: ", str(replier_two)
     1431 + )
     1432 + color_print_green(
     1433 + " ├ Top replier 3: ", str(replier_three)
     1434 + )
     1435 + color_print_green(
     1436 + " ├ Top replier 4: ", str(replier_four)
     1437 + )
     1438 + color_print_green(
     1439 + " ├ Top replier 5: ", str(replier_five)
     1440 + )
     1441 + color_print_green(
     1442 + " ├ Total unique repliers: ", str(replier_unique)
     1443 + )
     1444 + # add a figure for unique current posters who are active
     1445 + 
     1446 + if forwards_check is True:
     1447 + if forward_count >= 15:
     1448 + forwards_found = c_forwards.From.count()
     1449 + value_count = c_forwards["From"].value_counts()
     1450 + c_f_stats = value_count.rename_axis(
     1451 + "unique_values"
     1452 + ).reset_index(name="counts")
     1453 + 
     1454 + top_forward_one = c_f_stats.iloc[0]["unique_values"]
     1455 + top_value_one = c_f_stats.iloc[0]["counts"]
     1456 + top_forward_two = c_f_stats.iloc[1]["unique_values"]
     1457 + top_value_two = c_f_stats.iloc[1]["counts"]
     1458 + top_forward_three = c_f_stats.iloc[2][
     1459 + "unique_values"
     1460 + ]
     1461 + top_value_three = c_f_stats.iloc[2]["counts"]
     1462 + top_forward_four = c_f_stats.iloc[3][
     1463 + "unique_values"
     1464 + ]
     1465 + top_value_four = c_f_stats.iloc[3]["counts"]
     1466 + top_forward_five = c_f_stats.iloc[4][
     1467 + "unique_values"
     1468 + ]
     1469 + top_value_five = c_f_stats.iloc[4]["counts"]
     1470 + 
     1471 + forward_one = (
     1472 + str(top_forward_one)
     1473 + + ", "
     1474 + + str(top_value_one)
     1475 + + " forwarded messages"
     1476 + )
     1477 + forward_two = (
     1478 + str(top_forward_two)
     1479 + + ", "
     1480 + + str(top_value_two)
     1481 + + " forwarded messages"
     1482 + )
     1483 + forward_three = (
     1484 + str(top_forward_three)
     1485 + + ", "
     1486 + + str(top_value_three)
     1487 + + " forwarded messages"
     1488 + )
     1489 + forward_four = (
     1490 + str(top_forward_four)
     1491 + + ", "
     1492 + + str(top_value_four)
     1493 + + " forwarded messages"
     1494 + )
     1495 + forward_five = (
     1496 + str(top_forward_five)
     1497 + + ", "
     1498 + + str(top_value_five)
     1499 + + " forwarded messages"
     1500 + )
     1501 + 
     1502 + c_f_unique = c_forwards.From.unique()
     1503 + unique_forwards = len(c_f_unique)
     1504 + 
     1505 + #print("\n")
     1506 + color_print_green(" [+] Edgelist saved", "")
     1507 + color_print_green(
     1508 + " ┬ Forwarded message statistics", ""
     1509 + )
     1510 + color_print_green(
     1511 + " ├ Forwarded messages found: ",
     1512 + str(forward_count),
     1513 + )
     1514 + color_print_green(
     1515 + " ├ Forwards from active public chats: ",
     1516 + str(forwards_found),
     1517 + )
     1518 + color_print_green(
     1519 + " ├ Forwards from private (or now private) chats: ",
     1520 + str(private_count),
     1521 + )
     1522 + color_print_green(
     1523 + " ├ Unique forward sources: ",
     1524 + str(unique_forwards),
     1525 + )
     1526 + color_print_green(
     1527 + " ├ Top forward source 1: ", str(forward_one)
     1528 + )
     1529 + color_print_green(
     1530 + " ├ Top forward source 2: ", str(forward_two)
     1531 + )
     1532 + color_print_green(
     1533 + " ├ Top forward source 3: ",
     1534 + str(forward_three),
     1535 + )
     1536 + color_print_green(
     1537 + " ├ Top forward source 4: ", str(forward_four)
     1538 + )
     1539 + color_print_green(
     1540 + " ├ Top forward source 5: ", str(forward_five)
     1541 + )
     1542 + color_print_green(
     1543 + " â”” Edgelist saved to: ", edgelist_file
     1544 + )
     1545 + #print("\n")
     1546 + 
     1547 + else:
     1548 + #print("\n")
     1549 + color_print_green(
     1550 + " [!] Insufficient forwarded messages found",
     1551 + edgelist_file,
     1552 + )
     1553 + else:
     1554 + pass
     1555 + 
     1556 + if user_check == True:
     1557 + my_user = None
     1558 + try:
     1559 + 
     1560 + user = int(t)
     1561 + my_user = await client.get_entity(PeerUser(int(user)))
     1562 + 
     1563 + user_first_name = my_user.first_name
     1564 + user_last_name = my_user.last_name
     1565 + if user_last_name is not None:
     1566 + user_full_name = (
     1567 + str(user_first_name) + " " + str(user_last_name)
     1568 + )
     1569 + else:
     1570 + user_full_name = str(user_first_name)
     1571 + 
     1572 + if my_user.photo is not None:
     1573 + user_photo = my_user.photo.photo_id
     1574 + else:
     1575 + user_photo = "None"
     1576 + 
     1577 + if my_user.restriction_reason is not None:
     1578 + ios_restriction = entity.restriction_reason[0]
     1579 + if 1 in entity.restriction_reason:
     1580 + android_restriction = entity.restriction_reason[1]
     1581 + user_restrictions = (
     1582 + str(ios_restriction)
     1583 + + ", "
     1584 + + str(android_restriction)
     1585 + )
     1586 + else:
     1587 + user_restrictions = str(ios_restriction)
     1588 + else:
     1589 + user_restrictions = "None"
     1590 + 
     1591 + color_print_green(" [+] ", "User details for " + t)
     1592 + color_print_green(" ├ Username: ", str(my_user.username))
     1593 + color_print_green(" ├ Name: ", str(user_full_name))
     1594 + color_print_green(" ├ Verification: ", str(my_user.verified))
     1595 + color_print_green(" ├ Photo ID: ", str(user_photo))
     1596 + color_print_green(" ├ Phone number: ", str(my_user.phone))
     1597 + color_print_green(
     1598 + " ├ Access hash: ", str(my_user.access_hash)
     1599 + )
     1600 + color_print_green(" ├ Language: ", str(my_user.lang_code))
     1601 + color_print_green(" ├ Bot: ", str(my_user.bot))
     1602 + color_print_green(" ├ Scam: ", str(my_user.scam))
     1603 + color_print_green(" â”” Restrictions: ", str(user_restrictions))
     1604 + 
     1605 + except ValueError:
     1606 + pass
     1607 + if my_user is None:
     1608 + print(
     1609 + Fore.GREEN
     1610 + + " [!] "
     1611 + + Style.RESET_ALL
     1612 + + "User not found, this is likely because Telepathy has not encountered them yet."
     1613 + )
     1614 + 
     1615 + if location_check == True:
     1616 + 
     1617 + location = t
     1618 + 
     1619 + print(
     1620 + Fore.GREEN
     1621 + + " [!] "
     1622 + + Style.RESET_ALL
     1623 + + "Searching for users near "
     1624 + + location
     1625 + + "\n"
     1626 + )
     1627 + latitude, longitude = location.split(sep=",")
     1628 + 
     1629 + locations_file = telepathy_file + "locations/"
     1630 + 
     1631 + try:
     1632 + os.makedirs(locations_file)
     1633 + except FileExistsError:
     1634 + pass
     1635 + 
     1636 + save_file = (
     1637 + locations_file
     1638 + + latitude
     1639 + + "_"
     1640 + + longitude
     1641 + + "_"
     1642 + + "locations_"
     1643 + + filetime_clean
     1644 + + ".csv"
     1645 + )
     1646 + 
     1647 + locations_list = []
     1648 + result = await client(
     1649 + functions.contacts.GetLocatedRequest(
     1650 + geo_point=types.InputGeoPoint(
     1651 + lat=float(latitude),
     1652 + long=float(longitude),
     1653 + accuracy_radius=42,
     1654 + ),
     1655 + self_expires=42,
     1656 + )
     1657 + )
     1658 + 
     1659 + # progress bar?
     1660 + 
     1661 + for user in result.updates[0].peers:
     1662 + try:
     1663 + user_df = pd.DataFrame(
     1664 + locations_list, columns=["User_ID", "Distance"]
     1665 + )
     1666 + if hasattr(user, "peer"):
     1667 + ID = user.peer.user_id
     1668 + else:
     1669 + pass
     1670 + if hasattr(user, "distance"):
     1671 + distance = user.distance
     1672 + else:
     1673 + pass
     1674 + 
     1675 + locations_list.append([ID, distance])
     1676 + 
     1677 + except:
     1678 + pass
     1679 + 
     1680 + d_500 = 0
     1681 + d_1000 = 0
     1682 + d_2000 = 0
     1683 + d_3000 = 0
     1684 + 
     1685 + for account, distance in user_df.itertuples(index=False):
     1686 + account = int(account)
     1687 + my_user = await client.get_entity(PeerUser(account))
     1688 + user_id = my_user.id
     1689 + name = my_user.first_name
     1690 + distance = int(distance)
     1691 + 
     1692 + if distance == 500:
     1693 + d_500 += 1
     1694 + elif distance == 1000:
     1695 + d_1000 += 1
     1696 + elif distance == 2000:
     1697 + d_2000 += 1
     1698 + elif distance == 3000:
     1699 + d_3000 += 1
     1700 + 
     1701 + with open(
     1702 + save_file, "w+", encoding="utf-8"
     1703 + ) as f: # could one day append, including access time to differentiate
     1704 + user_df.to_csv(f, sep=";", index=False)
     1705 + 
     1706 + total = len(locations_list)
     1707 + 
     1708 + color_print_green(" [+] Users located", "")
     1709 + color_print_green(" ├ Users within 500m: ", str(d_500))
     1710 + color_print_green(" ├ Users within 1000m: ", str(d_1000))
     1711 + color_print_green(" ├ Users within 2000m: ", str(d_2000))
     1712 + color_print_green(" ├ Users within 3000m: ", str(d_3000))
     1713 + color_print_green(" ├ Total users found: ", str(total))
     1714 + color_print_green(" â”” Location list saved to: ", save_file)
     1715 + 
     1716 + # can also do the same for channels with similar output file to users
     1717 + # may one day add trilateration to find users closest to exact point
     1718 +
     1719 + with client:
     1720 + client.loop.run_until_complete(main())
     1721 + 
     1722 +if __name__ == "__main__":
     1723 + cli()
     1724 + 
  • ■ ■ ■ ■ ■ ■
    src/telepathy/utils.py
     1 +from colorama import Fore, Back, Style
     2 +from googletrans import Translator, constants
     3 +from .const import __version__, user_agent
     4 +import requests
     5 +import textwrap
     6 +from bs4 import BeautifulSoup
     7 +import random
     8 + 
     9 +def print_banner():
     10 + print(
     11 + Fore.GREEN
     12 + + """
     13 + ______ __ __ __
     14 + /_ __/__ / /__ ____ ____ _/ /_/ /_ __ __
     15 + / / / _ \/ / _ \/ __ \/ __ `/ __/ __ \/ / / /
     16 + / / / __/ / __/ /_/ / /_/ / /_/ / / / /_/ /
     17 + /_/ \___/_/\___/ .___/\__,_/\__/_/ /_/\__, /
     18 + /_/ /____/
     19 + -- An OSINT toolkit for investigating Telegram chats.
     20 + -- Developed by @jordanwildon | Version """ + __version__ + """.
     21 + """ + Style.RESET_ALL
     22 + )
     23 + 
     24 +def parse_tg_date(dd):
     25 + year = str(format(dd.year, "02d"))
     26 + month = str(format(dd.month, "02d"))
     27 + day = str(format(dd.day, "02d"))
     28 + hour = str(format(dd.hour, "02d"))
     29 + minute = str(format(dd.minute, "02d"))
     30 + second = str(format(dd.second, "02d"))
     31 + date = year + "-" + month + "-" + day
     32 + mtime = hour + ":" + minute + ":" + second
     33 + timestamp = date + "T" + mtime + "+00:00"
     34 + return {"timestamp":timestamp, "date":date, "mtime":mtime}
     35 + 
     36 + 
     37 +def populate_user(user, group_or_chat):
     38 + if user.username:
     39 + username = user.username
     40 + else:
     41 + username = "n/a"
     42 + if user.first_name:
     43 + first_name = user.first_name
     44 + else:
     45 + first_name = ""
     46 + if user.last_name:
     47 + last_name = user.last_name
     48 + else:
     49 + last_name = ""
     50 + if user.phone:
     51 + phone = user.phone
     52 + else:
     53 + phone = "n/a"
     54 + full_name = (first_name + " " + last_name).strip()
     55 + return [username, full_name, user.id, phone, group_or_chat]
     56 + 
     57 + 
     58 +def process_message(mess, user_lang):
     59 + 
     60 + if mess is not None:
     61 + mess_txt = '"' + mess + '"'
     62 + else:
     63 + mess_txt = "none"
     64 + 
     65 + if mess_txt != "none":
     66 + translator = Translator()
     67 + detection = translator.detect(mess_txt)
     68 + language_code = detection.lang
     69 + translation_confidence = detection.confidence
     70 + translation = translator.translate(mess_txt, dest=user_lang)
     71 + original_language = translation.src
     72 + translated_text = translation.text
     73 + else:
     74 + original_language = user_lang
     75 + translated_text = "n/a"
     76 + translation_confidence = "n/a"
     77 + 
     78 + return {
     79 + "original_language": original_language,
     80 + "translated_text": translated_text,
     81 + "translation_confidence": translation_confidence,
     82 + "message_text": mess_txt,
     83 + }
     84 + 
     85 +def process_description(desc, user_lang):
     86 + if desc is not None:
     87 + desc_txt = '"' + desc + '"'
     88 + else:
     89 + desc_txt = "none"
     90 + 
     91 + if desc_txt != "none":
     92 + translator = Translator()
     93 + detection = translator.detect(desc_txt)
     94 + language_code = detection.lang
     95 + translation_confidence = detection.confidence
     96 + translation = translator.translate(desc_txt, dest=user_lang)
     97 + original_language = translation.src
     98 + translated_text = translation.text
     99 + else:
     100 + original_language = user_lang
     101 + translated_text = "n/a"
     102 + translation_confidence = "n/a"
     103 + 
     104 + return {
     105 + "original_language": original_language,
     106 + "translated_text": translated_text,
     107 + "translation_confidence": translation_confidence,
     108 + "description_text": desc_txt,
     109 + }
     110 + 
     111 +def color_print_green(first_string,second_string):
     112 + print(
     113 + Fore.GREEN
     114 + + first_string
     115 + + Style.RESET_ALL
     116 + + second_string
     117 + )
     118 + 
     119 +def parse_html_page(url):
     120 + s = requests.Session()
     121 + s.max_redirects = 10
     122 + s.headers["User-Agent"] = random.choice(user_agent)
     123 + URL = s.get(url)
     124 + URL.encoding = "utf-8"
     125 + html_content = URL.text
     126 + soup = BeautifulSoup(html_content, "html.parser")
     127 + name = ""
     128 + group_description = ""
     129 + total_participants = ""
     130 + try:
     131 + name = soup.find(
     132 + "div", {"class": ["tgme_page_title"]}
     133 + ).text
     134 + except:
     135 + name = "Not found"
     136 + try:
     137 + group_description = soup.find(
     138 + "div", {"class": ["tgme_page_description"]}
     139 + ).text
     140 + descript = Fore.GREEN + "Description: " + Style.RESET_ALL+ group_description
     141 + prefix = descript + " "
     142 + except:
     143 + group_description = "None"
     144 + descript = Fore.GREEN + "Description: " + Style.RESET_ALL+ group_description
     145 + prefix = descript + " "
     146 + 
     147 + try:
     148 + group_participants = soup.find(
     149 + "div", {"class": ["tgme_page_extra"]}
     150 + ).text
     151 + sep = "members"
     152 + stripped = group_participants.split(sep, 1)[0]
     153 + total_participants = (
     154 + stripped.replace(" ", "")
     155 + .replace("members", "")
     156 + .replace("subscribers", "")
     157 + .replace("member", "")
     158 + )
     159 + except:
     160 + total_participants = "Not found" # could be due to restriction, might need to mention
     161 + 
     162 + return {"name":name,"group_description":group_description, "total_participants":total_participants}
  • ■ ■ ■ ■ ■ ■
    src/telepathy.egg-info/PKG-INFO
     1 +Metadata-Version: 2.1
     2 +Name: telepathy
     3 +Version: 2.2.50
     4 +Summary: An OSINT toolkit for investigating Telegram chats.
     5 +Home-page: https://pypi.python.org/pypi/telepathy/
     6 +Author: Jordan Wildon
     7 +Author-email: [email protected]
     8 +License: LICENSE.txt
     9 +Description-Content-Type: text/markdown
     10 +License-File: LICENSE.txt
     11 + 
     12 + 
     13 + 
     14 +Telepathy: An OSINT toolkit for investigating Telegram chats. Developed by Jordan Wildon. Version 2.2.22.
     15 + 
     16 + 
     17 +## Installation
     18 + 
     19 +### Pip install (recommended)
     20 + 
     21 +```
     22 +$ pip3 install telepathy
     23 +```
     24 + 
     25 +### Install from source
     26 + 
     27 +```
     28 +$ git clone https://github.com/jordanwildon/Telepathy.git
     29 +$ cd Telepathy
     30 +$ pip install -r requirements.txt
     31 +```
     32 + 
     33 +## Setup
     34 + 
     35 +On first use, Telepathy will ask for your Telegram API details (obtained from my.telegram.org). Once those are set up, it will prompt you to enter your phone number again and then send an authorization code to your Telegram account. If you have two-factor authentication enabled, you'll be asked to input your Telegram password.
     36 + 
     37 +OPTIONAL: Installing cryptg ($ pip3 install cryptg) may improve Telepathy's speed. The package hand decryption by Python over to C, making media downloads in particular quicker and more efficient.
     38 + 
     39 + 
     40 +## Usage:
     41 + 
     42 +```
     43 +telepathy [OPTIONS]
     44 +```
     45 + 
     46 +Options:
     47 +- **'--target', '-t' [CHAT]**
     48 + 
     49 +this option will identify the target of the scan. The specified chat must be public. To get the chat name, look for the 't.me/chatname' link, and subtract the 't.me/'.
     50 + 
     51 +For example:
     52 + 
     53 +```
     54 +$ telepathy -t durov
     55 +```
     56 + 
     57 +The default is a basic scan which will find the title, description, number of participants, username, URL, chat type, chat ID, access hash, first post date and any applicable restrictions to the chat. For group chats, Telepathy will also generate a memberlist (up to 5,000 members).
     58 + 
     59 + 
     60 +- **'--comprehensive', '-c'**
     61 + 
     62 +A comprehensive scan will offer the same information as the basic scan, but will also archive a chat's message history.
     63 + 
     64 +For example:
     65 + 
     66 +```
     67 +$ telepathy -t durov -c
     68 +```
     69 + 
     70 + 
     71 +- **'--forwards', '-f'**
     72 + 
     73 +This flag will create an edgelist based on messages forwarded into a chat. It can be used alongside either a default or comprehensive scan.
     74 + 
     75 +For example:
     76 + 
     77 +```
     78 +$ telepathy -t durov -f
     79 +```
     80 + 
     81 + 
     82 +- **'--media', '-m'**
     83 + 
     84 +Use this flag to include media archiving alongside a comprehensive scan. This makes the process take significantly longer and should also be used with caution: you'll download all media content from the target chat, and it's up to you to not store illegal files on your system.
     85 + 
     86 +Since 2.2.0, downloading all media files will also generate a CSV file listing the files' metadata.
     87 + 
     88 +For example, this will run a comprehensive scan, including media archiving:
     89 + 
     90 +```
     91 +$ telepathy -t durov -c -m
     92 +```
     93 + 
     94 + 
     95 +- **'--user', '-u' [USER]**
     96 + 
     97 +Looks up a specified user ID. This will only work if your account has "encountered" the user before (for example, after archiving a group).
     98 + 
     99 +```
     100 +$ telepathy -t 0123456789 -u
     101 +```
     102 + 
     103 + 
     104 +- **'--location', '-l' [COORDINATES]**
     105 + 
     106 +Finds users near to specified coordinates. Input should be longitude followed by latitude, seperated by a comma. This feature only works if your Telegram account has a profile image which is set to publicly viewable.
     107 + 
     108 +```
     109 +$ telepathy -t 51.5032973,-0.1217424 -l
     110 +```
     111 + 
     112 +- **'--alt', '-a'**
     113 + 
     114 +Flag for running Telepathy from an alternative number. You can use the same API key and Hash but authenticate with a different phone number. Allows for running multiple scans at once.
     115 + 
     116 +```
     117 +$ telepathy -t Durov -c -a
     118 +```
     119 + 
     120 +- **'--export', '-e'**
     121 + 
     122 +Exports all chats your account is part of to a CSV file. In a future release, this may assist with setting up multiple accounts following the same groups.
     123 + 
     124 +```
     125 +$ telepathy -e
     126 +```
     127 +
     128 +- **'--reply', '-r'**
     129 + 
     130 +Flag for enable the reply in the channel, it will map users who replied in the channel and it will dump the full conversation chain
     131 + 
     132 +```
     133 +$ telepathy -t [CHANNEL] -c -r
     134 +```
     135 + 
     136 + 
     137 +## A note on how Telegram works
     138 + 
     139 +Telegram chats are organised into three key types: Channels, Megagroups/Supergroups and Gigagroups. Each module works slightly differently depending on the chat type. Channels can have seemingly unlimited subscribers and are where an admin will broadcast messages to an audience, Megagroups can have up to 200,000 members, each of whom can participate (if not restricted), and Gigagroups sit somewhere between the two.
     140 + 
     141 + 
     142 +## Upcoming changes
     143 +In some environments (particularly Windows), Telepathy struggles to effectively manage files and can sometimes produce errors. Fixes for these errors will come in due course.
     144 + 
     145 +Upcoming features include:
     146 + 
     147 + - [ ] Adding a time specification flag to set archiving for specific period.
     148 + - [x] The ability to archive comments on messages to channels.
     149 + - [ ] The ability to gather the number of reactions to messages, including statistics on engagement rate.
     150 + - [ ] Finding a method to once again gather complete memberlists (currently restricted by the API).
     151 + - [x] Introducing the ability to scan multiple targets at once.
     152 + - [ ] Improved statistics: including timestamp analysis for channels.
     153 + - [ ] Generating an entirely automated complete report, including visualisation for some statistics.
     154 + - [x] Making it easier to scan private groups which your account is a member of.
     155 + - [ ] Hate speech analytics.
     156 + - [x] Clean code, efficiency tweaks.
     157 + - [x] Add user lookup.
     158 + - [x] Add location lookup.
     159 + - [ ] Maximise compatibility of edgelists with Gephi.
     160 + - [ ] Include sockpuppet account provisioning (creation of accounts from previous exported lists).
     161 + - [ ] Listing who has admin rights in memberlists.
     162 + - [ ] Media downloaded in the background to increase efficiency.
     163 + - [ ] When media archiving is flagged, the location of downloaded content will be added to the archive file.
     164 + - [ ] Adding direct link to posts in the chat archive file
     165 + 
     166 + 
     167 +## feedback
     168 + 
     169 +Please send feedback to @jordanwildon on Twitter. You can follow Telepathy updates at @TelepathyDB.
     170 + 
     171 + 
     172 +## Usage terms
     173 + 
     174 +You may use Telepathy however you like, but your usecase is your responsibility. Be safe and respectful.
     175 + 
     176 + 
     177 +## Credits
     178 + 
     179 +All tools created by Jordan Wildon (@jordanwildon). Special thanks go to [Giacomo Giallombardo](https://github.com/aaarghhh) for adding additional features and code refactoring, and Alex Newhouse (@AlexBNewhouse) for his help with Telepathy v1.
     180 + 
     181 +Where possible, credit for the use of this tool in published research is desired, but not required. This can either come in the form of crediting the author, or crediting Telepathy itself (preferably with a link).
     182 + 
  • ■ ■ ■ ■ ■ ■
    src/telepathy.egg-info/SOURCES.txt
     1 +LICENSE.txt
     2 +README.md
     3 +setup.py
     4 +src/telepathy/const.py
     5 +src/telepathy/telepathy.py
     6 +src/telepathy/utils.py
     7 +src/telepathy.egg-info/PKG-INFO
     8 +src/telepathy.egg-info/SOURCES.txt
     9 +src/telepathy.egg-info/dependency_links.txt
     10 +src/telepathy.egg-info/entry_points.txt
     11 +src/telepathy.egg-info/requires.txt
     12 +src/telepathy.egg-info/top_level.txt
  • ■ ■ ■ ■ ■ ■
    src/telepathy.egg-info/dependency_links.txt
     1 + 
     2 + 
  • ■ ■ ■ ■ ■ ■
    src/telepathy.egg-info/entry_points.txt
     1 +[console_scripts]
     2 +telepathy = telepathy.telepathy:cli
     3 + 
  • ■ ■ ■ ■ ■ ■
    src/telepathy.egg-info/requires.txt
     1 +click~=7.1.2
     2 +telethon==1.25.2
     3 +pandas==1.4.2
     4 +colorama~=0.4.3
     5 +alive_progress==2.4.1
     6 +beautifulsoup4==4.11.1
     7 +requests~=2.28.1
     8 +googletrans==4.0.0rc1
     9 +pprintpp==0.4.0
     10 + 
  • ■ ■ ■ ■ ■ ■
    src/telepathy.egg-info/top_level.txt
     1 +telepathy
     2 + 
Please wait...
Page is in error, reload to recover