Skip to content

Commit 97c9423

Browse files
committed
Begin work on Beacon (bridge protocol)
1 parent 52ca0c9 commit 97c9423

15 files changed

Lines changed: 364 additions & 0 deletions

File tree

shinobu/beacon/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Shinobu Beacon
2+
Beacon is the cross-platform bridge framework for Shinobu.

shinobu/beacon/__init__.py

Whitespace-only changes.

shinobu/beacon/cogs/backend.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from discord.ext import commands
2+
from shinobu.runtime.models import shinobu_cog
3+
4+
class BeaconBackend(shinobu_cog.ShinobuCog):
5+
def __init__(self, bot, **kwargs):
6+
# Register cog metadata
7+
self.setup_shinobu_cog(
8+
bot,
9+
**kwargs,
10+
shinobu_metadata=shinobu_cog.ShinobuCogMetadata(
11+
name="Beacon Backend",
12+
description="Manages the Shinobu Beacon bridge protocol.",
13+
visible_in_help=True
14+
)
15+
)
16+
17+
def on_entitlements_issued(self):
18+
# We will initialize Beacon here
19+
self.bot.shared_objects.add("beacon")
20+
return
21+
22+
def get_cog_type():
23+
return BeaconBackend
24+
25+
def setup(bot):
26+
bot.add_cog(BeaconBackend(bot))

shinobu/beacon/models/abc.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class BeaconABC:
2+
"""A abstract base class for objects."""
3+
4+
def __init__(self, object_id: str, platform: str):
5+
self._id: str = object_id
6+
self._platform: str = platform
7+
8+
@property
9+
def id(self) -> str:
10+
"""Returns the ID of an object."""
11+
return self._id
12+
13+
@property
14+
def platform(self) -> str:
15+
"""Returns the platform of an object."""
16+
return self._platform

shinobu/beacon/models/channel.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from shinobu.beacon.models import messageable, server as beacon_server
2+
3+
class BeaconChannel(messageable.BeaconMessageable):
4+
"""A class representing server channels."""
5+
6+
def __init__(self, object_id: str, platform: str, name: str, server: beacon_server.BeaconServer,
7+
nsfw: bool = False):
8+
super().__init__(object_id, platform, name)
9+
self._server: beacon_server.BeaconServer = server
10+
self._nsfw: bool = nsfw
11+
12+
@property
13+
def server(self) -> beacon_server.BeaconServer:
14+
return self._server
15+
16+
@property
17+
def server_id(self) -> str:
18+
return self._server.id
19+
20+
@property
21+
def nsfw(self) -> bool:
22+
return self._nsfw

shinobu/beacon/models/content.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from enum import Enum
2+
3+
class BeaconContentType(Enum):
4+
text = 1
5+
file = 2
6+
7+
class BeaconContentBlock:
8+
"""A class representing content blocks in a Beacon message."""
9+
10+
def __init__(self, content_type: BeaconContentType, content: dict):
11+
self._type: BeaconContentType = content_type
12+
self._content: dict = content
13+
14+
@property
15+
def type(self) -> BeaconContentType:
16+
return self._type
17+
18+
@property
19+
def content(self) -> dict:
20+
return self._content
21+
22+
class BeaconContentText(BeaconContentBlock):
23+
def __init__(self, content: str):
24+
super().__init__(BeaconContentType.text, {"content": content})

shinobu/beacon/models/driver.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from shinobu.beacon.models import (user as beacon_user, channel as beacon_channel, server as beacon_server,
2+
member as beacon_member, webhook as beacon_webhook, message as beacon_message,
3+
messageable as beacon_messageable)
4+
5+
class BeaconDriverUnsupported(Exception):
6+
def __init__(self):
7+
super().__init__("Driver does not support this feature.")
8+
9+
class BeaconDriverWebhookCache:
10+
def __init__(self):
11+
self._data: dict = {}
12+
13+
def store_webhook(self, webhook: beacon_webhook.BeaconWebhook):
14+
if webhook.id in self._data:
15+
raise KeyError("Webhook already in cache")
16+
17+
self._data.update({webhook.id: webhook})
18+
19+
def store_webhooks(self, webhooks: list):
20+
for webhook in webhooks:
21+
self.store_webhook(webhook)
22+
23+
def get_webhook(self, webhook_id: str):
24+
return self._data.get(webhook_id)
25+
26+
class BeaconDriver:
27+
"""A class representing a platform driver for the Beacon bridge protocol."""
28+
29+
def __init__(self, platform: str, bot):
30+
self._platform: str = platform
31+
self._webhooks: BeaconDriverWebhookCache = BeaconDriverWebhookCache()
32+
self._bot = bot
33+
34+
@property
35+
def platform(self) -> str:
36+
"""The platform the driver enables support for."""
37+
return self._platform
38+
39+
def get_user(self, user_id: str) -> beacon_user.BeaconUser:
40+
"""Gets a user."""
41+
raise BeaconDriverUnsupported()
42+
43+
async def fetch_user(self, user_id: str) -> beacon_user.BeaconUser:
44+
"""Fetches a user from the platform API."""
45+
raise BeaconDriverUnsupported()
46+
47+
def get_member(self, server: beacon_server.BeaconServer, member_id: str) -> beacon_member.BeaconMember:
48+
"""Gets a member from a server."""
49+
raise BeaconDriverUnsupported()
50+
51+
def get_channel(self, server: beacon_server.BeaconServer, channel_id: str) -> beacon_channel.BeaconChannel:
52+
"""Gets a channel from a server."""
53+
raise BeaconDriverUnsupported()
54+
55+
def get_server(self, server_id: str) -> beacon_server.BeaconServer:
56+
"""Gets a server."""
57+
raise BeaconDriverUnsupported()
58+
59+
async def fetch_server(self, server_id: str) -> beacon_server.BeaconServer:
60+
"""Fetches a server from the platform API."""
61+
raise BeaconDriverUnsupported()
62+
63+
def get_webhook(self, webhook_id: str) -> beacon_webhook.BeaconWebhook:
64+
"""Gets a webhook."""
65+
66+
# Many libraries don't have a built-in webhook cache, so Beacon provides its own.
67+
# However, this method can still be overwritten as needed.
68+
69+
return self._webhooks.get_webhook(webhook_id)
70+
71+
async def fetch_webhook(self, webhook_id: str) -> beacon_webhook.BeaconWebhook:
72+
"""Fetches a webhook from the platform API."""
73+
74+
# Preferrably this should call self._webhooks.store_webhook(webhook) after fetching the webhook
75+
# to store the webhook to cache, unless the library has a webhook cache of its own.
76+
77+
raise BeaconDriverUnsupported()
78+
79+
async def send(self, destination: beacon_messageable.BeaconMessageable,
80+
content: beacon_message.BeaconMessageContent, send_as: beacon_user.BeaconUser):
81+
"""Sends a message to a given destination."""
82+
raise BeaconDriverUnsupported()

