Просмотр исходного кода

Adding Kickstarter cog, most of support for both email and username based attribution

pull/35/head
Rocketsoup 2 недель назад
Родитель
Сommit
f721914df7
6 измененных файлов: 1388 добавлений и 2 удалений
  1. 1
    1
      requirements.txt
  2. 3
    0
      rocketbot/bot.py
  3. 6
    1
      rocketbot/cogs/basecog.py
  4. 1339
    0
      rocketbot/cogs/kickstartercog.py
  5. 10
    0
      rocketbot/utils.py
  6. 29
    0
      sql/kickstarter-create.sql

+ 1
- 1
requirements.txt Просмотреть файл

1
-discord.py == 2.6.4
1
+discord.py == 2.7.1
2
 yt-dlp == 2026.07.04
2
 yt-dlp == 2026.07.04

+ 3
- 0
rocketbot/bot.py Просмотреть файл

99
 	from rocketbot.cogs.generalcog import GeneralCog
99
 	from rocketbot.cogs.generalcog import GeneralCog
100
 	from rocketbot.cogs.helpcog import HelpCog
100
 	from rocketbot.cogs.helpcog import HelpCog
101
 	from rocketbot.cogs.joinraidcog import JoinRaidCog
101
 	from rocketbot.cogs.joinraidcog import JoinRaidCog
102
+	from rocketbot.cogs.kickstartercog import KickstarterCog
102
 	from rocketbot.cogs.logcog import LoggingCog
103
 	from rocketbot.cogs.logcog import LoggingCog
103
 	from rocketbot.cogs.patterncog import PatternCog
104
 	from rocketbot.cogs.patterncog import PatternCog
104
 	from rocketbot.cogs.urlspamcog import URLSpamCog
105
 	from rocketbot.cogs.urlspamcog import URLSpamCog
134
 		await rocketbot.add_cog(UsernamePatternCog(rocketbot))
135
 		await rocketbot.add_cog(UsernamePatternCog(rocketbot))
135
 	if VideoPreviewCog.supports_intents(rocketbot.intents):
136
 	if VideoPreviewCog.supports_intents(rocketbot.intents):
136
 		await rocketbot.add_cog(VideoPreviewCog(rocketbot))
137
 		await rocketbot.add_cog(VideoPreviewCog(rocketbot))
138
+	if KickstarterCog.supports_intents(rocketbot.intents):
139
+		await rocketbot.add_cog(KickstarterCog(rocketbot))
137
 
140
 
138
 	await rocketbot.start(CONFIG['client_token'], reconnect=True)
141
 	await rocketbot.start(CONFIG['client_token'], reconnect=True)
139
 	print('\nBot aborted')
142
 	print('\nBot aborted')

+ 6
- 1
rocketbot/cogs/basecog.py Просмотреть файл

67
 	async def cog_app_command_error(self, interaction: Interaction, error: AppCommandError) -> None:
67
 	async def cog_app_command_error(self, interaction: Interaction, error: AppCommandError) -> None:
68
 		if isinstance(error, CommandInvokeError):
68
 		if isinstance(error, CommandInvokeError):
69
 			error = error.original
69
 			error = error.original
70
+		bot_log(interaction.guild, type(self), f"Error in {interaction.command.qualified_name if interaction.command else 'unknown command'}")
70
 		dump_stacktrace(error)
71
 		dump_stacktrace(error)
71
 		message = f"\nException: {error.__class__.__name__}, "\
72
 		message = f"\nException: {error.__class__.__name__}, "\
72
 				  f"Command: {interaction.command.qualified_name if interaction.command else None}, "\
73
 				  f"Command: {interaction.command.qualified_name if interaction.command else None}, "\
75
 		try:
76
 		try:
76
 			await interaction.response.send_message(f"An error occurred: {message}", ephemeral=True)
77
 			await interaction.response.send_message(f"An error occurred: {message}", ephemeral=True)
77
 		except discord.InteractionResponded:
78
 		except discord.InteractionResponded:
78
-			await interaction.followup.send(f"An error occurred: {message}", ephemeral=True)
79
+			try:
80
+				await interaction.followup.send(f"An error occurred: {message}", ephemeral=True)
81
+			except BaseException:
82
+				interaction.channel.send
83
+				bot_log(interaction.guild, None, message)
79
 
84
 
80
 	@property
85
 	@property
81
 	def basecogs(self) -> list['BaseCog']:
86
 	def basecogs(self) -> list['BaseCog']:

+ 1339
- 0
rocketbot/cogs/kickstartercog.py
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 10
- 0
rocketbot/utils.py Просмотреть файл

