Skip to content

Commit f73c704

Browse files
committed
Add token scanning capability
1 parent 29e822f commit f73c704

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

config.template.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ level = 20
1818

1919
[SNEKBOX] # optional
2020
url = 'http://snekbox:8060' # default url
21+
22+
[GITHUB] # optional
23+
bot_secret = ""

modules/moderation.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
import base64
5+
import binascii
6+
import re
7+
from typing import TYPE_CHECKING, Any
8+
9+
import discord
10+
import yarl
11+
from discord.ext import commands
12+
13+
import core
14+
15+
16+
if TYPE_CHECKING:
17+
from bot import Bot
18+
19+
TOKEN_RE = re.compile(r"[a-zA-Z0-9_-]{23,28}\.[a-zA-Z0-9_-]{6,7}\.[a-zA-Z0-9_-]{27}")
20+
21+
22+
def validate_token(token: str) -> bool:
23+
try:
24+
# Just check if the first part validates as a user ID
25+
(user_id, _, _) = token.split(".")
26+
user_id = int(base64.b64decode(user_id + "==", validate=True))
27+
except (ValueError, binascii.Error):
28+
return False
29+
else:
30+
return True
31+
32+
33+
class GithubError(commands.CommandError):
34+
pass
35+
36+
37+
class Moderation(commands.Cog):
38+
def __init__(self, bot: Bot, /) -> None:
39+
self.bot: Bot = bot
40+
self._req_lock = asyncio.Lock()
41+
42+
async def github_request(
43+
self,
44+
method: str,
45+
url: str,
46+
*,
47+
params: dict[str, Any] | None = None,
48+
data: dict[str, Any] | None = None,
49+
headers: dict[str, Any] | None = None,
50+
) -> Any:
51+
api_key = core.CONFIG["TOKENS"].get("github_bot")
52+
if not api_key:
53+
return
54+
55+
hdrs = {
56+
"Accept": "application/vnd.github.inertia-preview+json",
57+
"User-Agent": "RoboDanny DPYExclusive Cog",
58+
"Authorization": f"token {api_key}",
59+
}
60+
61+
req_url = yarl.URL("https://api.github.com") / url
62+
63+
if headers is not None:
64+
hdrs.update(headers)
65+
66+
async with self._req_lock:
67+
async with self.bot.session.request(method, req_url, params=params, json=data, headers=hdrs) as r:
68+
remaining = r.headers.get("X-Ratelimit-Remaining")
69+
js = await r.json()
70+
if r.status == 429 or remaining == "0":
71+
# wait before we release the lock
72+
delta = discord.utils._parse_ratelimit_header(r) # type: ignore # shh this is okay
73+
await asyncio.sleep(delta)
74+
self._req_lock.release()
75+
return await self.github_request(method, url, params=params, data=data, headers=headers)
76+
elif 300 > r.status >= 200:
77+
return js
78+
else:
79+
raise GithubError(js["message"])
80+
81+
async def create_gist(
82+
self,
83+
content: str,
84+
*,
85+
description: str | None = None,
86+
filename: str | None = None,
87+
public: bool = True,
88+
) -> str:
89+
headers = {
90+
"Accept": "application/vnd.github.v3+json",
91+
}
92+
93+
filename = filename or "output.txt"
94+
data: dict[str, Any] = {
95+
"public": public,
96+
"files": {
97+
filename: {
98+
"content": content,
99+
}
100+
},
101+
}
102+
103+
if description:
104+
data["description"] = description
105+
106+
js = await self.github_request("POST", "gists", data=data, headers=headers)
107+
return js["html_url"]
108+
109+
@commands.Cog.listener("on_message")
110+
async def find_discord_tokens(self, message: discord.Message) -> None:
111+
tokens: list[str] = [token for token in TOKEN_RE.findall(message.content) if validate_token(token)]
112+
113+
if not tokens:
114+
return
115+
116+
url = await self.create_gist(
117+
"\n".join(tokens), filename="tokens.txt", description="Tokens found within the Pythonista guild."
118+
)
119+
120+
await message.reply(
121+
f"Hey {message.author.mention}, I found one or more Discord Bot tokens in your message and I've sent them off to be invalidated for you.\n"
122+
f"You can find the token(s) [here]({url})."
123+
)
124+
125+
126+
async def setup(bot: Bot) -> None:
127+
await bot.add_cog(Moderation(bot))

types_/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Tokens(TypedDict):
88
bot: str
99
idevision: str
1010
mystbin: str
11+
github_bot: str
1112

1213

1314
class Database(TypedDict):

0 commit comments

Comments
 (0)