shinobu/beacon/models/member.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from shinobu.beacon.models import user, server as beacon_server
2+
3+
class BeaconMember(user.BeaconUser):
4+
"""A class representing server members."""
5+
6+
def __init__(self, object_id: str, platform: str, name: str, server: beacon_server.BeaconServer,
7+
display_name: str | None = None, avatar_url: str | None = None):
8+
super().__init__(object_id, platform, name, display_name=display_name, avatar_url=avatar_url)
9+
self._server: beacon_server.BeaconServer = server
10+
11+
@property
12+
def server(self) -> beacon_server.BeaconServer:
13+
return self._server
14+
15+
@property
16+
def server_id(self) -> str:
17+
return self._server.id

shinobu/beacon/models/message.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from shinobu.beacon.models import (content as beacon_content, abc, user as beacon_user, channel as beacon_channel,
2+
server as beacon_server, webhook as beacon_webhook)
3+
4+
class BeaconMessageContent:
5+
def __init__(self):
6+
self._blocks: dict = {}
7+
8+
def to_plaintext(self) -> str:
9+
components: list = []
10+
for block in self._blocks:
11+
block_obj: beacon_content.BeaconContentBlock = self._blocks[block]
12+
13+
# We'll restrict blocks to BeaconContentText only here
14+
if type(block_obj) is not beacon_content.BeaconContentBlock:
15+
continue
16+
17+
components.append(block_obj.content)
18+
19+
return "\n".join(components)
20+
21+
class BeaconMessage(abc.BeaconABC):
22+
def __init__(self, message_id: str, platform: str, author: beacon_user.BeaconUser | beacon_webhook.BeaconWebhook,
23+
server: beacon_server.BeaconServer | None = None, channel: beacon_channel.BeaconChannel | None = None,
24+
content: str | dict | None = None):
25+
super().__init__(message_id, platform)
26+
self._author: beacon_user.BeaconUser | beacon_webhook.BeaconWebhook = author
27+
self._server: beacon_server.BeaconServer | None = server
28+
self._channel: beacon_channel.BeaconChannel | None = channel
29+
self._content: str | dict | None = content
30+
31+
@property
32+
def author(self) -> beacon_user.BeaconUser | beacon_webhook.BeaconWebhook:
33+
return self._author
34+
35+
@property
36+
def webhook(self) -> beacon_webhook.BeaconWebhook | None:
37+
if type(self._author) is not beacon_webhook.BeaconWebhook:
38+
return None
39+
40+
return self._author
41+
42+
@property
43+
def server(self) -> beacon_server.BeaconServer | None:
44+
return self._server
45+
46+
@property
47+
def channel(self) -> beacon_channel.BeaconChannel | None:
48+
return self._channel
49+
50+
@property
51+
def content(self) -> str | dict | None:
52+
return self._content
53+
54+
def edit_content(self, new_content: str | dict | None):
55+
self._content = new_content
56+
57+
def to_dict(self, include_content: bool = False) -> dict:
58+
"""Returns a BeaconMessage object as a dictionary. Should be used for backing up
59+
Beacon state."""
60+
converted = {
61+
"id": self.id,
62+
"platform": self.platform,
63+
"author_id": self.author.id,
64+
"server_id": self.server.id if self.server else None,
65+
"channel_id": self.channel.id if self.channel else None,
66+
"webhook_id": self.webhook.id if self.webhook else None
67+
}
68+
69+
if include_content:
70+
converted.update({"content": self.content})
71+
72+
return converted
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from shinobu.beacon.models import abc
2+
3+
class BeaconMessageable(abc.BeaconABC):
4+
"""A base class for messageable objects (e.g. users, channels)."""
5+
6+
def __init__(self, object_id: str, platform: str, name: str):
7+
super().__init__(object_id, platform)
8+
self._name: str = name
9+
10+
@property
11+
def name(self) -> str:
12+
return self._name

0 commit comments

Comments
 (0)