Skip to content

Commit ceca93e

Browse files
committed
Add bridge pending status and platform restart
1 parent 5af60dc commit ceca93e

11 files changed

Lines changed: 864 additions & 299 deletions

File tree

shinobu/__main__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@
3434
from shinobu.cli import secrets as secrets_cli, installer as installer_cli
3535
from shinobu.runtime.secrets.encryptor import EncryptedData
3636

37-
# Manifest path
38-
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.json")
39-
4037
# Prevent attacks via import
4138
if __name__ != "__main__":
4239
raise RuntimeError("Bootscript should not be imported!")
4340

41+
# Manifest path
42+
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.json")
43+
4444
# Create argument parser
4545
parser = argparse.ArgumentParser(
4646
prog="shinobu",

shinobu/beacon/cogs/config.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
Shinobu - Converse from anywhere, anytime.
3+
Copyright (C) 2026-present Green (@greeeen-dev)
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or (at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
"""
18+
19+
import discord
20+
from discord.ext import commands, bridge
21+
from shinobu.runtime.models import shinobu_cog
22+
from shinobu.beacon.protocol import beacon
23+
24+
class BeaconConfig(shinobu_cog.ShinobuCog):
25+
def __init__(self, bot):
26+
# Register cog metadata
27+
super().__init__(
28+
bot,
29+
shinobu_metadata=shinobu_cog.ShinobuCogMetadata(
30+
name="Config",
31+
description="A module containing configuration commands.",
32+
emoji="\U0001F4E1",
33+
visible_in_help=True
34+
)
35+
)
36+
37+
# Get Beacon
38+
self._beacon: beacon.Beacon = self.bot.shared_objects.get("beacon")
39+
40+
@bridge.bridge_group(name="config")
41+
async def config_universal(self, ctx):
42+
# Universal command group.
43+
pass
44+
45+
def get_cog_type():
46+
return BeaconConfig
47+
48+
def setup(bot):
49+
bot.add_cog(BeaconConfig(bot))

shinobu/beacon/discord/driver.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ async def _edit(self, message: beacon_message.BeaconMessage, content: beacon_mes
751751

752752
async def _delete(self, message: beacon_message.BeaconMessage):
753753
channel = self.bot.get_channel(int(message.channel.id))
754+
partial_message: discord.PartialMessage = discord.PartialMessage(channel=channel, id=int(message.id))
754755

755756
if message.webhook_id:
756757
# Ensure webhook is in cache
@@ -760,12 +761,14 @@ async def _delete(self, message: beacon_message.BeaconMessage):
760761
webhook_obj: discord.Webhook = self._webhooks.get_webhook(message.webhook_id)
761762

762763
# Delete message
763-
await webhook_obj.delete_message(message_id=int(message.id))
764+
try:
765+
await webhook_obj.delete_message(message_id=int(message.id))
766+
except RuntimeError:
767+
# Use regular delete
768+
await partial_message.delete()
764769
else:
765-
message_obj = await channel.fetch_message(int(message.id))
766-
767770
# Delete message
768-
await message_obj.delete()
771+
await partial_message.delete()
769772

770773
async def _purge(self, messages: list[beacon_message.BeaconMessage]):
771774
channel: discord.TextChannel = self.bot.get_channel(int(messages[0].channel.id))

shinobu/beacon/discord/parent.py

Lines changed: 101 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -190,41 +190,27 @@ async def _get_attachment_data(attachment: discord.Attachment) -> beacon_file.Be
190190
spoiler=spoiler
191191
)
192192

193-
@commands.Cog.listener()
194-
async def on_message(self, message: discord.Message):
195-
origin_driver: beacon_driver.BeaconDriver = self._beacon.drivers.get_driver("discord")
196-
193+
async def handle_edit(self, message: discord.Message):
197194
# noinspection DuplicatedCode
198-
if message.content.startswith(self.bot.command_prefix):
199-
# Assume this is a text command
200-
return
195+
origin_driver: beacon_driver.BeaconDriver = self._beacon.drivers.get_driver("discord")
201196

202-
if message.author.id == self.bot.user.id:
203-
# Do not self-bridge
197+
# Get the BeaconMessage object for the message
198+
message_obj: beacon_message.BeaconMessage = self._beacon.messages.get_message(str(message.id))
199+
if not message_obj:
200+
# We can't edit messages that aren't cached
204201
return
205202

203+
# Did we bridge this message?
206204
if message.webhook_id:
207-
# Check if the webhook was ours (to prevent a self-bridge)
208-
webhook: discord.Webhook | None = origin_driver.webhooks.get_webhook(str(message.webhook_id))
209-
210-
if not webhook:
211-
try:
212-
webhook = await self.bot.fetch_webhook(message.webhook_id)
213-
origin_driver.webhooks.store_webhook(str(webhook.id), webhook)
214-
except discord.HTTPException:
215-
pass
216-
217-
if webhook:
218-
# Does the bot own the webhook?
219-
if webhook.user.id == self.bot.user.id:
220-
# Do not self-bridge
221-
return
205+
if message_obj.author.id != str(message.webhook_id):
206+
# We probably did
207+
return
222208

223209
# Convert guild data to server.BeaconServer
224-
# noinspection DuplicatedCode
225210
server: beacon_server.BeaconServer = origin_driver.get_server(str(message.guild.id))
226211

227212
# Convert author data to member.BeaconMember
213+
# noinspection DuplicatedCode
228214
author: beacon_member.BeaconMember = origin_driver.get_member(server, str(message.author.id))
229215

230216
# Convert channel data to channel.BeaconChannel
@@ -236,15 +222,14 @@ async def on_message(self, message: discord.Message):
236222
# Get Space
237223
space: beacon_space.BeaconSpace = self._beacon.spaces.get_space_for_channel(channel)
238224

239-
# noinspection DuplicatedCode
240-
if not space:
241-
# We can't bridge
242-
return
243-
244225
# Get the ID of the webhook to use
245226
membership: beacon_space.BeaconSpaceMember = space.get_member(server)
246227
webhook_id = membership.webhook_id
247228

229+
if not space:
230+
# We can't bridge
231+
return
232+
248233
# Convert message data to message.BeaconMessageContent
249234
content: beacon_message.BeaconMessageContent = await self._to_beacon_content(message)
250235

@@ -261,26 +246,23 @@ async def on_message(self, message: discord.Message):
261246
if preliminary_block:
262247
return
263248

264-
# Send message!
249+
# Edit the message!
265250
try:
266-
await self._beacon.send(
267-
author=author,
268-
space=space,
269-
content=content,
270-
webhook_id=webhook_id
251+
await self._beacon.edit(
252+
message=message_obj,
253+
content=content
271254
)
272255
except beacon.BeaconPlatformDisabled:
273256
pass
274257

275-
@commands.Cog.listener()
276-
async def on_message_edit(self, _, message: discord.Message):
258+
async def handle_delete(self, message: discord.Message):
277259
# noinspection DuplicatedCode
278260
origin_driver: beacon_driver.BeaconDriver = self._beacon.drivers.get_driver("discord")
279261

280262
# Get the BeaconMessage object for the message
281263
message_obj: beacon_message.BeaconMessage = self._beacon.messages.get_message(str(message.id))
282264
if not message_obj:
283-
# We can't edit messages that aren't cached
265+
# We can't remove messages that aren't cached
284266
return
285267

286268
# Did we bridge this message?
@@ -292,11 +274,8 @@ async def on_message_edit(self, _, message: discord.Message):
292274
# Convert guild data to server.BeaconServer
293275
server: beacon_server.BeaconServer = origin_driver.get_server(str(message.guild.id))
294276

295-
# Convert author data to member.BeaconMember
296-
# noinspection DuplicatedCode
297-
author: beacon_member.BeaconMember = origin_driver.get_member(server, str(message.author.id))
298-
299277
# Convert channel data to channel.BeaconChannel
278+
# noinspection DuplicatedCode
300279
channel: beacon_channel.BeaconChannel = origin_driver.get_channel(server, str(message.channel.id))
301280
if not channel:
302281
# We can't bridge
@@ -305,61 +284,54 @@ async def on_message_edit(self, _, message: discord.Message):
305284
# Get Space
306285
space: beacon_space.BeaconSpace = self._beacon.spaces.get_space_for_channel(channel)
307286

308-
# Get the ID of the webhook to use
309-
membership: beacon_space.BeaconSpaceMember = space.get_member(server)
310-
webhook_id = membership.webhook_id
311-
312287
if not space:
313-
# We can't bridge
314-
return
315-
316-
# Convert message data to message.BeaconMessageContent
317-
content: beacon_message.BeaconMessageContent = await self._to_beacon_content(message)
318-
319-
# Run preliminary checks
320-
preliminary_block: beacon.BeaconMessageBlockedReason | None = await self._beacon.can_send(
321-
author=author,
322-
space=space,
323-
content=content,
324-
webhook_id=webhook_id,
325-
skip_filter=True
326-
)
327-
328-
# TODO: Add returning the block reason.
329-
if preliminary_block:
288+
# We can't bridge deletes, even if it was sent in the Space by the server
330289
return
331290

332-
# Edit the message!
291+
# Delete the message!
333292
try:
334-
await self._beacon.edit(
335-
message=message_obj,
336-
content=content
337-
)
293+
await self._beacon.delete(message=message_obj)
338294
except beacon.BeaconPlatformDisabled:
339295
pass
340296

341297
@commands.Cog.listener()
342-
async def on_message_delete(self, message: discord.Message):
343-
# noinspection DuplicatedCode
298+
async def on_message(self, message: discord.Message):
344299
origin_driver: beacon_driver.BeaconDriver = self._beacon.drivers.get_driver("discord")
345300

346-
# Get the BeaconMessage object for the message
347-
message_obj: beacon_message.BeaconMessage = self._beacon.messages.get_message(str(message.id))
348-
if not message_obj:
349-
# We can't remove messages that aren't cached
301+
# noinspection DuplicatedCode
302+
if message.content.startswith(self.bot.command_prefix):
303+
# Assume this is a text command
304+
return
305+
306+
if message.author.id == self.bot.user.id:
307+
# Do not self-bridge
350308
return
351309

352-
# Did we bridge this message?
353310
if message.webhook_id:
354-
if message_obj.author.id != str(message.webhook_id):
355-
# We probably did
356-
return
311+
# Check if the webhook was ours (to prevent a self-bridge)
312+
webhook: discord.Webhook | None = origin_driver.webhooks.get_webhook(str(message.webhook_id))
313+
314+
if not webhook:
315+
try:
316+
webhook = await self.bot.fetch_webhook(message.webhook_id)
317+
origin_driver.webhooks.store_webhook(str(webhook.id), webhook)
318+
except discord.HTTPException:
319+
pass
320+
321+
if webhook:
322+
# Does the bot own the webhook?
323+
if webhook.user.id == self.bot.user.id:
324+
# Do not self-bridge
325+
return
357326

358327
# Convert guild data to server.BeaconServer
328+
# noinspection DuplicatedCode
359329
server: beacon_server.BeaconServer = origin_driver.get_server(str(message.guild.id))
360330

331+
# Convert author data to member.BeaconMember
332+
author: beacon_member.BeaconMember = origin_driver.get_member(server, str(message.author.id))
333+
361334
# Convert channel data to channel.BeaconChannel
362-
# noinspection DuplicatedCode
363335
channel: beacon_channel.BeaconChannel = origin_driver.get_channel(server, str(message.channel.id))
364336
if not channel:
365337
# We can't bridge
@@ -368,16 +340,62 @@ async def on_message_delete(self, message: discord.Message):
368340
# Get Space
369341
space: beacon_space.BeaconSpace = self._beacon.spaces.get_space_for_channel(channel)
370342

343+
# noinspection DuplicatedCode
371344
if not space:
372-
# We can't bridge deletes, even if it was sent in the Space by the server
345+
# We can't bridge
373346
return
374347

375-
# Delete the message!
348+
# Get the ID of the webhook to use
349+
membership: beacon_space.BeaconSpaceMember = space.get_member(server)
350+
webhook_id = membership.webhook_id
351+
352+
# Convert message data to message.BeaconMessageContent
353+
content: beacon_message.BeaconMessageContent = await self._to_beacon_content(message)
354+
355+
# Run preliminary checks
356+
preliminary_block: beacon.BeaconMessageBlockedReason | None = await self._beacon.can_send(
357+
author=author,
358+
space=space,
359+
content=content,
360+
webhook_id=webhook_id,
361+
skip_filter=True
362+
)
363+
364+
# TODO: Add returning the block reason.
365+
if preliminary_block:
366+
return
367+
368+
# Send message!
376369
try:
377-
await self._beacon.delete(message=message_obj)
370+
await self._beacon.send(
371+
author=author,
372+
space=space,
373+
content=content,
374+
webhook_id=webhook_id
375+
)
378376
except beacon.BeaconPlatformDisabled:
379377
pass
380378

379+
@commands.Cog.listener()
380+
async def on_message_edit(self, _, message: discord.Message):
381+
# Check if message is pending
382+
if self._beacon.is_pending(str(message.id)):
383+
# Add callback
384+
self._beacon.add_callback(str(message.id), self.handle_edit, [message])
385+
else:
386+
# Run directly
387+
await self.handle_edit(message)
388+
389+
@commands.Cog.listener()
390+
async def on_message_delete(self, message: discord.Message):
391+
# Check if message is pending
392+
if self._beacon.is_pending(str(message.id)):
393+
# Add callback
394+
self._beacon.add_callback(str(message.id), self.handle_delete, [message])
395+
else:
396+
# Run directly
397+
await self.handle_delete(message)
398+
381399
@commands.Cog.listener()
382400
async def on_bulk_message_delete(self, messages: list[discord.Message]):
383401
# noinspection DuplicatedCode

0 commit comments

Comments
 (0)