Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jordanwildon
GitHub Repository: jordanwildon/Telepathy
Path: blob/main/build/lib/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
15
from telepathy.utils import (
16
print_banner,
17
color_print_green,
18
populate_user,
19
process_message,
20
process_description,
21
parse_tg_date,
22
parse_html_page,
23
print_shell,
24
createPlaceholdeCls
25
)
26
27
from telethon.errors import SessionPasswordNeededError, ChannelPrivateError
28
from telethon.tl.types import (
29
InputPeerEmpty,
30
PeerUser,
31
User,
32
PeerChat,
33
PeerChannel,
34
PeerLocated,
35
ChannelParticipantCreator,
36
ChannelParticipantAdmin,
37
)
38
from telethon.tl.functions.messages import GetDialogsRequest
39
from telethon import TelegramClient, functions, types, utils
40
from telethon.utils import get_display_name, get_message_id
41
from alive_progress import alive_bar
42
from colorama import Fore, Style
43
44
@click.command()
45
@click.option(
46
"--target",
47
"-t",
48
#default="",
49
multiple = True,
50
help = "Specifies a chat to investigate.",
51
)
52
@click.option(
53
"--comprehensive",
54
"-c",
55
is_flag = True,
56
help = "Comprehensive scan, includes archiving.",
57
)
58
@click.option(
59
"--media",
60
"-m",
61
is_flag = True,
62
help = "Archives media in the specified chat."
63
)
64
@click.option(
65
"--forwards",
66
"-f",
67
is_flag = True,
68
help = "Scrapes forwarded messages."
69
)
70
@click.option(
71
"--user",
72
"-u",
73
is_flag = True,
74
help = "Looks up a specified user ID."
75
)
76
@click.option(
77
"--location",
78
"-l",
79
is_flag = True,
80
help = "Finds users near to specified coordinates."
81
)
82
@click.option(
83
"--alt",
84
"-a",
85
default = 0,
86
help = "Uses an alternative login."
87
)
88
@click.option(
89
"--json",
90
"-j",
91
is_flag = True,
92
default = False,
93
help = "Export to JSON."
94
)
95
@click.option(
96
"--export",
97
"-e",
98
is_flag = True,
99
default = False,
100
help = "Export a list of chats your account is part of.",
101
)
102
@click.option(
103
"--replies",
104
"-r",
105
is_flag = True,
106
default = False,
107
help = "Enable replies analysis in channels.",
108
)
109
@click.option(
110
"--translate",
111
"-tr",
112
is_flag = True,
113
default = False,
114
help = "Enable translation of chat content.",
115
)
116
117
def cli(
118
target,
119
comprehensive,
120
media,
121
forwards,
122
user,
123
location,
124
alt,
125
json,
126
export,
127
replies,
128
translate
129
):
130
131
print_banner()
132
133
# Defining default values
134
user_check = location_check = False
135
basic = True if target else False
136
reply_analysis = True if replies else False
137
forwards_check = True if forwards else False
138
comp_check = True if comprehensive else False
139
media_archive = True if media else False
140
json_check = True if json else False
141
translate_check = True if translate else False
142
last_date, chunk_size, user_language = None, 1000, 'en'
143
144
if user:
145
user_check, basic = True, False
146
if location:
147
location_check, basic = True, False
148
if export:
149
t = " "
150
151
filetime = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M")
152
filetime_clean = str(filetime)
153
154
# Defining file values
155
telepathy_file = "./telepathy_files/"
156
json_file = telepathy_file + "json_files/"
157
login = telepathy_file + "login.txt"
158
log_file = telepathy_file + "log.csv"
159
export_file = telepathy_file + "export.csv"
160
161
# Creating core data file
162
if not os.path.exists(telepathy_file):
163
os.makedirs(telepathy_file)
164
165
'''Start of API details'''
166
167
def login_function():
168
api_id = input("Please enter your API ID:\n")
169
api_hash = input("Please enter your API Hash:\n")
170
phone_number = input("Please enter your phone number:\n")
171
return api_id, api_hash, phone_number
172
173
if os.path.isfile(login) == False:
174
api_id, api_hash, phone_number = login_function()
175
with open(login, "w+", encoding="utf-8") as f:
176
f.write(api_id + "," + api_hash + "," + phone_number + "\n")
177
else:
178
with open(login, encoding="utf-8") as file:
179
content = file.readlines()
180
if alt == 0:
181
details = content[0]
182
api_id, api_hash, phone_number = details.split(sep=",")
183
elif alt == 1:
184
try:
185
if content[1]:
186
details = content[1]
187
api_id, api_hash, phone_number = details.split(sep=",")
188
except:
189
print("Setting up alt 1: ")
190
api_id, api_hash, phone_number = login_function()
191
with open(login, "a+", encoding="utf-8") as file:
192
file.write(api_id + "," + api_hash + "," + phone_number + "\n")
193
elif alt == 2:
194
try:
195
if content[2]:
196
details = content[2]
197
api_id, api_hash, phone_number = details.split(sep=",")
198
except:
199
print("Setting up alt 2: ")
200
api_id, api_hash, phone_number = login_function()
201
with open(login, "a+", encoding="utf-8") as file:
202
file.write(api_id + "," + api_hash + "," + phone_number + "\n")
203
elif alt == 3:
204
try:
205
if content[3]:
206
details = content[3]
207
api_id, api_hash, phone_number = details.split(sep=",")
208
except:
209
print("Setting up alt 3: ")
210
api_id, api_hash, phone_number = login_function()
211
with open(login, "a+", encoding="utf-8") as file:
212
file.write(api_id + "," + api_hash + "," + phone_number + "\n")
213
elif alt == 4:
214
try:
215
if content[4]:
216
details = content[4]
217
api_id, api_hash, phone_number = details.split(sep=",")
218
except:
219
print("Setting up alt 4: ")
220
api_id, api_hash, phone_number = login_function()
221
with open(login, "a+", encoding="utf-8") as file:
222
file.write(api_id + "," + api_hash + "," + phone_number + "\n")
223
224
'''End of API details'''
225
226
client = TelegramClient(phone_number, api_id, api_hash)
227
228
async def main():
229
230
await client.connect()
231
232
if not await client.is_user_authorized():
233
await client.send_code_request(phone_number)
234
try:
235
await client.sign_in(
236
phone = phone_number,
237
code=input("Enter code: "),
238
)
239
except SessionPasswordNeededError:
240
await client.sign_in(
241
password=getpass.getpass(
242
prompt="Password: ",
243
stream=None,
244
)
245
)
246
247
result = client(
248
GetDialogsRequest(
249
offset_date=last_date,
250
offset_id=0,
251
offset_peer=InputPeerEmpty(),
252
limit=chunk_size,
253
hash=0,
254
)
255
)
256
257
else:
258
if export == True:
259
exports = []
260
print("Exporting...")
261
262
for Dialog in await client.get_dialogs():
263
try:
264
if Dialog.entity.username:
265
group_url = "http://t.me/" + Dialog.entity.username
266
group_username = Dialog.entity.username
267
268
web_req = parse_html_page(group_url)
269
group_description = web_req["group_description"]
270
total_participants = web_req["total_participants"]
271
272
if translate_check == True:
273
_desc = process_description(
274
group_description, user_language
275
)
276
translated_description = _desc["translated_text"]
277
else:
278
translated_description = "N/A"
279
280
if Dialog.entity.broadcast is True:
281
chat_type = "Channel"
282
elif Dialog.entity.megagroup is True:
283
chat_type = "Megagroup"
284
elif Dialog.entity.gigagroup is True:
285
chat_type = "Gigagroup"
286
else:
287
chat_type = "Chat"
288
289
if Dialog.entity.restriction_reason is not None:
290
ios_restriction = Dialog.entity.restriction_reason[
291
0
292
]
293
if 1 in Dialog.entity.restriction_reason:
294
android_restriction = (
295
Dialog.entity.restriction_reason[1]
296
)
297
group_status = (
298
str(ios_restriction)
299
+ ", "
300
+ str(android_restriction)
301
)
302
else:
303
group_status = str(ios_restriction)
304
else:
305
group_status = "None"
306
307
exports.append(
308
[
309
filetime,
310
Dialog.entity.title,
311
group_description,
312
translated_description,
313
total_participants,
314
group_username,
315
group_url,
316
chat_type,
317
Dialog.entity.id,
318
Dialog.entity.access_hash,
319
group_status,
320
]
321
)
322
323
export_df = pd.DataFrame(
324
exports,
325
columns=[
326
"Access Date",
327
"Title",
328
"Description",
329
"Translated description",
330
"Total participants",
331
"Username",
332
"URL",
333
"Chat type",
334
"Chat ID",
335
"Access hash",
336
"Restrictions",
337
],
338
)
339
340
if not os.path.isfile(export_file):
341
export_df.to_csv(
342
export_file,
343
sep=";",
344
index=False,
345
)
346
else:
347
export_df.to_csv(
348
export_file,
349
sep=";",
350
mode="w",
351
index=False,
352
)
353
354
except AttributeError:
355
pass
356
357
else:
358
for t in target:
359
alphanumeric = ""
360
for character in t:
361
if character.isalnum():
362
alphanumeric += character
363
364
if "https://t.me/+" in t:
365
t = t.replace('https://t.me/+', 'https://t.me/joinchat/')
366
367
if basic == True or comp_check == True:
368
save_directory = telepathy_file + alphanumeric
369
if not os.path.exists(save_directory):
370
os.makedirs(save_directory)
371
372
if media_archive:
373
media_directory = save_directory + "/media"
374
if not os.path.exists(media_directory):
375
os.makedirs(media_directory)
376
377
if basic == True and comp_check == False:
378
color_print_green(" [!] ", "Performing basic scan")
379
elif comp_check == True:
380
color_print_green(" [!] ", "Performing comprehensive scan")
381
382
file_archive = (
383
save_directory
384
+ "/"
385
+ alphanumeric
386
+ "_"
387
+ filetime_clean
388
+ "_archive.csv"
389
)
390
391
reply_file_archive = (
392
save_directory
393
+ "/"
394
+ alphanumeric
395
+ "_"
396
+ filetime_clean
397
+ "_reply_archive.csv"
398
)
399
400
if forwards_check == True:
401
color_print_green(" [!] ", "Forwards will be fetched")
402
file_forwards = (
403
save_directory
404
+ "/edgelists/"
405
+ alphanumeric
406
+ "_"
407
+ filetime_clean
408
+ "_edgelist.csv"
409
)
410
411
forward_directory = save_directory + "/edgelists/"
412
if not os.path.exists(forward_directory):
413
os.makedirs(forward_directory)
414
415
edgelist_file = (
416
forward_directory
417
+ "/"
418
+ alphanumeric
419
+ "_edgelist.csv"
420
)
421
422
if basic is True or comp_check is True:
423
424
color_print_green(" [-] ", "Fetching details for " + t + "...")
425
426
memberlist_directory = save_directory + "/memberlists"
427
if not os.path.exists(memberlist_directory):
428
os.makedirs(memberlist_directory)
429
430
memberlist_filename = (
431
memberlist_directory
432
+ "/"
433
+ alphanumeric
434
+ "_members.csv"
435
)
436
437
reply_memberlist_filename = (
438
memberlist_directory
439
+ "/"
440
+ alphanumeric
441
+ "_active_members.csv"
442
)
443
444
entity = await client.get_entity(t)
445
446
first_post = "Not found"
447
448
async for message in client.iter_messages(t, reverse=True):
449
datepost = parse_tg_date(message.date)
450
date = datepost["date"]
451
mtime = datepost["mtime"]
452
first_post = datepost["timestamp"]
453
break
454
455
if entity.username:
456
group_url = "http://t.me/" + entity.username
457
group_username = entity.username
458
web_req = parse_html_page(group_url)
459
elif "https://t.me/" in t:
460
group_url = t
461
web_req = parse_html_page(group_url)
462
group_username = "Private group"
463
else:
464
group_url, group_username = "Private group", "Private group"
465
466
group_description = web_req["group_description"]
467
total_participants = web_req["total_participants"]
468
469
if translate_check == True:
470
_desc = process_description(
471
group_description, user_language
472
)
473
474
original_language = _desc[
475
"original_language"
476
]
477
translated_description = _desc["translated_text"]
478
else:
479
translated_description = "N/A"
480
481
group_description = ('"' + group_description + '"')
482
483
if(entity.__class__ == User):
484
color_print_green(" [!] ", "You can't search for users using flag -c, run Telepathy using the flag -u.")
485
exit(1)
486
487
if entity.broadcast is True:
488
chat_type = "Channel"
489
elif entity.megagroup is True:
490
chat_type = "Megagroup"
491
elif entity.gigagroup is True:
492
chat_type = "Gigagroup"
493
else:
494
chat_type = "Chat"
495
496
if entity.restriction_reason is not None:
497
ios_restriction = entity.restriction_reason[0]
498
if 1 in entity.restriction_reason:
499
android_restriction = entity.restriction_reason[1]
500
group_status = (
501
str(ios_restriction)
502
+ ", "
503
+ str(android_restriction)
504
)
505
else:
506
group_status = str(ios_restriction)
507
else:
508
group_status = "None"
509
510
found_participants, found_percentage = 0, 0
511
512
if chat_type != "Channel":
513
members = []
514
members_df = None
515
all_participants = await client.get_participants(t, limit=5000)
516
517
for user in all_participants:
518
members_df = pd.DataFrame(
519
members,
520
columns=[
521
"Username",
522
"Full name",
523
"User ID",
524
"Phone number",
525
"Group name",
526
],
527
)
528
members.append(populate_user(user, t))
529
530
if members_df is not None:
531
with open(
532
memberlist_filename, "w+", encoding="utf-8"
533
) as save_members:
534
members_df.to_csv(save_members, sep=";")
535
536
if json_check == True:
537
if not os.path.exists(json_file):
538
os.makedirs(json_file)
539
540
members_df.to_json(
541
json_file + alphanumeric + "_memberlist.json",
542
orient="records",
543
compression="infer",
544
lines=True,
545
index=True,
546
)
547
548
found_participants = len(all_participants)
549
found_participants = int(found_participants)
550
found_percentage = (
551
int(found_participants) / int(total_participants) * 100
552
)
553
554
log = []
555
556
if chat_type != "Channel":
557
print("\n")
558
color_print_green(" [+] Memberlist fetched", "")
559
560
setattr(entity, "group_description", group_description)
561
setattr(entity, "group_status", group_status)
562
setattr(entity, "group_username", group_username)
563
setattr(entity, "first_post", first_post)
564
setattr(entity, "group_url", group_url)
565
setattr(entity, "chat_type", chat_type)
566
setattr(entity, "translated_description", translated_description)
567
setattr(entity, "total_participants", total_participants)
568
569
if chat_type != "Channel":
570
setattr(entity, "found_participants", found_participants)
571
setattr(entity, "found_percentage", found_percentage)
572
setattr(entity, "memberlist_filename", memberlist_filename)
573
else:
574
setattr(entity, "found_participants", found_participants)
575
print_flag = "group_recap"
576
577
if chat_type == "Channel":
578
print_flag = "channel_recap"
579
580
print_shell(print_flag, entity)
581
582
log.append(
583
[
584
filetime,
585
entity.title,
586
group_description,
587
translated_description,
588
total_participants,
589
found_participants,
590
group_username,
591
group_url,
592
chat_type,
593
entity.id,
594
entity.access_hash,
595
str(entity.scam),
596
date,
597
mtime,
598
group_status,
599
]
600
)
601
602
log_df = pd.DataFrame(
603
log,
604
columns=[
605
"Access Date",
606
"Title",
607
"Description",
608
"Translated description",
609
"Total participants",
610
"Participants found",
611
"Username",
612
"URL",
613
"Chat type",
614
"Chat ID",
615
"Access hash",
616
"Scam",
617
"First post date",
618
"First post time (UTC)",
619
"Restrictions",
620
],
621
)
622
623
if not os.path.isfile(log_file):
624
log_df.to_csv(log_file, sep=";", index=False)
625
else:
626
log_df.to_csv(
627
log_file, sep=";", mode="a", index=False, header=False
628
)
629
630
if forwards_check is True and comp_check is False:
631
color_print_green(
632
" [-] ", "Calculating number of forwarded messages..."
633
)
634
forwards_list = []
635
forward_count = 0
636
private_count = 0
637
to_ent = await client.get_entity(t)
638
to_title = to_ent.title
639
640
forwards_df = pd.DataFrame(
641
forwards_list,
642
columns=[
643
"Source",
644
"Target",
645
"Label",
646
"Source_ID",
647
"Username",
648
"Timestamp",
649
],
650
)
651
652
async for message in client.iter_messages(t):
653
if message.forward is not None:
654
forward_count += 1
655
656
color_print_green(" [-] ", "Fetching forwarded messages...")
657
658
progress_bar = (
659
Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
660
)
661
662
with alive_bar(
663
forward_count, dual_line=True, title=progress_bar, length=20
664
) as bar:
665
666
async for message in client.iter_messages(t):
667
if message.forward is not None:
668
try:
669
f_from_id = message.forward.original_fwd.from_id
670
if f_from_id is not None:
671
ent = await client.get_entity(f_from_id)
672
username = ent.username
673
timestamp = parse_tg_date(message.date)[
674
"timestamp"
675
]
676
677
substring = "PeerUser"
678
string = str(f_from_id)
679
if chat_type != "Channel":
680
if substring in string:
681
user_id = re.sub("[^0-9]", "", string)
682
user_id = await client.get_entity(
683
PeerUser(int(user_id))
684
)
685
user_id = str(user_id)
686
result = (
687
"User: "
688
+ str(ent.first_name)
689
+ " / ID: "
690
+ str(user_id.id)
691
)
692
else:
693
result = str(ent.title)
694
else:
695
result = str(ent.title)
696
697
forwards_df = pd.DataFrame(
698
forwards_list,
699
columns=[
700
"Source",
701
"Target",
702
"Label",
703
"Source_ID",
704
"Username",
705
"Timestamp",
706
],
707
)
708
709
forwards_list.append(
710
[
711
result,
712
t,
713
to_title,
714
f_from_id,
715
username,
716
timestamp,
717
]
718
)
719
720
except Exception as e:
721
if e is ChannelPrivateError:
722
print("Private channel")
723
continue
724
725
time.sleep(0.5)
726
bar()
727
728
with open(
729
edgelist_file, "w+", encoding="utf-8"
730
) as save_forwards:
731
forwards_df.to_csv(save_forwards, sep=";")
732
733
if json_check == True:
734
forwards_df.to_json(
735
json_file + alphanumeric + "_edgelist.json",
736
orient="records",
737
compression="infer",
738
lines=True,
739
index=True,
740
)
741
742
if forward_count >= 15:
743
forwards_found = forwards_df.Source.count()
744
value_count = forwards_df["Source"].value_counts()
745
df01 = value_count.rename_axis("unique_values").reset_index(
746
name="counts"
747
)
748
749
report_forward = createPlaceholdeCls()
750
report_forward.forward_one = (
751
str(df01.iloc[0]["unique_values"])
752
+ ", "
753
+ str(df01.iloc[0]["counts"])
754
+ " forwarded messages"
755
)
756
report_forward.forward_two = (
757
str(df01.iloc[1]["unique_values"])
758
+ ", "
759
+ str(df01.iloc[1]["counts"])
760
+ " forwarded messages"
761
)
762
report_forward.forward_three = (
763
str(df01.iloc[2]["unique_values"])
764
+ ", "
765
+ str(df01.iloc[2]["counts"])
766
+ " forwarded messages"
767
)
768
report_forward.forward_four = (
769
str(df01.iloc[3]["unique_values"])
770
+ ", "
771
+ str(df01.iloc[3]["counts"])
772
+ " forwarded messages"
773
)
774
report_forward.forward_five = (
775
str(df01.iloc[4]["unique_values"])
776
+ ", "
777
+ str(df01.iloc[4]["counts"])
778
+ " forwarded messages"
779
)
780
781
df02 = forwards_df.Source.unique()
782
report_forward.unique_forwards = len(df02)
783
report_forward.edgelist_file = edgelist_file
784
print_shell("forwarder_stat",report_forward)
785
else:
786
print(
787
"\n"
788
+ Fore.GREEN
789
+ " [!] Insufficient forwarded messages found"
790
+ Style.RESET_ALL
791
)
792
793
else:
794
if comp_check is True:
795
796
messages = client.iter_messages(t)
797
798
message_list = []
799
forwards_list = []
800
801
replies_list = []
802
user_replier_list = []
803
804
forward_count, private_count, message_count = 0, 0, 0
805
806
if media_archive is True:
807
files = []
808
print("\n")
809
color_print_green(
810
" [!] ", "Media content will be archived"
811
)
812
813
color_print_green(
814
" [!] ", "Calculating number of messages..."
815
)
816
817
async for message in messages:
818
if message is not None:
819
message_count += 1
820
821
print("\n")
822
color_print_green(" [-] ", "Fetching message archive...")
823
progress_bar = (
824
Fore.GREEN + " [-] " + Style.RESET_ALL + "Progress: "
825
)
826
827
with alive_bar(
828
message_count,
829
dual_line=True,
830
title=progress_bar,
831
length=20,
832
) as bar:
833
834
to_ent = await client.get_entity(t)
835
836
async for message in client.iter_messages(
837
t, limit=None
838
):
839
if message is not None:
840
try:
841
c_archive = pd.DataFrame(
842
message_list,
843
columns=[
844
"To",
845
"Message ID",
846
"Display_name",
847
"User ID",
848
"Message_text",
849
"Original_language",
850
"Translated_text",
851
"Translation_confidence",
852
"Timestamp",
853
"Has_media",
854
"Reply_to_ID",
855
"Replies",
856
"Forwards",
857
"Views",
858
"Total_reactions",
859
"Reply_ER_reach",
860
"Reply_ER_impressions",
861
"Forwards_ER_reach",
862
"Forwards_ER_impressions",
863
"Reaction_ER_reach",
864
"Reactions_ER_impressions",
865
"Thumbs_up",
866
"Thumbs_down",
867
"Heart",
868
"Fire",
869
"Smile_with_hearts",
870
"Clap",
871
"Smile",
872
"Thinking",
873
"Exploding_head",
874
"Scream",
875
"Angry",
876
"Single_tear",
877
"Party",
878
"Starstruck",
879
"Vomit",
880
"Poop",
881
"Pray",
882
"Edit_date",
883
"URL",
884
"Media save directory"
885
],
886
)
887
888
c_forwards = pd.DataFrame(
889
forwards_list,
890
columns=[
891
"Source",
892
"Target",
893
"Label",
894
"Source_ID",
895
"Username",
896
"Timestamp",
897
],
898
)
899
900
#if message.reactions:
901
# if message.reactions.can_see_list:
902
# c_reactioneer = pd.DataFrame(
903
# user_reaction_list,
904
# columns=[
905
# "Username",
906
# "Full name",
907
# "User ID",
908
# "Phone number",
909
# "Group name",
910
# ],
911
# )
912
913
if (
914
message.replies
915
and reply_analysis
916
and chat_type == "Channel"
917
):
918
if message.replies.replies > 0:
919
c_repliers = pd.DataFrame(
920
user_replier_list,
921
columns=[
922
"Username",
923
"Full name",
924
"User ID",
925
"Phone number",
926
"Group name",
927
],
928
)
929
930
c_replies = pd.DataFrame(
931
replies_list,
932
columns=[
933
"To",
934
"Message ID",
935
"Reply ID",
936
"Display_name",
937
"ID",
938
"Message_text",
939
"Original_language",
940
"Translated_text",
941
"Translation_confidence",
942
"Timestamp",
943
],
944
)
945
946
if message.replies:
947
if message.replies.replies > 0:
948
async for repl in client.iter_messages(
949
message.chat_id,
950
reply_to=message.id,
951
):
952
user = await client.get_entity(
953
repl.from_id.user_id
954
)
955
userdet = populate_user(user, t)
956
user_replier_list.append(
957
userdet
958
)
959
960
if translate_check == True:
961
mss_txt = process_message(
962
repl.text, user_language
963
)
964
original_language = mss_txt["original_language"],
965
translated_text = mss_txt["translated_text"],
966
translation_confidence = mss_txt["translation_confidence"],
967
reply_text = mss_txt["message_text"]
968
else:
969
original_language = "N/A"
970
translated_text = "N/A"
971
translation_confidence = "N/A"
972
reply_text = repl.text
973
974
replies_list.append(
975
[
976
t,
977
message.id,
978
repl.id,
979
userdet[1],
980
userdet[2],
981
reply_text,
982
original_language,
983
translated_text,
984
translation_confidence,
985
parse_tg_date(
986
repl.date
987
)["timestamp"],
988
]
989
)
990
991
display_name = get_display_name(
992
message.sender
993
)
994
if chat_type != "Channel":
995
substring = "PeerUser"
996
string = str(message.from_id)
997
if substring in string:
998
user_id = re.sub(
999
"[^0-9]", "", string
1000
)
1001
nameID = str(user_id)
1002
else:
1003
nameID = str(message.from_id)
1004
else:
1005
nameID = to_ent.id
1006
1007
timestamp = parse_tg_date(message.date)[
1008
"timestamp"
1009
]
1010
reply = message.reply_to_msg_id
1011
1012
if translate_check == True:
1013
_mess = process_message(
1014
message.text, user_language
1015
)
1016
message_text = _mess["message_text"]
1017
original_language = _mess[
1018
"original_language"
1019
]
1020
translated_text = _mess["translated_text"]
1021
translation_confidence = _mess[
1022
"translation_confidence"
1023
]
1024
else:
1025
message_text = message.text
1026
original_language = "N/A"
1027
translated_text = "N/A"
1028
translation_confidence = "N/A"
1029
1030
if message.forwards is not None:
1031
forwards = int(message.forwards)
1032
else:
1033
forwards = "N/A"
1034
1035
if message.views is not None:
1036
views = int(message.views)
1037
else:
1038
views = 'N/A'
1039
1040
if message.reactions:
1041
reactions = message.reactions.results
1042
total_reactions = 0
1043
i = range(len(reactions))
1044
1045
for idx, i in enumerate(reactions):
1046
total_reactions = total_reactions + i.count
1047
thumbs_up = i.count if i.reaction == '👍' else 0
1048
thumbs_down = i.count if i.reaction == '👎' else 0
1049
heart = i.count if i.reaction == '❤️' else 0
1050
fire = i.count if i.reaction == '🔥' else 0
1051
smile_with_hearts = i.count if i.reaction == '🥰' else 0
1052
clap = i.count if i.reaction == '👏' else 0
1053
smile = i.count if i.reaction == '😁' else 0
1054
thinking = i.count if i.reaction == '🤔' else 0
1055
exploding_head = i.count if i.reaction == '🤯' else 0
1056
scream = i.count if i.reaction == '😱' else 0
1057
angry = i.count if i.reaction == '🤬' else 0
1058
single_tear = i.count if i.reaction == '😢' else 0
1059
party_popper = i.count if i.reaction == '🎉' else 0
1060
starstruck = i.count if i.reaction == '🤩' else 0
1061
vomiting = i.count if i.reaction == '🤮' else 0
1062
poop = i.count if i.reaction == '💩' else 0
1063
praying = i.count if i.reaction == '🙏' else 0
1064
else:
1065
total_reactions = 'N/A'
1066
thumbs_up = 'N/A'
1067
thumbs_down = 'N/A'
1068
heart = 'N/A'
1069
fire = 'N/A'
1070
smile_with_hearts = 'N/A'
1071
clap = 'N/A'
1072
smile = 'N/A'
1073
thinking = 'N/A'
1074
exploding_head = 'N/A'
1075
scream = 'N/A'
1076
angry = 'N/A'
1077
single_tear = 'N/A'
1078
party_popper = 'N/A'
1079
starstruck = 'N/A'
1080
vomiting = 'N/A'
1081
poop = 'N/A'
1082
praying = 'N/A'
1083
1084
if media_archive == True:
1085
if message.media is not None:
1086
path = await message.download_media(
1087
file = media_directory
1088
)
1089
files.append(path)
1090
media_file = path
1091
else:
1092
media_file = "N/A"
1093
else:
1094
media_file = "N/A"
1095
1096
if message.media is not None:
1097
has_media = "TRUE"
1098
else:
1099
has_media = "FALSE"
1100
1101
if message.replies:
1102
reply_count = int(message.replies.replies)
1103
else:
1104
reply_count = "N/A"
1105
1106
if message.edit_date:
1107
edit_date = str(message.edit_date)
1108
else:
1109
edit_date = "None"
1110
1111
'''Need to find a way to calculate these in case these figures don't exist to make it
1112
comparable across channels for a total engagement number (e.g. if replies/reactions are off).
1113
If not N/A would cover if it's off, zero if it's none. Working on some better logic here.'''
1114
1115
if reply_count != 'N/A' and total_participants is not None:
1116
reply_reach_ER = (reply_count / int(total_participants)) * 100
1117
else:
1118
reply_reach_ER = 'N/A'
1119
1120
if reply_count != 'N/A' and views != 'N/A':
1121
reply_impressions_ER = (reply_count / int(views)) * 100
1122
else:
1123
reply_impressions_ER = 'N/A'
1124
1125
if forwards != 'N/A' and total_participants is not None:
1126
forwards_reach_ER = (forwards / int(total_participants)) * 100
1127
else:
1128
forwards_reach_ER = 'N/A'
1129
1130
if forwards != 'N/A' and views != 'N/A':
1131
forwards_impressions_ER = (forwards / int(views)) * 100
1132
else:
1133
forwards_impressions_ER = 'N/A'
1134
1135
if total_reactions != 'N/A' and total_participants is not None:
1136
reactions_reach_ER = (total_reactions / int(total_participants)) * 100
1137
else:
1138
reactions_reach_ER = 'N/A'
1139
1140
if total_reactions != 'N/A' and views != 'N/A':
1141
reactions_impressions_ER = (total_reactions / int(views)) * 100
1142
else:
1143
reactions_impressions_ER = 'N/A'
1144
1145
post_url = "https://t.me/s/" + t + "/" + str(message.id)
1146
1147
message_list.append(
1148
[
1149
t,
1150
message.id,
1151
display_name,
1152
nameID,
1153
message_text,
1154
original_language,
1155
translated_text,
1156
translation_confidence,
1157
timestamp,
1158
has_media,
1159
reply,
1160
reply_count,
1161
forwards,
1162
views,
1163
total_reactions,
1164
reply_reach_ER,
1165
reply_impressions_ER,
1166
forwards_reach_ER,
1167
forwards_impressions_ER,
1168
reactions_reach_ER,
1169
reactions_impressions_ER,
1170
thumbs_up,
1171
thumbs_down,
1172
heart,
1173
fire,
1174
smile_with_hearts,
1175
clap,
1176
smile,
1177
thinking,
1178
exploding_head,
1179
scream,
1180
angry,
1181
single_tear,
1182
party_popper,
1183
starstruck,
1184
vomiting,
1185
poop,
1186
praying,
1187
edit_date,
1188
post_url,
1189
media_file,
1190
]
1191
)
1192
1193
if message.forward is not None:
1194
try:
1195
forward_count += 1
1196
to_title = to_ent.title
1197
f_from_id = (
1198
message.forward.original_fwd.from_id
1199
)
1200
1201
if f_from_id is not None:
1202
ent = await client.get_entity(
1203
f_from_id
1204
)
1205
1206
user_string = "user_id"
1207
channel_string = "broadcast"
1208
1209
if user_string in str(ent):
1210
ent_type = "User"
1211
else:
1212
if channel_string in str(
1213
ent
1214
):
1215
if (
1216
ent.broadcast
1217
is True
1218
):
1219
ent_type = (
1220
"Channel"
1221
)
1222
elif (
1223
ent.megagroup
1224
is True
1225
):
1226
ent_type = (
1227
"Megagroup"
1228
)
1229
elif (
1230
ent.gigagroup
1231
is True
1232
):
1233
ent_type = (
1234
"Gigagroup"
1235
)
1236
else:
1237
ent_type = "Chat"
1238
else:
1239
continue
1240
1241
if ent.username is not None:
1242
username = ent.username
1243
else:
1244
username = "none"
1245
1246
if ent_type != "Chat":
1247
result = str(ent.title)
1248
else:
1249
result = "none"
1250
1251
if ent_type == "User":
1252
substring_1 = "PeerUser"
1253
string_1 = str(ent.user_id)
1254
if substring_1 in string_1:
1255
user_id = re.sub(
1256
"[^0-9]",
1257
"",
1258
string_1,
1259
)
1260
user_id = await client.get_entity(
1261
PeerUser(
1262
int(user_id)
1263
)
1264
)
1265
user_id = str(user_id)
1266
result = (
1267
"User: "
1268
+ str(
1269
ent.first_name
1270
)
1271
+ " / ID: "
1272
+ str(user_id)
1273
)
1274
else:
1275
result = str(ent.title)
1276
else:
1277
result = str(ent.title)
1278
1279
forwards_list.append(
1280
[
1281
result,
1282
t,
1283
to_title,
1284
f_from_id,
1285
username,
1286
timestamp,
1287
]
1288
)
1289
1290
except ChannelPrivateError:
1291
private_count += 1
1292
continue
1293
1294
except Exception as e:
1295
print("An exception occurred.", e)
1296
continue
1297
1298
except Exception as e:
1299
print("An exception occurred.", e)
1300
1301
else:
1302
message_list.append(
1303
[
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
"None",
1335
"None",
1336
"None",
1337
"None",
1338
"None",
1339
"None",
1340
"None",
1341
"None",
1342
"None",
1343
"None",
1344
]
1345
)
1346
1347
time.sleep(0.5)
1348
bar()
1349
1350
if reply_analysis is True:
1351
if len(replies_list) > 0:
1352
with open(
1353
reply_file_archive, "w+", encoding="utf-8"
1354
) as rep_file:
1355
c_replies.to_csv(rep_file, sep=";")
1356
1357
if len(user_replier_list) > 0:
1358
with open(
1359
reply_memberlist_filename, "w+", encoding="utf-8"
1360
) as repliers_file:
1361
c_repliers.to_csv(repliers_file, sep=";")
1362
1363
with open(
1364
file_archive, "w+", encoding="utf-8"
1365
) as archive_file:
1366
c_archive.to_csv(archive_file, sep=";")
1367
1368
if json_check == True:
1369
c_archive.to_json(
1370
json_file
1371
+ alphanumeric
1372
+ "_archive.json",
1373
orient="records",
1374
compression="infer",
1375
lines=True,
1376
index=True,
1377
)
1378
1379
if forwards_check is True:
1380
with open(
1381
file_forwards, "w+", encoding="utf-8"
1382
) as forwards_file:
1383
c_forwards.to_csv(forwards_file, sep=";")
1384
1385
if json_check == True:
1386
c_forwards.to_json(
1387
json_file
1388
+ alphanumeric
1389
+ "_edgelist.json",
1390
orient="records",
1391
compression="infer",
1392
lines=True,
1393
index=True,
1394
)
1395
1396
messages_found = int(c_archive.To.count()) - 1
1397
report_obj = createPlaceholdeCls()
1398
report_obj.messages_found = messages_found
1399
report_obj.file_archive = file_archive
1400
1401
if chat_type == "Channel":
1402
print_shell("channel_stat", report_obj)
1403
else:
1404
pvalue_count = c_archive["Display_name"].value_counts()
1405
df03 = pvalue_count.rename_axis(
1406
"unique_values"
1407
).reset_index(name="counts")
1408
1409
'''
1410
message_frequency_count = {}
1411
message_text = {}
1412
word_count = {}
1413
most_used_words = {}
1414
most_used_words_filtered = {}
1415
'''
1416
#message stats, top words
1417
1418
report_obj.poster_one = (
1419
str(df03.iloc[0]["unique_values"])
1420
+ ", "
1421
+ str(df03.iloc[0]["counts"])
1422
+ " messages"
1423
)
1424
report_obj.poster_two = (
1425
str(df03.iloc[1]["unique_values"])
1426
+ ", "
1427
+ str(df03.iloc[1]["counts"])
1428
+ " messages"
1429
)
1430
report_obj.poster_three = (
1431
str(df03.iloc[2]["unique_values"])
1432
+ ", "
1433
+ str(df03.iloc[2]["counts"])
1434
+ " messages"
1435
)
1436
report_obj.poster_four = (
1437
str(df03.iloc[3]["unique_values"])
1438
+ ", "
1439
+ str(df03.iloc[3]["counts"])
1440
+ " messages"
1441
)
1442
report_obj.poster_four = (
1443
str(df03.iloc[4]["unique_values"])
1444
+ ", "
1445
+ str(df03.iloc[4]["counts"])
1446
+ " messages"
1447
)
1448
1449
df04 = c_archive.Display_name.unique()
1450
unique_active = len(df04)
1451
report_obj.unique_active = unique_active
1452
print_shell("group_stat", report_obj)
1453
1454
if reply_analysis is True:
1455
if len(replies_list) > 0:
1456
replier_value_count = c_repliers["User ID"].value_counts()
1457
replier_df = replier_value_count.rename_axis(
1458
"unique_values"
1459
).reset_index(name="counts")
1460
1461
repliers = createPlaceholdeCls()
1462
repliers.replier_one = (
1463
str(replier_df.iloc[0]["unique_values"])
1464
+ ", "
1465
+ str(replier_df.iloc[0]["counts"])
1466
+ " replies"
1467
)
1468
repliers.replier_two = (
1469
str(replier_df.iloc[1]["unique_values"])
1470
+ ", "
1471
+ str(replier_df.iloc[1]["counts"])
1472
+ " replies"
1473
)
1474
repliers.replier_three = (
1475
str(replier_df.iloc[2]["unique_values"])
1476
+ ", "
1477
+ str(replier_df.iloc[2]["counts"])
1478
+ " replies"
1479
)
1480
repliers.replier_four = (
1481
str(replier_df.iloc[3]["unique_values"])
1482
+ ", "
1483
+ str(replier_df.iloc[3]["counts"])
1484
+ " replies"
1485
)
1486
repliers.replier_five = (
1487
str(replier_df.iloc[4]["unique_values"])
1488
+ ", "
1489
+ str(replier_df.iloc[4]["counts"])
1490
+ " replies"
1491
)
1492
1493
replier_count_df = c_repliers["User ID"].unique()
1494
replier_unique = len(replier_count_df)
1495
repliers.user_replier_list_len = len(user_replier_list)
1496
repliers.reply_file_archive = str(reply_file_archive)
1497
repliers.reply_memberlist_filename = str(reply_memberlist_filename)
1498
repliers.replier_unique = str(replier_unique)
1499
print_shell("reply_stat", repliers)
1500
1501
if forwards_check is True:
1502
if forward_count >= 15:
1503
forwards_found = c_forwards.Source.count()
1504
value_count = c_forwards["Source"].value_counts()
1505
c_f_stats = value_count.rename_axis(
1506
"unique_values"
1507
).reset_index(name="counts")
1508
1509
report_forward = createPlaceholdeCls()
1510
report_forward.forward_one = (
1511
str(c_f_stats.iloc[0]["unique_values"])
1512
+ ", "
1513
+ str(c_f_stats.iloc[0]["counts"])
1514
+ " forwarded messages"
1515
)
1516
report_forward.forward_two = (
1517
str(c_f_stats.iloc[1]["unique_values"])
1518
+ ", "
1519
+ str(c_f_stats.iloc[1]["counts"])
1520
+ " forwarded messages"
1521
)
1522
report_forward.forward_three = (
1523
str(c_f_stats.iloc[2]["unique_values"])
1524
+ ", "
1525
+ str(c_f_stats.iloc[2]["counts"])
1526
+ " forwarded messages"
1527
)
1528
report_forward.forward_four = (
1529
str(c_f_stats.iloc[3]["unique_values"])
1530
+ ", "
1531
+ str(c_f_stats.iloc[3]["counts"])
1532
+ " forwarded messages"
1533
)
1534
report_forward.forward_five = (
1535
str(c_f_stats.iloc[4]["unique_values"])
1536
+ ", "
1537
+ str(c_f_stats.iloc[4]["counts"])
1538
+ " forwarded messages"
1539
)
1540
1541
c_f_unique = c_forwards.Source.unique()
1542
1543
report_forward.unique_forwards = len(c_f_unique)
1544
report_forward.edgelist_file = edgelist_file
1545
report_forward.private_count = private_count
1546
print_shell("forwarder_stat", report_forward)
1547
1548
else:
1549
color_print_green(
1550
" [!] Insufficient forwarded messages found",
1551
edgelist_file,
1552
)
1553
1554
if user_check == True:
1555
my_user = None
1556
try:
1557
if "@" in t:
1558
my_user = await client.get_entity(t)
1559
else:
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)
1568
+ " "
1569
+ str(user_last_name)
1570
)
1571
else:
1572
user_full_name = str(user_first_name)
1573
1574
if my_user.photo is not None:
1575
user_photo = my_user.photo.photo_id
1576
else:
1577
user_photo = "None"
1578
1579
if my_user.status is not None:
1580
if "Empty" in str(my_user.status):
1581
user_status = "Last seen over a month ago"
1582
elif "Month" in str(my_user.status):
1583
user_status = "Between a week and a month"
1584
elif "Week" in str(my_user.status):
1585
user_status = "Between three and seven days"
1586
elif "Offline" in str(my_user.status):
1587
user_status = "Offline"
1588
elif "Online" in str(my_user.status):
1589
user_status = "Online"
1590
elif "Recently" in str(my_user.status):
1591
user_status = "Recently (within two days)"
1592
else:
1593
user_status = "Not found"
1594
1595
if my_user.restriction_reason is not None:
1596
ios_restriction = entity.restriction_reason[0]
1597
if 1 in entity.restriction_reason:
1598
android_restriction = entity.restriction_reason[1]
1599
user_restrictions = (
1600
str(ios_restriction)
1601
+ ", "
1602
+ str(android_restriction)
1603
)
1604
else:
1605
user_restrictions = str(ios_restriction)
1606
else:
1607
user_restrictions = "None"
1608
1609
setattr(my_user, "user_restrictions", str(user_restrictions))
1610
setattr(my_user, "user_full_name", str(user_full_name))
1611
setattr(my_user, "user_photo", str(user_photo))
1612
setattr(my_user, "user_status", str(user_status))
1613
setattr(my_user, "target", t)
1614
print_shell("user", my_user)
1615
1616
except ValueError:
1617
pass
1618
1619
if my_user is None:
1620
print(
1621
Fore.GREEN
1622
+ " [!] "
1623
+ Style.RESET_ALL
1624
+ "User not found, this is likely because Telepathy has not encountered them yet."
1625
)
1626
1627
if location_check == True:
1628
1629
print(
1630
Fore.GREEN
1631
+ " [!] "
1632
+ Style.RESET_ALL
1633
+ "Searching for users near "
1634
+ t
1635
+ "\n"
1636
)
1637
1638
latitude, longitude = t.split(sep=",")
1639
1640
locations_file = telepathy_file + "locations/"
1641
if not os.path.exists(locations_file):
1642
os.makedirs(locations_file)
1643
1644
save_file = (
1645
locations_file
1646
+ latitude
1647
+ "_"
1648
+ longitude
1649
+ "_"
1650
+ "locations_"
1651
+ filetime_clean
1652
+ ".csv"
1653
)
1654
1655
locations_list = []
1656
l_save_list = []
1657
1658
result = await client(
1659
functions.contacts.GetLocatedRequest(
1660
geo_point=types.InputGeoPoint(
1661
lat=float(latitude),
1662
long=float(longitude),
1663
accuracy_radius=42,
1664
),
1665
self_expires=42,
1666
)
1667
)
1668
1669
for user in result.updates[0].peers:
1670
try:
1671
user_df = pd.DataFrame(
1672
locations_list, columns=[
1673
"User_ID",
1674
"Distance"]
1675
)
1676
1677
l_save_df = pd.DataFrame(
1678
l_save_list, columns=[
1679
"User_ID",
1680
"Distance",
1681
"Latitude",
1682
"Longitude",
1683
"Date_retrieved"
1684
]
1685
)
1686
1687
if hasattr(user, "peer"):
1688
ID = user.peer.user_id
1689
1690
if hasattr(user, "distance"):
1691
distance = user.distance
1692
1693
locations_list.append([ID, distance])
1694
l_save_list.append(
1695
[
1696
ID,
1697
distance,
1698
latitude,
1699
longitude,
1700
filetime
1701
]
1702
)
1703
except:
1704
pass
1705
1706
distance_obj = createPlaceholdeCls()
1707
distance_obj.d500 = 0
1708
distance_obj.d1000 = 0
1709
distance_obj.d2000 = 0
1710
distance_obj.d3000 = 0
1711
1712
for account, distance in user_df.itertuples(index=False):
1713
account = int(account)
1714
my_user = await client.get_entity(PeerUser(account))
1715
user_id = my_user.id
1716
distance = int(distance)
1717
1718
if distance == 500:
1719
distance_obj.d500 += 1
1720
elif distance == 1000:
1721
distance_obj.d1000 += 1
1722
elif distance == 2000:
1723
distance_obj.d2000 += 1
1724
elif distance == 3000:
1725
distance_obj.d3000 += 1
1726
1727
1728
with open(save_file, "w+", encoding="utf-8") as f:
1729
l_save_df.to_csv(f, sep=";", index=False)
1730
1731
total = len(locations_list)
1732
1733
distance_obj.save_file = save_file
1734
distance_obj.total = total
1735
print_shell("location_report",distance_obj)
1736
# can also do the same for channels with similar output file to users
1737
# may one day add trilateration to find users closest to exact point
1738
1739
with client:
1740
client.loop.run_until_complete(main())
1741
1742
if __name__ == "__main__":
1743
cli()
1744