135
 __MENTION_REGEX: re.Pattern = re.compile('^<@[!&]([0-9]{17,20})>$')
135
 __MENTION_REGEX: re.Pattern = re.compile('^<@[!&]([0-9]{17,20})>$')
136
 __USER_MENTION_REGEX: re.Pattern = re.compile('^<@!([0-9]{17,20})>$')
136
 __USER_MENTION_REGEX: re.Pattern = re.compile('^<@!([0-9]{17,20})>$')
137
 __ROLE_MENTION_REGEX: re.Pattern = re.compile('^<@&([0-9]{17,20})>$')
137
 __ROLE_MENTION_REGEX: re.Pattern = re.compile('^<@&([0-9]{17,20})>$')
138
+__EMAIL_REGEX: re.Pattern = re.compile(r'^(?:(?:[^<>()\[\]\\.,;:\s@"]+(?:\.[^<>()\[\]\\.,;:\s@"]+)*)|(?:".+"))@(?:(?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(?:(?:[a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
139
+__USERNAME_REGEX: re.Pattern = re.compile(r'[a-z0-9\._]{2,32}')
138
 
140
 
139
 def is_user_id(val: str) -> bool:
141
 def is_user_id(val: str) -> bool:
140
 	"""Tests if a string is in user/role ID format."""
142
 	"""Tests if a string is in user/role ID format."""
152
 	"""Tests if a string is a user mention."""
154
 	"""Tests if a string is a user mention."""
153
 	return __USER_MENTION_REGEX.match(val) is not None
155
 	return __USER_MENTION_REGEX.match(val) is not None
154
 
156
 
157
+def is_email_address(val: str) -> bool:
158
+	"""Tests if a string is a well-formed email address."""
159
+	return __EMAIL_REGEX.match(val) is not None
160
+
161
+def is_discord_username(val: str) -> bool:
162
+	"""Tests if a string is a properly formatted Discord username."""
163
+	return __USERNAME_REGEX.match(val.lower())
164
+
155
 def user_id_from_mention(mention: str) -> str:
165
 def user_id_from_mention(mention: str) -> str:
156
 	"""Extracts the user ID from a mention. Raises a ValueError if malformed."""
166
 	"""Extracts the user ID from a mention. Raises a ValueError if malformed."""
157
 	m = __USER_MENTION_REGEX.match(mention)
167
 	m = __USER_MENTION_REGEX.match(mention)

+ 29
- 0
sql/kickstarter-create.sql Просмотреть файл

1
+CREATE TABLE IF NOT EXISTS kickstarter_emails (
2
+	pk INTEGER PRIMARY KEY AUTOINCREMENT,
3
+	guild_id INTEGER NOT NULL,  -- Discord Guild.id
4
+	email_hash TEXT NOT NULL,  -- base64(sha256(email.lower.trimmed))
5
+	imported_at INTEGER NOT NULL,  -- unix timestamp
6
+	UNIQUE(guild_id, email_hash) ON CONFLICT FAIL
7
+);
8
+
9
+CREATE TABLE IF NOT EXISTS kickstarter_discord_users (
10
+	pk INTEGER PRIMARY KEY AUTOINCREMENT,
11
+	guild_id INTEGER NOT NULL,  -- Discord Guild.id
12
+	discord_username TEXT NOT NULL,  -- Discord Member.name
13
+	discord_member_id INTEGER DEFAULT NULL,  -- resolved member id
14
+	lookup_status INTEGER NOT NULL DEFAULT 0,  -- enum defined in code
15
+	imported_at INTEGER NOT NULL,  -- unix timestamp
16
+	UNIQUE(guild_id, discord_username COLLATE NOCASE) ON CONFLICT FAIL
17
+);
18
+CREATE INDEX IF NOT EXISTS idx_kickstarter_discord_users_discord_member_id
19
+	ON kickstarter_discord_users (discord_member_id);
20
+
21
+CREATE TABLE IF NOT EXISTS member_links (
22
+	pk INTEGER PRIMARY KEY AUTOINCREMENT,
23
+	guild_id INTEGER NOT NULL,  -- Discord Guild.id
24
+	member_id INTEGER NOT NULL,  -- Discord Member.id
25
+	email_hash TEXT NOT NULL,  -- base64(sha256(email.lower.trimmed))
26
+	complete INTEGER NOT NULL DEFAULT 0,  -- link found, role assigned successfully
27
+	UNIQUE(guild_id, member_id) ON CONFLICT FAIL,
28
+	UNIQUE(guild_id, email_hash) ON CONFLICT FAIL
29
+);

Загрузка…
Отмена
Сохранить