Skip to content

Commit 0bb0460

Browse files
committed
Add half-cooked CrossChat(TM) command.
1 parent 5222170 commit 0bb0460

File tree

10 files changed

+378
-2
lines changed

10 files changed

+378
-2
lines changed

.github/workflows/nameless_push.yml

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ jobs:
3737
pip install --upgrade setuptools wheel
3838
pip install -U -r requirements.txt -r requirements.dev.txt
3939
40+
- name: Create Prisma client
41+
run: |
42+
prisma db push --schema nameless/prisma/schema.prisma
43+
4044
- name: Fix the code with ruff
4145
run: |
4246
ruff check --select I --fix --exit-zero .

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66

77
# Files should not not be exposed to the public
88
.env
9+
nameless.sqlite

.vscode/settings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"**/__pycache__/": true,
88
"**/.ruff_cache/": true,
99
".ruff_cache/**/*": true,
10-
".venv/**/*": true
10+
".venv/**/*": true,
11+
"nameless.sqlite": true
1112
}
1213
}

nameless/command/crossover.py

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import logging
2+
3+
import discord
4+
import discord.ui
5+
from discord import app_commands
6+
from discord.ext import commands
7+
from prisma.models import CrossChatMessage, CrossChatSubscription
8+
9+
from nameless import Nameless
10+
from nameless.custom.crud import NamelessCRUD
11+
from nameless.custom.ui import NamelessYesNoPrompt
12+
13+
__all__ = ["CrossOverCommand"]
14+
15+
16+
class CrossOverCommand(commands.GroupCog, group_name="crossover"):
17+
def __init__(self, bot: Nameless):
18+
self.bot: Nameless = bot
19+
20+
@commands.Cog.listener()
21+
async def on_message(self, message: discord.Message):
22+
assert message.guild is not None
23+
assert message.channel is not None
24+
assert self.bot.user is not None
25+
26+
if message.author.id == self.bot.user.id:
27+
return
28+
29+
if isinstance(message.channel, (discord.DMChannel, discord.PartialMessageable)):
30+
return
31+
32+
subs = await CrossChatSubscription.prisma().find_many(
33+
where={"GuildId": message.guild.id, "ChannelId": message.channel.id}
34+
)
35+
36+
for sub in subs:
37+
guild = self.bot.get_guild(sub.TargetGuildId)
38+
39+
if guild is None:
40+
return
41+
42+
channel = guild.get_channel(sub.TargetChannelId)
43+
44+
if channel is None:
45+
return
46+
47+
if isinstance(channel, (discord.ForumChannel, discord.CategoryChannel)):
48+
return
49+
50+
embed = discord.Embed(
51+
description=message.content, color=discord.Colour.orange()
52+
)
53+
54+
avatar_url = message.author.avatar.url if message.author.avatar else ""
55+
guild_icon = message.guild.icon.url if message.guild.icon else ""
56+
57+
embed.set_author(
58+
name=f"@{message.author.global_name} wrote:", icon_url=avatar_url
59+
)
60+
embed.set_footer(
61+
text=f"{message.guild.name} at #{message.channel.name}",
62+
icon_url=guild_icon,
63+
)
64+
65+
sent_message = await channel.send(
66+
embed=embed,
67+
stickers=message.stickers,
68+
files=[await x.to_file() for x in message.attachments],
69+
)
70+
71+
await CrossChatMessage.prisma().create(
72+
data={
73+
"Subscription": {"connect": {"SubscriptionId": sub.SubscriptionId}},
74+
"OriginMessageId": message.id,
75+
"ClonedMessageId": sent_message.id,
76+
}
77+
)
78+
79+
@commands.Cog.listener()
80+
async def on_message_edit(self, _: discord.Message, message: discord.Message):
81+
assert message.guild is not None
82+
assert message.channel is not None
83+
assert self.bot.user is not None
84+
85+
if message.author.id == self.bot.user.id:
86+
return
87+
88+
if isinstance(message.channel, (discord.DMChannel, discord.PartialMessageable)):
89+
return
90+
91+
subs = await CrossChatSubscription.prisma().find_many(
92+
where={
93+
"GuildId": message.guild.id,
94+
"ChannelId": message.channel.id,
95+
"Messages": {"some": {"OriginMessageId": message.id}},
96+
},
97+
include={"Messages": True},
98+
)
99+
100+
for sub in subs:
101+
guild = self.bot.get_guild(sub.TargetGuildId)
102+
103+
if guild is None:
104+
return
105+
106+
channel = guild.get_channel(sub.TargetChannelId)
107+
108+
if channel is None:
109+
return
110+
111+
if isinstance(channel, (discord.ForumChannel, discord.CategoryChannel)):
112+
return
113+
114+
assert sub.Messages is not None
115+
116+
the_true_id: int = [
117+
x.ClonedMessageId
118+
for x in sub.Messages
119+
if x.OriginMessageId == message.id
120+
][0]
121+
122+
the_message: discord.Message = await channel.fetch_message(the_true_id)
123+
the_embed = the_message.embeds[0]
124+
the_embed.description = message.content
125+
126+
await the_message.edit(embed=the_embed)
127+
128+
@commands.Cog.listener()
129+
async def on_message_delete(self, message: discord.Message):
130+
assert message.guild is not None
131+
assert message.channel is not None
132+
assert self.bot.user is not None
133+
134+
if message.author.id == self.bot.user.id:
135+
return
136+
137+
if isinstance(message.channel, (discord.DMChannel, discord.PartialMessageable)):
138+
return
139+
140+
subs = await CrossChatSubscription.prisma().find_many(
141+
where={
142+
"GuildId": message.guild.id,
143+
"ChannelId": message.channel.id,
144+
"Messages": {"some": {"OriginMessageId": message.id}},
145+
},
146+
include={"Messages": True},
147+
)
148+
149+
for sub in subs:
150+
guild = self.bot.get_guild(sub.TargetGuildId)
151+
152+
if guild is None:
153+
return
154+
155+
channel = guild.get_channel(sub.TargetChannelId)
156+
157+
if channel is None:
158+
return
159+
160+
if isinstance(channel, (discord.ForumChannel, discord.CategoryChannel)):
161+
return
162+
163+
assert sub.Messages is not None
164+
165+
the_true_id: int = [
166+
x.ClonedMessageId
167+
for x in sub.Messages
168+
if x.OriginMessageId == message.id
169+
][0]
170+
171+
the_message: discord.Message = await channel.fetch_message(the_true_id)
172+
await the_message.delete()
173+
174+
@app_commands.command()
175+
@app_commands.guild_only()
176+
@app_commands.describe(
177+
target_guild="Target guild ID to establish.",
178+
target_channel="Target channel to establish.",
179+
)
180+
async def create_link(
181+
self,
182+
interaction: discord.Interaction[Nameless],
183+
target_guild: str,
184+
target_channel: str,
185+
):
186+
"""Create link to another guild."""
187+
await interaction.response.defer()
188+
189+
guild = self.bot.get_guild(int(target_guild))
190+
191+
if guild is None:
192+
await interaction.followup.send("That guild does not exist.")
193+
return
194+
195+
channel = guild.get_channel(int(target_channel))
196+
197+
if channel is None:
198+
await interaction.followup.send("That channel does not exist.")
199+
return
200+
201+
if isinstance(channel, (discord.ForumChannel, discord.CategoryChannel)):
202+
await interaction.followup.send("Invalid channel to link to.")
203+
return
204+
205+
assert interaction.guild is not None
206+
assert interaction.channel is not None
207+
208+
temp_data: (
209+
CrossChatSubscription | None
210+
) = await CrossChatSubscription.prisma().find_first(
211+
where={
212+
"ChannelId": interaction.channel.id,
213+
"TargetGuildId": guild.id,
214+
"TargetChannelId": channel.id,
215+
}
216+
)
217+
218+
if temp_data is not None:
219+
await interaction.followup.send("You had a link with this place before.")
220+
return
221+
222+
await interaction.followup.send("Sending out request, please wait.")
223+
224+
prompt = NamelessYesNoPrompt()
225+
226+
await channel.send("You have an incoming link!", view=prompt)
227+
await prompt.wait()
228+
229+
if not prompt.is_a_yes:
230+
await interaction.followup.send("Response declined.")
231+
return
232+
233+
await NamelessCRUD.get_or_create_guild_entry(interaction.guild)
234+
await NamelessCRUD.get_or_create_guild_entry(guild)
235+
236+
await CrossChatSubscription.prisma().create(
237+
data={
238+
"Guild": {"connect": {"Id": interaction.guild.id}},
239+
"ChannelId": interaction.channel.id,
240+
"TargetGuildId": guild.id,
241+
"TargetChannelId": channel.id,
242+
}
243+
)
244+
245+
await CrossChatSubscription.prisma().create(
246+
data={
247+
"Guild": {"connect": {"Id": guild.id}},
248+
"ChannelId": channel.id,
249+
"TargetGuildId": interaction.guild.id,
250+
"TargetChannelId": interaction.channel.id,
251+
}
252+
)
253+
254+
await interaction.followup.send("Linking success!")
255+
256+
257+
async def setup(bot: Nameless):
258+
await bot.add_cog(CrossOverCommand(bot))
259+
logging.info("%s added!", __name__)
260+
261+
262+
async def teardown(bot: Nameless):
263+
await bot.remove_cog(CrossOverCommand.__cog_name__)
264+
logging.warning("%s removed!", __name__)

