Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jordanwildon
GitHub Repository: jordanwildon/Telepathy
Path: blob/main/src/telepathy/telepathy.py
201 views
1
#!/usr/bin/python3
2
3
"""Telepathy cli interface:
4
An OSINT toolkit for investigating Telegram chats.
5
"""
6
7
import pandas as pd
8
import datetime
9
import os
10
import getpass
11
import click
12
import re
13
import time
14
import configparser
15
import asyncio
16
17
from src.telepathy.utils import (
18
print_banner,
19
color_print_green,
20
populate_user,
21
process_message,
22
process_description,
23
parse_tg_date,
24
parse_html_page,
25
print_shell,
26
createPlaceholdeCls,
27
create_path,
28
create_file_report,
29
clean_private_invite,
30
evaluate_reactions,
31
)
32
33
from telethon.tl.functions.messages import GetHistoryRequest, GetDialogsRequest
34
from telethon.tl.types.messages import Messages
35
from telethon.errors import SessionPasswordNeededError, ChannelPrivateError
36
from telethon.tl.types import (
37
InputPeerEmpty,
38
PeerUser,
39
User,
40
PeerChat,
41
PeerChannel,
42
PeerLocated,
43
ChannelParticipantCreator,
44
ChannelParticipantAdmin,
45
)
46
from telethon.tl.functions.users import GetFullUserRequest
47
from telethon import TelegramClient, functions, types, utils
48
from telethon.utils import get_display_name, get_message_id
49
from alive_progress import alive_bar
50
from colorama import Fore, Style
51
from src.telepathy.const import telepathy_file
52
53
"""
54
try:
55
bot = await TelegramClient(os.path.join(base_path, bot_id), API_ID, API_HASH, proxy=proxy).start(bot_token=bot_token)
56
bot.id = bot_id
57
except AccessTokenExpiredError as e:
58
print("Token has expired!")
59
sys.exit()
60
61
me = await bot.get_me()
62
print_bot_info(me)
63
user = await bot(GetFullUserRequest(me))
64
all_users[me.id] = user
65
66
user_info = user.user.to_dict()
67
user_info['token'] = bot_token
68
"""
69
70
71
class Group_Chat_Analisys:
72
73
save_directory = None
74
media_directory = None
75
file_archive = None
76
reply_file_archive = None
77
forward_directory = None
78
file_forwards = None
79
edgelist_file = None
80
memberlist_directory = None
81
memberlist_filename = None
82
reply_memberlist_filename = None
83
history = None
84
history_count = 0
85
86
_target = None
87
_target_entity = None
88
_alphanumeric = None
89
_log_file = None
90
_filetime = None
91
92
client = None
93
94
def __init__(
95
self,
96
target,
97
client,
98
log_file,
99
filetime,
100
replies,
101
forwards,
102
comprehensive,
103
media,
104
json,
105
translate,
106
):
107
self.client = client
108
self._target = target
109
self._log_file = log_file
110
self._filetime = filetime
111
self._alphanumeric = self.calculate_alfanumeric()
112
self.user_check = self.location_check = False
113
self.basic = True if target else False
114
self.reply_analysis = True if replies else False
115
self.forwards_check = True if forwards else False
116
self.comp_check = True if comprehensive else False
117
self.media_archive = True if media else False
118
self.json_check = True if json else False
119
self.translate_check = True if translate else False
120
self.last_date, self.chunk_size, self.user_language = None, 1000, "en"
121
self.create_dirs_files()
122
123
def telepathy_log_run(self):
124
log = []
125
log.append(
126
[
127
self._filetime,
128
self._entity.title,
129
self._group_description,
130
self._translated_description,
131
self._total_participants,
132
self._found_participants,
133
self._group_username,
134
self._group_url,
135
self._chat_type,
136
self._entity.id,
137
self._entity.access_hash,
138
str(self._entity.scam),
139
self._date,
140
self._mtime,
141
self._group_status,
142
]
143
)
144
log_df = pd.DataFrame(
145
log,
146
columns=[
147
"Access Date",
148
"Title",
149
"Description",
150
"Translated description",
151
"Total participants",
152
"Participants found",
153
"Username",
154
"URL",
155
"Chat type",
156
"Chat ID",
157
"Access hash",
158
"Scam",
159
"First post date",
160
"First post time (UTC)",
161
"Restrictions",
162
],
163
)
164
165
if not os.path.isfile(self._log_file):
166
log_df.to_csv(self._log_file, sep=";", index=False)
167
else:
168
log_df.to_csv(self._log_file, sep=";", mode="a", index=False, header=False)
169
170
def calculate_alfanumeric(self):
171
alphanumeric = ""
172
for character in self._target:
173
if character.isalnum():
174
alphanumeric += character
175
return alphanumeric
176
177
async def retrieve_entity(self, _target):
178
current_entity = None
179
target = None
180
try:
181
current_entity = await self.client.get_entity(_target)
182
target = _target
183
except Exception as exx:
184
try:
185
current_entity = await self.client.get_entity(int(_target))
186
target = int(_target)
187
except:
188
pass
189
pass
190
if not current_entity:
191
try:
192
current_entity = await self.client.get_entity(PeerChannel(_target))
193
target = _target
194
except Exception as exx:
195
pass
196
if not current_entity:
197
try:
198
current_entity = await self.client.get_entity(PeerChat(_target))
199
target = _target
200
except Exception as exx:
201
pass
202
if type(target) is int and current_entity.username:
203
target = current_entity.username
204
return current_entity, target
205
206
async def looking_for_members(self, _target):
207
members = []
208
members_df = None
209
all_participants = await self.client.get_participants(_target, limit=5000)
210
211
for user in all_participants:
212
members_df = pd.DataFrame(
213
members,
214
columns=[
215
"Username",
216
"Full name",
217
"User ID",
218
"Phone number",
219
"Group name",
220
],
221
)
222
members.append(populate_user(user, _target))
223
return members_df, all_participants
224
225
async def retrieve_memberlist(self):
226
members_df, all_participants = await self.looking_for_members(self._target)
227
if members_df is not None:
228
with open(self.memberlist_filename, "w+", encoding="utf-8") as save_members:
229
members_df.to_csv(save_members, sep=";")
230
231
if self.json_check:
232
members_df.to_json(
233
self.memberlist_filename_json,
234
orient="records",
235
compression="infer",
236
lines=True,
237
index=True,
238
)
239
240
found_participants = len(all_participants)
241
found_percentage = 0
242
if self._total_participants > 0:
243
found_percentage = (
244
int(found_participants) / int(self._total_participants) * 100
245
)
246
print("\n")
247
color_print_green(" [+] Memberlist fetched", "")
248
return found_participants, found_percentage
249
250
async def retrieve_entity_info(self, _handle, _check_user=False):
251
_result = {"entity": None}
252
web_req = None
253
254
_result["entity"], self._target = await self.retrieve_entity(_handle)
255
256
if not _result["entity"] or (
257
_result["entity"].__class__ == User and _check_user
258
):
259
if _result["entity"].__class__ == User:
260
color_print_green(
261
" [!] ",
262
"You can't search for users using flag -c, run Telepathy using the flag -u.",
263
)
264
exit(1)
265
else:
266
color_print_green(
267
" [!] ", "Telegram handle: {} wasn't found. !!!!".format(_handle)
268
)
269
exit(1)
270
return
271
elif _check_user:
272
if _result["entity"].__class__ == User:
273
_result = {"chat_type": "User"}
274
substring_1 = "PeerUser"
275
276
if _result["entity"].username is not None:
277
_result["username"] = _result["entity"].username
278
else:
279
username = "none"
280
281
string_1 = str(_result["entity"].user_id)
282
if substring_1 in string_1:
283
user_id = re.sub(
284
"[^0-9]",
285
"",
286
string_1,
287
)
288
user_id = await self.client.get_entity(PeerUser(int(user_id)))
289
user_id = str(user_id)
290
_result["user_id"] = user_id
291
_result["first_name"] = _result["entity"].first_name
292
return _result
293
294
_result["total_participants"] = 0
295
(
296
_result["chat_type"],
297
_result["group_url"],
298
_result["group_username"],
299
_result["group_description"],
300
_result["group_status"],
301
_result["translated_description"],
302
) = ("", "", "", "", "", "")
303
304
if _result["entity"].broadcast is True:
305
_result["chat_type"] = "Channel"
306
elif _result["entity"].megagroup is True:
307
_result["chat_type"] = "Megagroup"
308
elif _result["entity"].gigagroup is True:
309
_result["chat_type"] = "Gigagroup"
310
else:
311
_result["chat_type"] = "Chat"
312
313
if _result["entity"].username:
314
_result["group_url"] = "http://t.me/" + _result["entity"].username
315
_result["group_username"] = _result["entity"].username
316
web_req = parse_html_page(_result["group_url"])
317
elif "https://t.me/" in _handle:
318
_result["group_url"] = _handle
319
web_req = parse_html_page(_result["group_url"])
320
_result["group_username"] = "Private group"
321
else:
322
_result["group_url"], _result["group_username"] = (
323
"Private group",
324
"Private group",
325
)
326
327
if web_req:
328
_result["group_description"] = web_req["group_description"]
329
_result["total_participants"] = web_req["total_participants"]
330
331
if self.translate_check:
332
_result["desc"] = process_description(
333
_result["group_description"], self.user_language
334
)
335
_original_language = _result["desc"]["original_language"]
336
_result["translated_description"] = _result["desc"]["translated_text"]
337
else:
338
_result["translated_description"] = "N/A"
339
340
_result["first_post"] = "Not found"
341
_result["date"] = "N/A"
342
_result["datepost"] = "N/A"
343
_result["mtime"] = "N/A"
344
345
async for message in self.client.iter_messages(self._target, reverse=True):
346
_result["datepost"] = parse_tg_date(message.date)
347
_result["date"] = _result["date"]
348
_result["mtime"] = _result["mtime"]
349
_result["first_post"] = _result["datepost"]["timestamp"]
350
break
351
352
if _result["entity"].restriction_reason is not None:
353
ios_restriction = _result["entity"].restriction_reason[0]
354
if 1 in _result["entity"].restriction_reason:
355
android_restriction = _result["entity"].restriction_reason[1]
356
_result["group_status"] = (
357
str(ios_restriction) + ", " + str(android_restriction)
358
)
359
else:
360
_result["group_status"] = str(ios_restriction)
361
else:
362
_result["group_status"] = "None"
363
return _result
364
365
async def retrieve_chat_group_entity(self, _handle):
366
try:
367
_entitydetails = await self.retrieve_entity_info(_handle)
368
except Exception as exx:
369
pass
370
371
self._entity = _entitydetails["entity"]
372
self._total_participants = _entitydetails["total_participants"]
373
(
374
self._chat_type,
375
self._group_url,
376
self._group_username,
377
self._group_description,
378
self._group_status,
379
self._translated_description,
380
) = (
381
_entitydetails["chat_type"],
382
_entitydetails["group_url"],
383
_entitydetails["group_username"],
384
_entitydetails["group_description"],
385
_entitydetails["group_status"],
386
_entitydetails["translated_description"],
387
)
388
self._first_post = _entitydetails["first_post"]
389
self._date = _entitydetails["date"]
390
self._datepost = _entitydetails["datepost"]
391
self._mtime = _entitydetails["mtime"]
392
if self._entity.broadcast is True:
393
self._chat_type = "Channel"
394
elif self._entity.megagroup is True:
395
self._chat_type = "Megagroup"
396
elif self._entity.gigagroup is True:
397
self._chat_type = "Gigagroup"
398
else:
399
self._chat_type = "Chat"
400
401
if self._entity.username:
402
self._group_url = "http://t.me/" + self._entity.username
403
self._group_username = self._entity.username
404
web_req = parse_html_page(self._group_url)
405
elif "https://t.me/" in _handle:
406
self._group_url = _handle
407
web_req = parse_html_page(self._group_url)
408
self._group_username = "Private group"
409
else:
410
web_req = None
411
self._group_url, self._group_username = "Private group", "Private group"
412
413
if web_req:
414
self._group_description = web_req["group_description"]
415
self._total_participants = web_req["total_participants"]
416
417
if self.translate_check == True:
418
self._desc = process_description(
419
self._group_description, self.user_language
420
)
421
_original_language = self._desc["original_language"]
422
self._translated_description = self._desc["translated_text"]
423
else:
424
self._translated_description = "N/A"
425
426
self._group_status = _entitydetails["group_status"]
427
self._first_post = _entitydetails["first_post"]
428
self._date = _entitydetails["date"]
429
self._datepost = _entitydetails["datepost"]
430
self._mtime = _entitydetails["mtime"]
431
432
def create_dirs_files(self):
433
self.save_directory = None
434
self.media_directory = None
435
self.file_archive = None
436
self.reply_file_archive = None
437
self.forward_directory = None
438
self.file_forwards = None
439
self.edgelist_file = None
440
self.memberlist_directory = None
441
self.memberlist_filename = None
442
self.reply_memberlist_filename = None
443
444
if self.basic or self.comp_check:
445
self.save_directory = create_path(
446
os.path.join(telepathy_file, self._alphanumeric)
447
)
448
449
if self.media_archive and self.save_directory:
450
self.media_directory = create_path(
451
os.path.join(self.save_directory, "media")
452
)
453
454
if self.comp_check:
455
self.file_archive = create_file_report(
456
self.save_directory,
457
self._alphanumeric,
458
"archive",
459
"csv",
460
self._filetime,
461
)
462
self.reply_file_archive = create_file_report(
463
self.save_directory,
464
self._alphanumeric,
465
"reply_archive",
466
"csv",
467
self._filetime,
468
)
469
if self.json_check:
470
self.archive_filename_json = create_file_report(
471
self.memberlist_directory,
472
self._alphanumeric,
473
"archive",
474
"json",
475
self._filetime,
476
False,
477
)
478
479
if self.forwards_check == True:
480
self.forward_directory = create_path(
481
os.path.join(self.save_directory, "edgelist")
482
)
483
self.file_forwards = create_file_report(
484
self.forward_directory,
485
self._alphanumeric,
486
"edgelist",
487
"csv",
488
self._filetime,
489
)
490
self.edgelist_file = create_file_report(
491
self.forward_directory,
492
self._alphanumeric,
493
"edgelist",
494
"csv",
495
self._filetime,
496
False,
497
)
498
if self.json_check:
499
self.edgelist_filename_json = create_file_report(
500
self.memberlist_directory,
501
self._alphanumeric,
502
"edgelist",
503
"json",
504
self._filetime,
505
False,
506
)
507
508
if self.basic is True or self.comp_check is True:
509
self.memberlist_directory = create_path(
510
os.path.join(self.save_directory, "memeberlist")
511
)
512
self.memberlist_filename = create_file_report(
513
self.memberlist_directory,
514
self._alphanumeric,
515
"members",
516
"csv",
517
self._filetime,
518
)
519
if self.json_check:
520
self.memberlist_filename_json = create_file_report(
521
self.memberlist_directory,
522
self._alphanumeric,
523
"members",
524
"json",
525
self._filetime,
526
False,
527
)
528
self.reply_memberlist_filename = create_file_report(
529
self.memberlist_directory,
530
self._alphanumeric,
531
"active_members",
532
"csv",
533
self._filetime,
534
)
535
536
async def analyze_group_channel(self, _target=None):
537
if not _target:
538
_target = self._target
539
_target = clean_private_invite(_target)
540
self._found_participants = 0
541
await self.retrieve_chat_group_entity(_target)
542
543
if self.basic and not self.comp_check:
544
color_print_green(" [!] ", "Performing basic scan")
545
elif self.comp_check:
546
color_print_green(" [!] ", "Performing comprehensive scan")
547
if self.forwards_check:
548
color_print_green(" [!] ", "Forwards will be fetched")
549
550
if self.basic is True or self.comp_check is True:
551
if self._chat_type != "Channel":
552
(
553
self._found_participants,
554
self._found_percentage,
555
) = await self.retrieve_memberlist()
556
557
setattr(self._entity, "group_description", self._group_description)
558
setattr(self._entity, "group_status", self._group_status)
559
setattr(self._entity, "group_username", self._group_username)
560
setattr(self._entity, "first_post", self._first_post)
561
setattr(self._entity, "group_url", self._group_url)
562
setattr(self._entity, "chat_type", self._chat_type)
563
setattr(
564
self._entity, "translated_description", self._translated_description
565
)
566
setattr(self._entity, "total_participants", self._total_participants)
567
568
if self._chat_type != "Channel":
569
setattr(self._entity, "found_participants", self._found_participants)
570
setattr(self._entity, "found_percentage", self._found_percentage)
571
setattr(self._entity, "memberlist_filename", self.memberlist_filename)
572
else:
573
setattr(self._entity, "found_participants", self._found_participants)
574
print_flag = "group_recap"
575
576
if self._chat_type == "Channel":
577
print_flag = "channel_recap"
578
print_shell(print_flag, self._entity)
579
self.telepathy_log_run()
580
await self.process_group_channel_messages(self._target)
581
582
async def f_export(self):
583
exports = []
584
for Dialog in await self.client.get_dialogs():
585
try:
586
if Dialog.entity.username:
587
self._group_url = "http://t.me/" + Dialog.entity.username
588
self._group_username = Dialog.entity.username
589
590
web_req = parse_html_page(self._group_url)
591
self._group_description = web_req["group_description"]
592
self._total_participants = web_req["total_participants"]
593
594
if self.translate_check:
595
_desc = process_description(
596
self._group_description, self.user_language
597
)
598
self._translated_description = _desc["translated_text"]
599
else:
600
self._translated_description = "N/A"
601
602
if Dialog.entity.broadcast is True:
603
self._chat_type = "Channel"
604
elif Dialog.entity.megagroup is True:
605
self._chat_type = "Megagroup"
606
elif Dialog.entity.gigagroup is True:
607
self._chat_type = "Gigagroup"
608
else:
609
self._chat_type = "Chat"
610
if not self.target_type:
611
self.target_type = "g"
612
613
if Dialog.entity.restriction_reason is not None:
614
ios_restriction = Dialog.entity.restriction_reason[0]
615
if 1 in Dialog.entity.restriction_reason:
616
android_restriction = Dialog.entity.restriction_reason[1]
617
self._group_status = (
618
str(ios_restriction) + ", " + str(android_restriction)
619
)
620
else:
621
self._group_status = str(ios_restriction)
622
else:
623
self._group_status = "None"
624
625
exports.append(
626
[
627
self.filetime,
628
Dialog.entity.title,
629
self._group_description,
630
self._translated_description,
631
self._total_participants,
632
self._group_username,
633
self._group_url,
634
self._chat_type,
635
Dialog.entity.id,
636
Dialog.entity.access_hash,
637
self._group_status,
638
]
639
)
640
641
export_df = pd.DataFrame(
642
exports,
643
columns=[
644
"Access Date",
645
"Title",
646
"Description",
647
"Translated description",
648
"Total participants",
649
"Username",
650
"URL",
651
"Chat type",
652
"Chat ID",
653
"Access hash",
654
"Restrictions",
655
],
656
)
657
658
if not os.path.isfile(self.export_file):
659
export_df.to_csv(
660
self.export_file,
661
sep=";",
662
index=False,
663
)
664
else:
665
export_df.to_csv(
666
self.export_file,
667
sep=";",
668
mode="w",
669
index=False,
670
)
671
672
except AttributeError:
673
pass
674
675
async def retrieve_self_history(self, _target=None):
676
cc = False
677
if not _target:
678
_target = self._target
679
cc = True
680
681
_target = clean_private_invite(_target)
682
await self.retrieve_chat_group_entity(_target)
683
684
get_history = GetHistoryRequest(
685
peer=self._entity,
686
offset_id=0,
687
offset_date=None,
688
add_offset=0,
689
limit=1,
690
max_id=0,
691
min_id=0,
692
hash=0,
693
)
694
history = await self.client(get_history)
695
if isinstance(history, Messages):
696
count = len(history.messages)
697
else:
698
count = history.count
699
700
if cc:
701
self.history = history
702
self.history_count = count
703
return None, None
704
else:
705
return history, count
706
707
async def process_group_channel_messages(self, _target):
708
if self.forwards_check is True and self.comp_check is False:
709
color_print_green(" [-] ", "Calculating number of forwarded messages...")
710
forwards_list = []
711
forward_count = 0
712
private_count = 0
713
to_ent = await self.client.get_entity(_target)
714
to_title = to_ent.title
715
716
forwards_df = pd.DataFrame(
717
forwards_list,
718
columns=[
719
"Source",
720
"Target",
721
"Label",
722
"Source_ID",
723
"Username",
724
"Timestamp",
725
],
726
)
727
728
await self.retrieve_self_history()
729
for message in self.history.messages:
730
if message.forward is not None:
731
forward_count += 1
732
733
color_print_green(" [-] ", "Fetching forwarded messages...")
734
progress_bar = Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
735
736
with alive_bar(
737
forward_count, dual_line=True, title=progress_bar, length=20
738
) as bar:
739
740
async for message in self.client.iter_messages(_target):
741
if message.forward is not None:
742
try:
743
f_from_id = message.forward.original_fwd.from_id
744
if f_from_id is not None:
745
ent = await self.client.get_entity(f_from_id)
746
username = ent.username
747
timestamp = parse_tg_date(message.date)["timestamp"]
748
749
substring = "PeerUser"
750
string = str(f_from_id)
751
if self._chat_type != "Channel":
752
if substring in string:
753
user_id = re.sub("[^0-9]", "", string)
754
user_id = await self.client.get_entity(
755
PeerUser(int(user_id))
756
)
757
user_id = str(user_id)
758
result = (
759
"User: "
760
+ str(ent.first_name)
761
+ " / ID: "
762
+ str(user_id.id)
763
)
764
else:
765
result = str(ent.title)
766
else:
767
result = str(ent.title)
768
769
forwards_df = pd.DataFrame(
770
forwards_list,
771
columns=[
772
"Source",
773
"Target",
774
"Label",
775
"Source_ID",
776
"Username",
777
"Timestamp",
778
],
779
)
780
781
forwards_list.append(
782
[
783
result,
784
_target,
785
to_title,
786
f_from_id,
787
username,
788
timestamp,
789
]
790
)
791
792
except Exception as e:
793
if e is ChannelPrivateError:
794
private_count += 1
795
print("Private channel")
796
continue
797
798
time.sleep(0.5)
799
bar()
800
801
with open(
802
self.edgelist_file, "w+", encoding="utf-8"
803
) as save_forwards:
804
forwards_df.to_csv(save_forwards, sep=";", index=False) # missing index=False (Gephi issue)
805
806
if self.json_check:
807
forwards_df.to_json(
808
self.edgelist_filename_json,
809
orient="records",
810
compression="infer",
811
lines=True,
812
index=True,
813
)
814
815
if forward_count >= 15:
816
forwards_found = forwards_df.Source.count()
817
value_count = forwards_df["Source"].value_counts()
818
df01 = value_count.rename_axis("unique_values").reset_index(
819
name="counts"
820
)
821
822
report_forward = createPlaceholdeCls()
823
report_forward.forward_one = (
824
str(df01.iloc[0]["unique_values"])
825
+ ", "
826
+ str(df01.iloc[0]["counts"])
827
+ " forwarded messages"
828
)
829
report_forward.forward_two = (
830
str(df01.iloc[1]["unique_values"])
831
+ ", "
832
+ str(df01.iloc[1]["counts"])
833
+ " forwarded messages"
834
)
835
report_forward.forward_three = (
836
str(df01.iloc[2]["unique_values"])
837
+ ", "
838
+ str(df01.iloc[2]["counts"])
839
+ " forwarded messages"
840
)
841
report_forward.forward_four = (
842
str(df01.iloc[3]["unique_values"])
843
+ ", "
844
+ str(df01.iloc[3]["counts"])
845
+ " forwarded messages"
846
)
847
report_forward.forward_five = (
848
str(df01.iloc[4]["unique_values"])
849
+ ", "
850
+ str(df01.iloc[4]["counts"])
851
+ " forwarded messages"
852
)
853
854
df02 = forwards_df.Source.unique()
855
report_forward.unique_forwards = len(df02)
856
report_forward.edgelist_file = self.edgelist_file
857
report_forward.forward_count = forward_count
858
report_forward.forwards_found = forwards_found
859
print_shell("forwarder_stat", report_forward)
860
else:
861
print(
862
"\n"
863
+ Fore.GREEN
864
+ " [!] Insufficient forwarded messages found"
865
+ Style.RESET_ALL
866
)
867
868
else:
869
if self.comp_check is True:
870
871
files = []
872
message_list = []
873
forwards_list = []
874
replies_list = []
875
user_replier_list = []
876
forward_count, private_count, message_count, total_reactions = (
877
0,
878
0,
879
0,
880
0,
881
)
882
883
if self.media_archive is True:
884
print("\n")
885
color_print_green(" [!] ", "Media content will be archived")
886
887
color_print_green(" [!] ", "Calculating number of messages...")
888
889
await self.retrieve_self_history()
890
print("\n")
891
color_print_green(" [-] ", "Fetching message archive...")
892
progress_bar = Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
893
894
with alive_bar(
895
self.history_count,
896
dual_line=True,
897
title=progress_bar,
898
length=20,
899
) as bar:
900
901
to_ent = self._entity
902
903
async for message in self.client.iter_messages(_target, limit=None):
904
if message is not None:
905
try:
906
c_archive = pd.DataFrame(
907
message_list,
908
columns=[
909
"To",
910
"Message ID",
911
"Display_name",
912
"User ID",
913
"Message_text",
914
"Original_language",
915
"Translated_text",
916
"Translation_confidence",
917
"Timestamp",
918
"Has_media",
919
"Reply_to_ID",
920
"Replies",
921
"Forwards",
922
"Views",
923
"Total_reactions",
924
"Reply_ER_reach",
925
"Reply_ER_impressions",
926
"Forwards_ER_reach",
927
"Forwards_ER_impressions",
928
"Reaction_ER_reach",
929
"Reactions_ER_impressions",
930
"Thumbs_up",
931
"Thumbs_down",
932
"Heart",
933
"Fire",
934
"Smile_with_hearts",
935
"Clap",
936
"Smile",
937
"Thinking",
938
"Exploding_head",
939
"Scream",
940
"Angry",
941
"Single_tear",
942
"Party",
943
"Starstruck",
944
"Vomit",
945
"Poop",
946
"Pray",
947
"Edit_date",
948
"URL",
949
"Media save directory",
950
],
951
)
952
953
c_forwards = pd.DataFrame(
954
forwards_list,
955
columns=[
956
"Source",
957
"Target",
958
"Label",
959
"Source_ID",
960
"Username",
961
"Timestamp",
962
],
963
)
964
965
# if message.reactions:
966
# if message.reactions.can_see_list:
967
# c_reactioneer = pd.DataFrame(
968
# user_reaction_list,
969
# columns=[
970
# "Username",
971
# "Full name",
972
# "User ID",
973
# "Phone number",
974
# "Group name",
975
# ],
976
# )
977
978
if (
979
message.replies
980
and self.reply_analysis
981
and self._chat_type == "Channel"
982
):
983
if message.replies.replies > 0:
984
c_repliers = pd.DataFrame(
985
user_replier_list,
986
columns=[
987
"Username",
988
"Full name",
989
"User ID",
990
"Phone number",
991
"Group name",
992
],
993
)
994
995
c_replies = pd.DataFrame(
996
replies_list,
997
columns=[
998
"To",
999
"Message ID",
1000
"Reply ID",
1001
"Display_name",
1002
"ID",
1003
"Message_text",
1004
"Original_language",
1005
"Translated_text",
1006
"Translation_confidence",
1007
"Timestamp",
1008
],
1009
)
1010
1011
if message.replies:
1012
if message.replies.replies > 0:
1013
async for repl in self.client.iter_messages(
1014
message.chat_id,
1015
reply_to=message.id,
1016
):
1017
1018
user = await self.client.get_entity(
1019
repl.from_id.user_id
1020
)
1021
1022
userdet = populate_user(user, _target)
1023
user_replier_list.append(userdet)
1024
1025
if self.translate_check:
1026
mss_txt = process_message(
1027
repl.text, self.user_language
1028
)
1029
original_language = (
1030
mss_txt["original_language"],
1031
)
1032
translated_text = (
1033
mss_txt["translated_text"],
1034
)
1035
translation_confidence = (
1036
mss_txt["translation_confidence"],
1037
)
1038
reply_text = mss_txt["message_text"]
1039
else:
1040
original_language = "N/A"
1041
translated_text = "N/A"
1042
translation_confidence = "N/A"
1043
reply_text = repl.text
1044
1045
replies_list.append(
1046
[
1047
_target,
1048
message.id,
1049
repl.id,
1050
userdet[1],
1051
userdet[2],
1052
reply_text,
1053
original_language,
1054
translated_text,
1055
translation_confidence,
1056
parse_tg_date(repl.date)[
1057
"timestamp"
1058
],
1059
]
1060
)
1061
1062
display_name = get_display_name(message.sender)
1063
if self._chat_type != "Channel":
1064
substring = "PeerUser"
1065
string = str(message.from_id)
1066
if substring in string:
1067
user_id = re.sub("[^0-9]", "", string)
1068
nameID = str(user_id)
1069
else:
1070
nameID = str(message.from_id)
1071
else:
1072
nameID = to_ent.id
1073
1074
timestamp = parse_tg_date(message.date)["timestamp"]
1075
reply = message.reply_to_msg_id
1076
1077
if self.translate_check:
1078
_mess = process_message(
1079
message.text, self.user_language
1080
)
1081
message_text = _mess["message_text"]
1082
original_language = _mess["original_language"]
1083
translated_text = _mess["translated_text"]
1084
translation_confidence = _mess[
1085
"translation_confidence"
1086
]
1087
else:
1088
message_text = message.text
1089
original_language = "N/A"
1090
translated_text = "N/A"
1091
translation_confidence = "N/A"
1092
1093
if message.forwards is not None:
1094
forwards = int(message.forwards)
1095
else:
1096
forwards = "N/A"
1097
1098
if message.views is not None:
1099
views = int(message.views)
1100
else:
1101
views = "N/A"
1102
1103
total_reactions, reaction_detail = evaluate_reactions(
1104
message
1105
)
1106
1107
if self.media_archive:
1108
if message.media is not None:
1109
path = await message.download_media(
1110
file=self.media_directory
1111
)
1112
files.append(path)
1113
media_file = path
1114
else:
1115
media_file = "N/A"
1116
else:
1117
media_file = "N/A"
1118
1119
if message.media is not None:
1120
has_media = "TRUE"
1121
else:
1122
has_media = "FALSE"
1123
1124
if message.replies:
1125
reply_count = int(message.replies.replies)
1126
else:
1127
reply_count = "N/A"
1128
1129
if message.edit_date:
1130
edit_date = str(message.edit_date)
1131
else:
1132
edit_date = "None"
1133
1134
"""Need to find a way to calculate these in case these figures don't exist to make it
1135
comparable across channels for a total engagement number (e.g. if replies/reactions are off).
1136
If not N/A would cover if it's off, zero if it's none. Working on some better logic here."""
1137
1138
if (
1139
reply_count != "N/A"
1140
and self._total_participants is not None
1141
):
1142
reply_reach_ER = (
1143
reply_count / int(self._total_participants)
1144
) * 100
1145
else:
1146
reply_reach_ER = "N/A"
1147
1148
if reply_count != "N/A" and views != "N/A":
1149
reply_impressions_ER = (
1150
reply_count / int(views)
1151
) * 100
1152
else:
1153
reply_impressions_ER = "N/A"
1154
1155
if (
1156
forwards != "N/A"
1157
and self._total_participants is not None
1158
):
1159
forwards_reach_ER = (
1160
forwards / int(self._total_participants)
1161
) * 100
1162
else:
1163
forwards_reach_ER = "N/A"
1164
1165
if forwards != "N/A" and views != "N/A":
1166
forwards_impressions_ER = (
1167
forwards / int(views)
1168
) * 100
1169
else:
1170
forwards_impressions_ER = "N/A"
1171
1172
if (
1173
total_reactions != "N/A"
1174
and self._total_participants is not None
1175
):
1176
reactions_reach_ER = (
1177
total_reactions / int(self._total_participants)
1178
) * 100
1179
else:
1180
reactions_reach_ER = "N/A"
1181
1182
if total_reactions != "N/A" and views != "N/A":
1183
reactions_impressions_ER = (
1184
total_reactions / int(views)
1185
) * 100
1186
else:
1187
reactions_impressions_ER = "N/A"
1188
1189
post_url = (
1190
"https://t.me/s/" + _target + "/" + str(message.id)
1191
)
1192
1193
message_list.append(
1194
[
1195
_target,
1196
message.id,
1197
display_name,
1198
nameID,
1199
message_text,
1200
original_language,
1201
translated_text,
1202
translation_confidence,
1203
timestamp,
1204
has_media,
1205
reply,
1206
reply_count,
1207
forwards,
1208
views,
1209
total_reactions,
1210
reply_reach_ER,
1211
reply_impressions_ER,
1212
forwards_reach_ER,
1213
forwards_impressions_ER,
1214
reactions_reach_ER,
1215
reactions_impressions_ER,
1216
reaction_detail["thumbs_up"],
1217
reaction_detail["thumbs_down"],
1218
reaction_detail["heart"],
1219
reaction_detail["fire"],
1220
reaction_detail["smile_with_hearts"],
1221
reaction_detail["clap"],
1222
reaction_detail["smile"],
1223
reaction_detail["thinking"],
1224
reaction_detail["exploding_head"],
1225
reaction_detail["scream"],
1226
reaction_detail["angry"],
1227
reaction_detail["single_tear"],
1228
reaction_detail["party_popper"],
1229
reaction_detail["starstruck"],
1230
reaction_detail["vomiting"],
1231
reaction_detail["poop"],
1232
reaction_detail["praying"],
1233
edit_date,
1234
post_url,
1235
media_file,
1236
]
1237
)
1238
1239
if message.forward is not None:
1240
try:
1241
forward_count += 1
1242
to_title = to_ent.title
1243
f_from_id = message.forward.original_fwd.from_id
1244
1245
if f_from_id is not None:
1246
ent_info = await self.retrieve_entity_info(
1247
f_from_id, True
1248
)
1249
result = ""
1250
username = ent_info["entityt"].username
1251
if ent_info:
1252
if ent_info["chat_type"] == "User":
1253
result = (
1254
"User: {} / ID: {} ".format(
1255
ent_info[""], ent_info[""]
1256
)
1257
)
1258
elif (
1259
ent_info["chat_type"] == "Megagroup"
1260
or ent_info["chat_type"]
1261
== "Gigagroup"
1262
or ent_info["chat_type"] == "Chat"
1263
):
1264
result = ent_info["entity"].title
1265
elif ent_info["chat_type"] == "Channel":
1266
result = ent_info["entity"].title
1267
print("user")
1268
1269
forwards_list.append(
1270
[
1271
result,
1272
_target,
1273
to_title,
1274
f_from_id,
1275
username,
1276
timestamp,
1277
]
1278
)
1279
1280
except ChannelPrivateError:
1281
private_count += 1
1282
continue
1283
1284
except Exception as e:
1285
print("An exception occurred.", e)
1286
continue
1287
1288
except Exception as e:
1289
print("An exception occurred.", e)
1290
1291
else:
1292
message_list.append(
1293
[
1294
"None",
1295
"None",
1296
"None",
1297
"None",
1298
"None",
1299
"None",
1300
"None",
1301
"None",
1302
"None",
1303
"None",
1304
"None",
1305
"None",
1306
"None",
1307
"None",
1308
"None",
1309
"None",
1310
"None",
1311
"None",
1312
"None",
1313
"None",
1314
"None",
1315
"None",
1316
"None",
1317
"None",
1318
"None",
1319
"None",
1320
"None",
1321
"None",
1322
"None",
1323
"None",
1324
"None",
1325
"None",
1326
"None",
1327
"None",
1328
"None",
1329
"None",
1330
"None",
1331
"None",
1332
"None",
1333
"None",
1334
]
1335
)
1336
1337
time.sleep(0.5)
1338
bar()
1339
1340
if self.reply_analysis is True:
1341
if len(replies_list) > 0:
1342
with open(
1343
self.reply_file_archive, "w+", encoding="utf-8"
1344
) as rep_file:
1345
c_replies.to_csv(rep_file, sep=";")
1346
1347
if len(user_replier_list) > 0:
1348
with open(
1349
self.reply_memberlist_filename, "w+", encoding="utf-8"
1350
) as repliers_file:
1351
c_repliers.to_csv(repliers_file, sep=";")
1352
1353
with open(self.file_archive, "w+", encoding="utf-8") as archive_file:
1354
c_archive.to_csv(archive_file, sep=";")
1355
1356
if self.json_check:
1357
c_archive.to_json(
1358
self.archive_filename_json,
1359
orient="records",
1360
compression="infer",
1361
lines=True,
1362
index=True,
1363
)
1364
1365
if self.forwards_check:
1366
with open(
1367
self.file_forwards, "w+", encoding="utf-8"
1368
) as forwards_file:
1369
c_forwards.to_csv(forwards_file, sep=";")
1370
1371
if self.json_check:
1372
c_forwards.to_json(
1373
self.edgelist_filename_json,
1374
orient="records",
1375
compression="infer",
1376
lines=True,
1377
index=True,
1378
)
1379
1380
messages_found = int(c_archive.To.count()) - 1
1381
report_obj = createPlaceholdeCls()
1382
report_obj.messages_found = messages_found
1383
report_obj.file_archive = self.file_archive
1384
1385
if self._chat_type == "Channel":
1386
print_shell("channel_stat", report_obj)
1387
else:
1388
pvalue_count = c_archive["Display_name"].value_counts()
1389
df03 = pvalue_count.rename_axis("unique_values").reset_index(
1390
name="counts"
1391
)
1392
1393
"""
1394
message_frequency_count = {}
1395
message_text = {}
1396
word_count = {}
1397
most_used_words = {}
1398
most_used_words_filtered = {}
1399
"""
1400
# message stats, top words
1401
1402
report_obj.poster_one = (
1403
str(df03.iloc[0]["unique_values"])
1404
+ ", "
1405
+ str(df03.iloc[0]["counts"])
1406
+ " messages"
1407
)
1408
report_obj.poster_two = (
1409
str(df03.iloc[1]["unique_values"])
1410
+ ", "
1411
+ str(df03.iloc[1]["counts"])
1412
+ " messages"
1413
)
1414
report_obj.poster_three = (
1415
str(df03.iloc[2]["unique_values"])
1416
+ ", "
1417
+ str(df03.iloc[2]["counts"])
1418
+ " messages"
1419
)
1420
report_obj.poster_four = (
1421
str(df03.iloc[3]["unique_values"])
1422
+ ", "
1423
+ str(df03.iloc[3]["counts"])
1424
+ " messages"
1425
)
1426
report_obj.poster_five = (
1427
str(df03.iloc[4]["unique_values"])
1428
+ ", "
1429
+ str(df03.iloc[4]["counts"])
1430
+ " messages"
1431
)
1432
1433
df04 = c_archive.Display_name.unique()
1434
unique_active = len(df04)
1435
report_obj.unique_active = unique_active
1436
print_shell("group_stat", report_obj)
1437
1438
if self.reply_analysis is True:
1439
if len(replies_list) > 0:
1440
replier_value_count = c_repliers["User ID"].value_counts()
1441
replier_df = replier_value_count.rename_axis(
1442
"unique_values"
1443
).reset_index(name="counts")
1444
1445
repliers = createPlaceholdeCls()
1446
repliers.replier_one = (
1447
str(replier_df.iloc[0]["unique_values"])
1448
+ ", "
1449
+ str(replier_df.iloc[0]["counts"])
1450
+ " replies"
1451
)
1452
repliers.replier_two = (
1453
str(replier_df.iloc[1]["unique_values"])
1454
+ ", "
1455
+ str(replier_df.iloc[1]["counts"])
1456
+ " replies"
1457
)
1458
repliers.replier_three = (
1459
str(replier_df.iloc[2]["unique_values"])
1460
+ ", "
1461
+ str(replier_df.iloc[2]["counts"])
1462
+ " replies"
1463
)
1464
repliers.replier_four = (
1465
str(replier_df.iloc[3]["unique_values"])
1466
+ ", "
1467
+ str(replier_df.iloc[3]["counts"])
1468
+ " replies"
1469
)
1470
repliers.replier_five = (
1471
str(replier_df.iloc[4]["unique_values"])
1472
+ ", "
1473
+ str(replier_df.iloc[4]["counts"])
1474
+ " replies"
1475
)
1476
1477
replier_count_df = c_repliers["User ID"].unique()
1478
replier_unique = len(replier_count_df)
1479
repliers.user_replier_list_len = len(user_replier_list)
1480
repliers.reply_file_archive = str(self.reply_file_archive)
1481
repliers.reply_memberlist_filename = str(
1482
self.reply_memberlist_filename
1483
)
1484
repliers.replier_unique = str(replier_unique)
1485
print_shell("reply_stat", repliers)
1486
1487
if self.forwards_check is True:
1488
if forward_count >= 15:
1489
forwards_found = c_forwards.Source.count()
1490
value_count = c_forwards["Source"].value_counts()
1491
c_f_stats = value_count.rename_axis(
1492
"unique_values"
1493
).reset_index(name="counts")
1494
1495
report_forward = createPlaceholdeCls()
1496
report_forward.forward_one = (
1497
str(c_f_stats.iloc[0]["unique_values"])
1498
+ ", "
1499
+ str(c_f_stats.iloc[0]["counts"])
1500
+ " forwarded messages"
1501
)
1502
report_forward.forward_two = (
1503
str(c_f_stats.iloc[1]["unique_values"])
1504
+ ", "
1505
+ str(c_f_stats.iloc[1]["counts"])
1506
+ " forwarded messages"
1507
)
1508
report_forward.forward_three = (
1509
str(c_f_stats.iloc[2]["unique_values"])
1510
+ ", "
1511
+ str(c_f_stats.iloc[2]["counts"])
1512
+ " forwarded messages"
1513
)
1514
report_forward.forward_four = (
1515
str(c_f_stats.iloc[3]["unique_values"])
1516
+ ", "
1517
+ str(c_f_stats.iloc[3]["counts"])
1518
+ " forwarded messages"
1519
)
1520
report_forward.forward_five = (
1521
str(c_f_stats.iloc[4]["unique_values"])
1522
+ ", "
1523
+ str(c_f_stats.iloc[4]["counts"])
1524
+ " forwarded messages"
1525
)
1526
1527
c_f_unique = c_forwards.Source.unique()
1528
report_forward.unique_forwards = len(c_f_unique)
1529
report_forward.forward_count = forward_count
1530
report_forward.forwards_found = forwards_found
1531
report_forward.edgelist_file = self.edgelist_file
1532
report_forward.private_count = private_count
1533
print_shell("forwarder_stat", report_forward)
1534
else:
1535
color_print_green(
1536
" [!] Insufficient forwarded messages found",
1537
self.edgelist_file,
1538
)
1539
1540
1541
class Telepathy_cli:
1542
1543
alt = None
1544
target_type = None
1545
export = False
1546
1547
def __init__(
1548
self,
1549
target,
1550
comprehensive,
1551
media,
1552
forwards,
1553
user,
1554
bot,
1555
location,
1556
alt,
1557
json,
1558
export,
1559
replies,
1560
translate,
1561
triangulate_membership,
1562
):
1563
1564
self.config_p = configparser.ConfigParser()
1565
self.config_p.read(os.path.join("config", "config.ini"))
1566
# Defining default values
1567
self.user_check = self.location_check = False
1568
self.basic = True if target else False
1569
self.reply_analysis = True if replies else False
1570
self.forwards_check = True if forwards else False
1571
self.comp_check = True if comprehensive else False
1572
self.media_archive = True if media else False
1573
self.json_check = True if json else False
1574
self.translate_check = True if translate else False
1575
self.last_date, self.chunk_size, self.user_language = None, 1000, "en"
1576
self.bot = bot is not None
1577
self.alt = 0 if alt is None else int(alt)
1578
self.filetime = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M")
1579
self.filetime_clean = str(self.filetime)
1580
1581
if bot:
1582
if ":" in bot:
1583
self.bot_id = bot.split(":")[0]
1584
self.hash = bot.split(":")[1]
1585
else:
1586
color_print_green(
1587
" [!] ",
1588
"The bot_id/bot_hash isn't valid. Pls insert a valid api_id//api_hash",
1589
)
1590
if user:
1591
self.user_check, self.basic = True, False
1592
if location:
1593
self.location_check, self.basic = True, False
1594
if export:
1595
self.export = True
1596
self.t = " "
1597
1598
self.triangulate = True if triangulate_membership else False
1599
1600
if "telepathy" in self.config_p.keys():
1601
self.telepathy_file = self.config_p["telepathy"]["telepathy_files"]
1602
self.json_file = os.path.join(
1603
self.telepathy_file, self.config_p["telepathy"]["json_file"]
1604
)
1605
self.login = os.path.join(
1606
self.telepathy_file, self.config_p["telepathy"]["login"]
1607
)
1608
self.log_file = os.path.join(
1609
self.telepathy_file, self.config_p["telepathy"]["log_file"]
1610
)
1611
self.export_file = os.path.join(
1612
self.telepathy_file, self.config_p["telepathy"]["export_file"]
1613
)
1614
else:
1615
self.telepathy_file = os.path.join(
1616
"..", "src", "telepathy", "telepathy_files"
1617
)
1618
self.json_file = os.path.join(self.telepathy_file, "json_files")
1619
self.login = os.path.join(self.telepathy_file, "login.txt")
1620
self.log_file = os.path.join(self.telepathy_file, "log.csv")
1621
self.export_file = os.path.join(self.telepathy_file, "export.csv")
1622
self.create_path(self.telepathy_file)
1623
self.overlaps_dir = os.path.join(self.telepathy_file, "overlaps")
1624
self.bots_dir = os.path.join(self.telepathy_file, "bots")
1625
self.create_path(self.overlaps_dir)
1626
self.target = target
1627
self.create_tg_client()
1628
1629
@staticmethod
1630
def create_path(path_d):
1631
if not os.path.exists(path_d):
1632
os.makedirs(path_d)
1633
return path_d
1634
1635
@staticmethod
1636
def login_function():
1637
api_id = input("Please enter your API ID:\n")
1638
api_hash = input("Please enter your API Hash:\n")
1639
phone_number = input("Please enter your phone number:\n")
1640
return api_id, api_hash, phone_number
1641
1642
@staticmethod
1643
def clean_private_invite(url):
1644
if "https://t.me/+" in url:
1645
return url.replace("https://t.me/+", "https://t.me/joinchat/")
1646
1647
def retrieve_alt(self):
1648
with open(self.login, encoding="utf-8") as file:
1649
_api_id = ""
1650
_api_hash = ""
1651
_phone_number = ""
1652
try:
1653
content = file.readlines()
1654
details = content[self.alt]
1655
_api_id, _api_hash, _phone_number = details.split(sep=",")
1656
except:
1657
_api_id, _api_hash, _phone_number = self.login_function()
1658
with open(self.login, "a+", encoding="utf-8") as file_io:
1659
file_io.write(
1660
_api_id + "," + _api_hash + "," + _phone_number + "\n"
1661
)
1662
return _api_id, _api_hash, _phone_number
1663
1664
def create_tg_client(self):
1665
if os.path.isfile(self.login) == False:
1666
api_id, api_hash, phone_number = self.login_function()
1667
with open(self.login, "w+", encoding="utf-8") as f:
1668
f.write(api_id + "," + api_hash + "," + phone_number + "\n")
1669
else:
1670
self.api_id, self.api_hash, self.phone_number = self.retrieve_alt()
1671
"""End of API details"""
1672
self.client = TelegramClient(
1673
os.path.join(self.telepathy_file, "{}.session".format(self.phone_number)),
1674
self.api_id,
1675
self.api_hash,
1676
)
1677
1678
async def connect_tg_client_and_run(self):
1679
await self.client.connect()
1680
if not await self.client.is_user_authorized():
1681
await self.client.send_code_request(self.phone_number)
1682
try:
1683
await self.client.sign_in(
1684
phone=self.phone_number,
1685
code=input("Enter code: "),
1686
)
1687
except SessionPasswordNeededError:
1688
await self.client.sign_in(
1689
password=getpass.getpass(
1690
prompt="Password: ",
1691
stream=None,
1692
)
1693
)
1694
result = self.client(
1695
GetDialogsRequest(
1696
offset_date=self.last_date,
1697
offset_id=0,
1698
offset_peer=InputPeerEmpty(),
1699
limit=self.chunk_size,
1700
hash=0,
1701
)
1702
)
1703
await self.start_process()
1704
1705
async def start_process(self):
1706
if self.location_check:
1707
for _t in self.target:
1708
await self.analyze_location(_t)
1709
elif self.user_check:
1710
for _t in self.target:
1711
await self.analyze_user(_t)
1712
else:
1713
for _t in self.target:
1714
if self.export:
1715
group_channel = Group_Chat_Analisys(
1716
_t,
1717
self.client,
1718
self.log_file,
1719
self.filetime,
1720
self.reply_analysis,
1721
self.forwards_check,
1722
self.comp_check,
1723
self.media_archive,
1724
self.json_check,
1725
self.translate_check,
1726
)
1727
await group_channel.f_export()
1728
else:
1729
group_channel = Group_Chat_Analisys(
1730
_t,
1731
self.client,
1732
self.log_file,
1733
self.filetime,
1734
self.reply_analysis,
1735
self.forwards_check,
1736
self.comp_check,
1737
self.media_archive,
1738
self.json_check,
1739
self.translate_check,
1740
)
1741
await group_channel.analyze_group_channel()
1742
1743
class PlaceholderClass:
1744
def __init__(self):
1745
self.d500 = 0
1746
self.d1000 = 0
1747
self.d2000 = 0
1748
self.d3000 = 0
1749
self.save_file = ""
1750
self.total = 0
1751
1752
async def analyze_location(self, _target):
1753
print(
1754
Fore.GREEN
1755
+ " [!] "
1756
+ Style.RESET_ALL
1757
+ "Searching for users near "
1758
+ _target
1759
+ "\n"
1760
)
1761
1762
latitude, longitude = map(float, _target.split(','))
1763
1764
locations_file = self.create_path(
1765
os.path.join(self.telepathy_file, self.config_p["telepathy"]["location"])
1766
)
1767
save_file = (
1768
locations_file
1769
+ f"{latitude}_{longitude}_locations_{self.filetime_clean}.csv"
1770
)
1771
1772
locations_list = []
1773
l_save_list = []
1774
1775
result = await self.client(
1776
functions.contacts.GetLocatedRequest(
1777
geo_point=types.InputGeoPoint(
1778
lat=latitude,
1779
long=longitude,
1780
accuracy_radius=42,
1781
),
1782
self_expires=42,
1783
)
1784
)
1785
1786
for user in result.updates[0].peers:
1787
try:
1788
ID = 0
1789
distance = 0
1790
if hasattr(user, "peer"):
1791
ID = user.peer.user_id
1792
1793
if hasattr(user, "distance"):
1794
distance = user.distance
1795
1796
locations_list.append([ID, distance])
1797
l_save_list.append([ID, distance, latitude, longitude, self.filetime])
1798
except:
1799
pass
1800
1801
user_df = pd.DataFrame(locations_list, columns=["User_ID", "Distance"])
1802
1803
l_save_df = pd.DataFrame(
1804
l_save_list,
1805
columns=["User_ID", "Distance", "Latitude", "Longitude", "Date_retrieved"],
1806
)
1807
1808
distance_obj = PlaceholderClass()
1809
1810
for account, distance in user_df.itertuples(index=False):
1811
account = int(account)
1812
my_user = await self.client.get_entity(types.PeerUser(account))
1813
user_id = my_user.id
1814
distance = int(distance)
1815
1816
if distance == 500:
1817
distance_obj.d500 += 1
1818
elif distance == 1000:
1819
distance_obj.d1000 += 1
1820
elif distance == 2000:
1821
distance_obj.d2000 += 1
1822
elif distance == 3000:
1823
distance_obj.d3000 += 1
1824
1825
with open(save_file, "w+", encoding="utf-8") as f:
1826
l_save_df.to_csv(f, sep=";", index=False)
1827
1828
total = len(locations_list)
1829
distance_obj.save_file = save_file
1830
distance_obj.total = total
1831
print_shell("location_report", distance_obj)
1832
1833
# Display user and channel information
1834
current_time = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+00:00")
1835
1836
for user in result.users:
1837
name = str(user.first_name) + (" " + str(user.last_name) if user.last_name else "")
1838
user_status = "Not found"
1839
last_seen = ""
1840
1841
if user.status is not None:
1842
if isinstance(user.status, types.UserStatusRecently):
1843
user_status = "Recently (within two days)"
1844
elif isinstance(user.status, types.UserStatusOffline):
1845
last_seen = user.status.was_online.strftime("%Y-%m-%dT%H:%M:%S+00:00")
1846
user_status = "Offline"
1847
elif isinstance(user.status, types.UserStatusOnline):
1848
user_status = "Online"
1849
elif isinstance(user.status, types.UserStatusLastMonth):
1850
user_status = "Between a week and a month"
1851
elif isinstance(user.status, types.UserStatusLastWeek):
1852
user_status = "Between three and seven days"
1853
elif isinstance(user.status, types.UserStatusEmpty):
1854
user_status = "Not found"
1855
1856
distance = -1
1857
for peer in result.updates[0].peers:
1858
if not isinstance(peer, types.PeerLocated):
1859
continue
1860
if peer.peer.user_id == user.id:
1861
distance = peer.distance
1862
break
1863
1864
if distance != -1:
1865
user_record = {
1866
"user_id": user.id,
1867
"name": name,
1868
"username": user.username if user.username else "",
1869
"distance": f"{distance}m",
1870
"phone_number": user.phone,
1871
"access_hash": user.access_hash,
1872
"latitude": latitude,
1873
"longitude": longitude,
1874
"retrieval_time": current_time,
1875
"last_seen": user_status,
1876
}
1877
1878
for key, value in user_record.items():
1879
print(f"{key}: {value}")
1880
print() # Blank line for readability
1881
1882
for channel in result.chats:
1883
name = channel.title
1884
date = channel.date.strftime("%Y-%m-%dT%H:%M:%S+00:00")
1885
channel_username = channel.username if channel.username else None
1886
1887
group_status = "None"
1888
if channel.restriction_reason is not None:
1889
restrictions = channel.restriction_reason
1890
ios = android = None
1891
for restriction in restrictions:
1892
if restriction.platform == 'android':
1893
android = f"restricted on Android due to {restriction.reason}"
1894
if restriction.platform == 'ios':
1895
ios = f"restricted on iOS due to {restriction.reason}"
1896
1897
if ios and android:
1898
group_status = f"{ios} and {android}"
1899
elif android:
1900
group_status = android
1901
elif ios:
1902
group_status = ios
1903
1904
coordinates = f"{latitude}, {longitude}"
1905
url = f"https://t.me/{channel_username}" if channel_username else None
1906
1907
channel_record = {
1908
"channel_id": channel.id,
1909
"name": name,
1910
"username": channel_username if channel_username else "",
1911
"access_hash": channel.access_hash,
1912
"participants_count": channel.participants_count,
1913
"restrictions": group_status,
1914
"latitude": latitude,
1915
"longitude": longitude,
1916
"url": url,
1917
"retrieval_time": current_time
1918
}
1919
1920
for key, value in channel_record.items():
1921
print(f"{key}: {value}")
1922
print() # Blank line for readability
1923
1924
async def telepangulate(self):
1925
current_set = None
1926
filename = "overlaps_"
1927
if len(self.target) > 1:
1928
group_processor = Group_Chat_Analisys(
1929
self.target[0],
1930
self.client,
1931
self.log_file,
1932
self.filetime,
1933
self.reply_analysis,
1934
self.forwards_check,
1935
self.comp_check,
1936
self.media_archive,
1937
self.json_check,
1938
self.translate_check,
1939
)
1940
(pd_members,) = group_processor.looking_for_members()
1941
filename = filename + "_" + self.target[0]
1942
for _t, i in self.target:
1943
filename = filename + "_" + _t
1944
dp_t = group_processor.looking_for_members(_t)
1945
if i == 1:
1946
current_set = pd.merge(
1947
pd_members, dp_t, how="inner", on=["User ID"]
1948
)
1949
elif i > 1:
1950
current_set = pd.merge(
1951
current_set, dp_t, how="inner", on=["User ID"]
1952
)
1953
if current_set:
1954
filename = filename + ".csv"
1955
current_set.to_csv(os.path.join(self.overlaps_dir, filename), sep=";")
1956
1957
async def analyze_user(self, _target):
1958
my_user = None
1959
self.target_type = "u"
1960
try:
1961
if "@" in _target:
1962
my_user = await self.client.get_entity(_target)
1963
else:
1964
user = int(_target)
1965
my_user = await self.client.get_entity(user)
1966
1967
user_first_name = my_user.first_name
1968
user_last_name = my_user.last_name
1969
if user_last_name is not None:
1970
user_full_name = str(user_first_name) + " " + str(user_last_name)
1971
else:
1972
user_full_name = str(user_first_name)
1973
1974
if my_user.photo is not None:
1975
user_photo = my_user.photo.photo_id
1976
else:
1977
user_photo = "None"
1978
1979
user_status = "Not found"
1980
if my_user.status is not None:
1981
if "Empty" in str(my_user.status):
1982
user_status = "Last seen over a month ago"
1983
elif "Month" in str(my_user.status):
1984
user_status = "Between a week and a month"
1985
elif "Week" in str(my_user.status):
1986
user_status = "Between three and seven days"
1987
elif "Offline" in str(my_user.status):
1988
user_status = "Offline"
1989
elif "Online" in str(my_user.status):
1990
user_status = "Online"
1991
elif "Recently" in str(my_user.status):
1992
user_status = "Recently (within two days)"
1993
else:
1994
user_status = "Not found"
1995
1996
if my_user.restriction_reason is not None:
1997
ios_restriction = my_user.restriction_reason[0]
1998
if 1 in my_user.restriction_reason:
1999
android_restriction = my_user.restriction_reason[1]
2000
user_restrictions = (
2001
str(ios_restriction) + ", " + str(android_restriction)
2002
)
2003
else:
2004
user_restrictions = str(ios_restriction)
2005
else:
2006
user_restrictions = "None"
2007
2008
setattr(my_user, "user_restrictions", str(user_restrictions))
2009
setattr(my_user, "user_full_name", str(user_full_name))
2010
setattr(my_user, "user_photo", str(user_photo))
2011
setattr(my_user, "user_status", str(user_status))
2012
setattr(my_user, "id", str(my_user.id))
2013
setattr(my_user, "target", _target)
2014
print_shell("user", my_user)
2015
2016
except ValueError as exx:
2017
pass
2018
2019
if my_user is None:
2020
print(
2021
Fore.GREEN
2022
+ " [!] "
2023
+ Style.RESET_ALL
2024
+ "User not found, this is likely because Telepathy has not encountered them yet."
2025
)
2026
2027
2028
@click.command()
2029
@click.option(
2030
"--target",
2031
"-t",
2032
multiple=True,
2033
help="Specifies a chat to investigate.",
2034
)
2035
@click.option(
2036
"--bot",
2037
"-b",
2038
multiple=True,
2039
help="BOT info, analyzing bot info, it needs API_HASH:API_ID.",
2040
)
2041
@click.option(
2042
"--comprehensive",
2043
"-c",
2044
is_flag=True,
2045
help="Comprehensive scan, includes archiving.",
2046
)
2047
@click.option(
2048
"--media", "-m", is_flag=True, help="Archives media in the specified chat."
2049
)
2050
@click.option("--forwards", "-f", is_flag=True, help="Scrapes forwarded messages.")
2051
@click.option("--user", "-u", is_flag=True, help="Looks up a specified user ID.")
2052
@click.option(
2053
"--location", "-l", is_flag=True, help="Finds users near to specified coordinates."
2054
)
2055
@click.option("--alt", "-a", default=0, help="Uses an alternative login.")
2056
@click.option("--json", "-j", is_flag=True, default=False, help="Export to JSON.")
2057
@click.option(
2058
"--export",
2059
"-e",
2060
is_flag=True,
2061
default=False,
2062
help="Export a list of chats your account is part of.",
2063
)
2064
@click.option(
2065
"--replies",
2066
"-r",
2067
is_flag=True,
2068
default=False,
2069
help="Enable replies analysis in channels.",
2070
)
2071
@click.option(
2072
"--translate",
2073
"-tr",
2074
is_flag=True,
2075
default=False,
2076
help="Enable translation of chat content.",
2077
)
2078
@click.option(
2079
"--triangulate_membership",
2080
"-tm",
2081
is_flag=True,
2082
default=False,
2083
help="Get interpolation from a list of groups",
2084
)
2085
def cli(
2086
target,
2087
comprehensive,
2088
media,
2089
forwards,
2090
user,
2091
bot,
2092
location,
2093
alt,
2094
json,
2095
export,
2096
replies,
2097
translate,
2098
triangulate_membership,
2099
):
2100
telepathy_cli = Telepathy_cli(
2101
target,
2102
comprehensive,
2103
media,
2104
forwards,
2105
user,
2106
bot,
2107
location,
2108
alt,
2109
json,
2110
export,
2111
replies,
2112
translate,
2113
triangulate_membership,
2114
)
2115
loop = asyncio.new_event_loop()
2116
asyncio.set_event_loop(loop)
2117
loop.run_until_complete(telepathy_cli.connect_tg_client_and_run())
2118
2119
2120
if __name__ == "__main__":
2121
cli()
2122
2123