Coverage for kryptic_cypher/bot.py: 32%

91 statements  

« prev     ^ index     » next       coverage.py v7.14.2, created at 2026-06-20 18:52 +0000

1import discord 

2from discord import app_commands 

3from discord.ext import commands 

4 

5from pydantic_settings import BaseSettings 

6 

7from kryptic_cypher.cypher.base import Cypher, CypherWithKey 

8 

9from .cypher import registered_cyphers 

10 

11intents = discord.Intents.default() 

12intents.message_content = True 

13 

14bot = commands.Bot(command_prefix="!", intents=intents) 

15 

16 

17class DiscordBotConfig(BaseSettings): 

18 DISCORD_BOT_TOKEN: str 

19 

20 

21@bot.event 

22async def on_ready(): 

23 print(f"Logged in as {bot.user.name}") 

24 try: 

25 # Syncs the tree to Discord so commands appear in the client 

26 synced = await bot.tree.sync() 

27 print(f"Synced {len(synced)} command(s)") 

28 except Exception as e: 

29 print(f"Failed to sync commands: {e}") 

30 

31 

32@bot.tree.command(name="ping", description="Replies with a pong!") 

33async def ping(interaction: discord.Interaction): 

34 # Use interaction.response.send_message instead of ctx.send 

35 await interaction.response.send_message("🏓 Pong!") 

36 

37 

38class EncodeModal(discord.ui.Modal, title="Encode Message"): 

39 message = discord.ui.TextInput( 

40 label="Message", 

41 placeholder="Message to encode", 

42 required=True, 

43 ) 

44 key = discord.ui.TextInput(label="Key", placeholder="Key to use", required=False) 

45 cypher = discord.ui.TextInput( 

46 label="Cypher", 

47 placeholder="Cypher to use", 

48 required=True, 

49 ) 

50 

51 def __init__(self): 

52 super().__init__() 

53 

54 async def on_submit(self, interaction: discord.Interaction): 

55 cypher = self.cypher.value 

56 message = self.message.value 

57 key = self.key.value 

58 

59 if cypher not in registered_cyphers: 

60 await interaction.response.send_message( 

61 f"Cypher is unsupported currently... {cypher} please use {', '.join(registered_cyphers.keys())}", 

62 ephemeral=True, 

63 ) 

64 return 

65 

66 cypher_instance = registered_cyphers[cypher] 

67 if isinstance(cypher_instance, CypherWithKey): 

68 if not key: 

69 await interaction.response.send_message( 

70 "You must specify a key for this cypher", 

71 ephemeral=True, 

72 ) 

73 return 

74 key_validation = cypher_instance.validate_key(key) 

75 if not key_validation.success: 

76 await interaction.response.send_message( 

77 "\n".join(key_validation.messages), 

78 ephemeral=True, 

79 ) 

80 return 

81 response = cypher_instance.encode(message, key) 

82 elif isinstance(cypher_instance, Cypher): 

83 response = cypher_instance.encode(message) 

84 else: 

85 await interaction.response.send_message( 

86 f"Cypher is unsupported currently... {cypher}", 

87 ephemeral=True, 

88 ) 

89 return 

90 if not response.success: 

91 await interaction.response.send_message( 

92 f"Failed to encode message: {response.error}", 

93 ephemeral=True, 

94 ) 

95 else: 

96 await interaction.response.send_message(response.new_text) 

97 

98 

99@bot.tree.command( 

100 name="encode", 

101 description="Writes a message to the discord channel encoded by the specified cypher", 

102) 

103async def encode( 

104 interaction: discord.Interaction, 

105): 

106 await interaction.response.send_modal(EncodeModal()) 

107 

108 

109@bot.tree.command( 

110 name="decode", 

111 description="Allows you to see an encoded message by decoding it with the specified cypher...", 

112) 

113@app_commands.describe( 

114 cypher="The specific cypher to use for encoding", 

115 message_id="Link to message to decode", 

116 message="The message to decode", 

117 key="The key to use for encoding", 

118 display="Whether or not to display the decoded message", 

119) 

120@app_commands.choices( 

121 cypher=[ 

122 app_commands.Choice( 

123 name=cypher, 

124 value=cypher, 

125 ) 

126 for cypher in registered_cyphers.keys() 

127 ] 

128) 

129async def decode( 

130 interaction: discord.Interaction, 

131 cypher: str, 

132 message_id: str = None, 

133 message: str = None, 

134 key: str = None, 

135 display: bool = False, 

136): 

137 if not message and not message_id: 

138 await interaction.response.send_message( 

139 "You must specify a message to decode either by message_id or message text", 

140 ephemeral=True, 

141 ) 

142 return 

143 cypher_instance = registered_cyphers[cypher] 

144 

145 if message_id: 

146 if "://discord.com" in message_id: 

147 message_id = int(message_id.split("/")[-1]) 

148 elif message_id.isnumeric(): 

149 message_id = int(message_id) 

150 else: 

151 await interaction.response.send_message( 

152 "Invalid message_id, please either paste the link to the message or the numeric id", 

153 ephemeral=True, 

154 ) 

155 return 

156 

157 discord_message = await interaction.channel.fetch_message(message_id) 

158 message = discord_message.content 

159 

160 if isinstance(cypher_instance, CypherWithKey): 

161 if not key: 

162 await interaction.response.send_message( 

163 "You must specify a key for this cypher", 

164 ephemeral=True, 

165 ) 

166 key_validation = cypher_instance.validate_key(key) 

167 if not key_validation.success: 

168 await interaction.response.send_message( 

169 "\n".join(key_validation.messages), 

170 ephemeral=True, 

171 ) 

172 response = cypher_instance.decode(message, key) 

173 elif isinstance(cypher_instance, Cypher): 

174 response = cypher_instance.decode(message) 

175 else: 

176 await interaction.response.send_message( 

177 f"Cypher is unsupported currently... {cypher}", 

178 ephemeral=True, 

179 ) 

180 return 

181 

182 if not response.success: 

183 await interaction.response.send_message( 

184 f"Failed to decode message: {response.error}", 

185 ephemeral=True, 

186 ) 

187 else: 

188 await interaction.response.send_message( 

189 response.new_text, ephemeral=not display 

190 ) 

191 

192 

193def run( 

194 env_file: str = None, 

195): 

196 config = DiscordBotConfig( 

197 _env_file=env_file, 

198 ) 

199 bot.run(config.DISCORD_BOT_TOKEN) 

200 

201 

202if __name__ == "__main__": 

203 run()