nameless/custom/crud.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import discord
2+
from prisma import Prisma, models
3+
from prisma.errors import RecordNotFoundError
4+
5+
__all__ = ["NamelessCRUD"]
6+
7+
raw_db: Prisma = Prisma(auto_register=True)
8+
9+
10+
class NamelessCRUD:
11+
"""A repository class to connect to database."""
12+
13+
@staticmethod
14+
async def init() -> None:
15+
await raw_db.connect()
16+
17+
@staticmethod
18+
async def get_or_create_guild_entry(
19+
guild: discord.Guild, *, include_cross_chat: bool = False
20+
) -> models.Guild:
21+
try:
22+
return await raw_db.guild.find_first_or_raise(
23+
where={"Id": guild.id},
24+
include={"CrossChat": include_cross_chat},
25+
)
26+
except RecordNotFoundError:
27+
return await raw_db.guild.create(
28+
data={"Id": guild.id}, include={"CrossChat": include_cross_chat}
29+
)

nameless/custom/ui/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .yes_no import *

nameless/custom/ui/yes_no.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from typing import Self, override
2+
3+
import discord
4+
5+
from nameless import Nameless
6+
7+
__all__ = ["NamelessYesNoPrompt"]
8+
9+
10+
class NamelessYesNoPrompt(discord.ui.View):
11+
"""A simple Yes/No prompt."""
12+
13+
def __init__(self, timeout: int = 15) -> None:
14+
super().__init__(timeout=timeout)
15+
self.is_a_yes: bool = False
16+
17+
@discord.ui.button(label="Yep!", style=discord.ButtonStyle.green)
18+
async def confirm(
19+
self, interaction: discord.Interaction[Nameless], _btn: discord.ui.Button[Self]
20+
) -> None:
21+
self.is_a_yes = True
22+
await interaction.followup.send("Response received!")
23+
self.stop()
24+
25+
@discord.ui.button(label="Nope!", style=discord.ButtonStyle.red)
26+
async def cancel(
27+
self, interaction: discord.Interaction[Nameless], _btn: discord.ui.Button[Self]
28+
) -> None:
29+
await interaction.followup.send("Response received!")
30+
self.stop()
31+
32+
@override
33+
async def interaction_check(self, interaction: discord.Interaction) -> bool:
34+
await interaction.response.defer()
35+
return await super().interaction_check(interaction)

nameless/nameless.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ def __init__(
3636
self.description: str = nameless_config["nameless"]["description"]
3737

3838
@override
39-
async def setup_hook(self) -> None:
39+
async def setup_hook(self):
40+
logging.info("Connecting to database.")
41+
await NamelessCRUD.init()
42+
4043
logging.info("Registering commands.")
4144
await self._register_commands()
4245

0 commit comments

Comments
 (0)