Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- More emojis in the messages that SpellBot sends.

## [v17.10.2](https://github.com/lexicalunit/spellbot/releases/tag/v17.10.2) - 2026-02-04

### Changed
Expand Down
6 changes: 5 additions & 1 deletion src/spellbot/actions/lfg_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,9 +633,13 @@ def mythic_track_link(player_xid: int) -> str:
async def notify_player(player_xid: int) -> None:
embed = base_embed.copy()
if pin := player_pins[player_xid]:
mt_emoji = ""
emojis = self.bot.emojis_cache
if emoji := next((e for e in emojis if e.name == "mythic_track"), None):
mt_emoji = f"{emoji} "
embed.description = (
f"{embed.description}\n\n"
f"Track your game on [Mythic Track]({mythic_track_link(player_xid)}) "
f"Track your game on {mt_emoji}[Mythic Track]({mythic_track_link(player_xid)}) "
f"with PIN code: `{pin}`"
)
if player := await safe_fetch_user(self.bot, player_xid):
Expand Down
Binary file added src/spellbot/assets/emoji/convoke.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spellbot/assets/emoji/girudo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spellbot/assets/emoji/mythic_track.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spellbot/assets/emoji/spellbot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spellbot/assets/emoji/spellbot_creator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spellbot/assets/emoji/spellbot_supporter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spellbot/assets/emoji/spelltable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spellbot/assets/emoji/table_stream.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 14 additions & 13 deletions src/spellbot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import logging
from contextlib import asynccontextmanager
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock
from uuid import uuid4
Expand All @@ -23,6 +24,8 @@
from .settings import settings
from .utils import user_can_moderate

ASSETS_DIR = Path(__file__).resolve().parent / "assets"

if TYPE_CHECKING:
from collections.abc import AsyncGenerator

Expand Down Expand Up @@ -96,13 +99,9 @@ async def setup_hook(self) -> None: # pragma: no cover
async def _create_application_emoji(
self,
name: str,
image_url: str,
image_bytes: bytes,
) -> discord.Emoji | None:
try:
async with httpx.AsyncClient(follow_redirects=True) as client:
response = await client.get(image_url)
response.raise_for_status()
image_bytes = response.content
return await self.create_application_emoji(name=name, image=image_bytes)
except Exception:
logger.exception("warning: could not create application emoji %s", name)
Expand All @@ -128,21 +127,23 @@ async def fetch() -> list[discord.PartialEmoji | discord.Emoji]:
async def ensure(
emojis: list[discord.PartialEmoji | discord.Emoji],
name: str,
image_url: str,
image_bytes: bytes,
) -> discord.PartialEmoji | discord.Emoji | None:
for emoji in emojis:
if emoji.name == name:
return emoji
return await self._create_application_emoji(name, image_url)
return await self._create_application_emoji(name, image_bytes)

try:
emojis = await fetch()
created = await ensure(emojis, "spellbot_creator", settings.EMOJI_SPELLBOT_CREATOR)
if created and created not in emojis:
emojis.append(created)
created = await ensure(emojis, "spellbot_supporter", settings.EMOJI_SPELLBOT_SUPPORTER)
if created and created not in emojis:
emojis.append(created)
emoji_dir = ASSETS_DIR / "emoji"
emoji_files = list(emoji_dir.glob("*.png"))
for image_path in emoji_files:
name = image_path.stem
image_bytes = image_path.read_bytes()
created = await ensure(emojis, name, image_bytes)
if created and created not in emojis:
emojis.append(created)
self.emojis_cache = emojis
logger.info("cached %d application emojis", len(self.emojis_cache))
except Exception:
Expand Down
20 changes: 16 additions & 4 deletions src/spellbot/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ def __str__(self) -> str:
NOT_ANY = "Not any", "_Please contact the players in your game to organize this game._", None, 8
SPELLTABLE = (
"SpellTable",
"_A SpellTable link will be created when all players have joined._",
(
"_A {emoji}[SpellTable](https://spelltable.wizards.com/) link will "
"be created when all players have joined._"
),
"https://spelltable.wizards.com/",
4,
)
Expand All @@ -56,19 +59,28 @@ def __str__(self) -> str:
TTS = "TabletopSim", "_Please use TabletopSim for this game._", None, 10
TABLE_STREAM = (
"Table Stream",
"_A Table Stream link will be created when all players have joined._",
(
"_A {emoji}[Table Stream](https://table-stream.com/) link will "
"be created when all players have joined._"
),
"https://table-stream.com/",
6,
)
CONVOKE = (
"Convoke",
"_A Convoke link will be created when all players have joined._",
(
"_A {emoji}[Convoke](https://www.convoke.games/) link will "
"be created when all players have joined._"
),
"https://www.convoke.games/",
8,
)
GIRUDO = (
"Girudo",
"_A Girudo link will be created when all players have joined._",
(
"_A {emoji}[Girudo](https://www.girudo.com/) link will "
"be created when all players have joined._"
),
"https://www.girudo.com/",
4,
)
Expand Down
12 changes: 11 additions & 1 deletion src/spellbot/models/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,16 @@ def embed_description_link_info(
effective_service: GameService,
dm: bool,
rematch: bool,
emojis: list[discord.Emoji | discord.PartialEmoji] | None = None,
) -> str:
if self.status != GameStatus.STARTED.value:
if "{emoji}" in effective_service.pending_msg:
emoji_str = ""
if emojis:
emoji_name = effective_service.name.lower().replace("-", "_").replace(" ", "_")
if emoji := next((e for e in emojis if e.name == emoji_name), None):
emoji_str = f"{emoji} "
return effective_service.pending_msg.format(emoji=emoji_str)
return effective_service.pending_msg
if not self.show_links(dm):
return "Please check your Direct Messages for your game details."
Expand Down Expand Up @@ -347,6 +355,7 @@ def embed_description(
dm: bool = False,
suggested_vc: VoiceChannelSuggestion | None = None,
rematch: bool = False,
emojis: list[discord.Emoji | discord.PartialEmoji] | None = None,
) -> str:
if span := tracer.current_span(): # pragma: no cover
span.set_tags(
Expand All @@ -362,7 +371,7 @@ def embed_description(
parts: list[str] = []
if self.guild.notice:
parts.append(f"{self.guild.notice}")
parts.append(self.embed_description_link_info(effective_service, dm, rematch))
parts.append(self.embed_description_link_info(effective_service, dm, rematch, emojis))
parts.extend(self.embed_description_extras(dm, suggested_vc))
parts.extend(self.embed_motd())
return "\n\n".join(parts)
Expand Down Expand Up @@ -462,6 +471,7 @@ def to_embed(
dm=dm,
suggested_vc=suggested_vc,
rematch=rematch,
emojis=emojis,
)
if self.rules:
embed.add_field(name="⚠️ Additional Rules:", value=self.rules, inline=False)
Expand Down
10 changes: 0 additions & 10 deletions src/spellbot/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ class Settings:
"DD_TRACE_ENABLED",
"DEBUG_GUILD",
"DONATE_LINK",
"EMOJI_SPELLBOT_CREATOR",
"EMOJI_SPELLBOT_SUPPORTER",
"EMPTY_EMBED_COLOR",
"EXPIRE_GAMES_LOOP_M",
"EXPIRE_TIME_M",
Expand Down Expand Up @@ -96,14 +94,6 @@ def __init__(self, guild_xid: int | None = None) -> None: # noqa: PLR0915
self.DEBUG_GUILD = getenv("DEBUG_GUILD")
self.API_BASE_URL = getenv("API_BASE_URL", "https://bot.spellbot.io")
self.OWNER_XID = getenv("OWNER_XID")
self.EMOJI_SPELLBOT_CREATOR = getenv(
"EMOJI_SPELLBOT_CREATOR",
"https://github.com/user-attachments/assets/beaabff5-dc19-40ee-a86e-40cd4b387fc4",
)
self.EMOJI_SPELLBOT_SUPPORTER = getenv(
"EMOJI_SPELLBOT_SUPPORTER",
"https://github.com/user-attachments/assets/ec1045a4-4d81-412b-81fe-ea1f414a5e32",
)

# datadog
self.DD_API_KEY = getenv("DD_API_KEY")
Expand Down
8 changes: 6 additions & 2 deletions tests/cogs/test_admin_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@

pytestmark = pytest.mark.use_db

SPELLTABLE_PENDING_MSG = (
"_A [SpellTable](https://spelltable.wizards.com/) link will "
"be created when all players have joined._"
)


@pytest_asyncio.fixture
async def cog(bot: SpellBot) -> AdminCog:
Expand Down Expand Up @@ -355,8 +360,7 @@ async def test_happy_path(
assert get_last_send_message(interaction, "embed") == {
"color": settings.EMPTY_EMBED_COLOR,
"description": (
"_A SpellTable link will be created when all players have joined._\n\n"
f"{game.guild.motd}\n\n{game.channel.motd}"
f"{SPELLTABLE_PENDING_MSG}\n\n{game.guild.motd}\n\n{game.channel.motd}"
),
"fields": [
{"inline": True, "name": "Format", "value": "Commander"},
Expand Down
29 changes: 9 additions & 20 deletions tests/cogs/test_leave_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@

pytestmark = pytest.mark.use_db

SPELLTABLE_PENDING_MSG = (
"_A [SpellTable](https://spelltable.wizards.com/) link will "
"be created when all players have joined._"
)


@pytest_asyncio.fixture
async def cog(bot: SpellBot) -> LeaveGameCog:
Expand Down Expand Up @@ -62,11 +67,7 @@ async def test_leave(
safe_update_embed_call = leave_action.safe_update_embed.call_args_list[0]
assert safe_update_embed_call.kwargs["embed"].to_dict() == {
"color": settings.PENDING_EMBED_COLOR,
"description": (
"_A SpellTable link will be created when all players have joined._\n"
"\n"
f"{guild.motd}\n\n{channel.motd}"
),
"description": (f"{SPELLTABLE_PENDING_MSG}\n\n{guild.motd}\n\n{channel.motd}"),
"fields": [
{"inline": False, "name": "Players", "value": f"• <@{p2.xid}> ({p2.name})"},
{"inline": True, "name": "Format", "value": "Commander"},
Expand Down Expand Up @@ -133,11 +134,7 @@ async def test_leave_all(
safe_update_embed_call = leave_action.safe_update_embed.call_args_list[0]
assert safe_update_embed_call.kwargs["embed"].to_dict() == {
"color": settings.PENDING_EMBED_COLOR,
"description": (
"_A SpellTable link will be created when all players have joined._\n"
"\n"
f"{guild.motd}\n\n{channel.motd}"
),
"description": (f"{SPELLTABLE_PENDING_MSG}\n\n{guild.motd}\n\n{channel.motd}"),
"fields": [
{"inline": False, "name": "Players", "value": f"• <@{p2.xid}> ({p2.name})"},
{"inline": True, "name": "Format", "value": "Commander"},
Expand Down Expand Up @@ -340,11 +337,7 @@ async def test_leave_button(
safe_update_embed_origin_call = leave_action.safe_update_embed_origin.call_args_list[0]
assert safe_update_embed_origin_call.kwargs["embed"].to_dict() == {
"color": settings.PENDING_EMBED_COLOR,
"description": (
"_A SpellTable link will be created when all players have joined._\n"
"\n"
f"{guild.motd}\n\n{channel.motd}"
),
"description": (f"{SPELLTABLE_PENDING_MSG}\n\n{guild.motd}\n\n{channel.motd}"),
"fields": [
{"inline": False, "name": "Players", "value": f"• <@{p2.xid}> ({p2.name})"},
{"inline": True, "name": "Format", "value": "Commander"},
Expand Down Expand Up @@ -406,11 +399,7 @@ async def test_leave_button_with_other_players(
safe_update_embed_origin_call = leave_action.safe_update_embed_origin.call_args_list[0]
assert safe_update_embed_origin_call.kwargs["embed"].to_dict() == {
"color": settings.PENDING_EMBED_COLOR,
"description": (
"_A SpellTable link will be created when all players have joined._\n"
"\n"
f"{guild.motd}\n\n{channel.motd}"
),
"description": (f"{SPELLTABLE_PENDING_MSG}\n\n{guild.motd}\n\n{channel.motd}"),
"fields": [
{"inline": False, "name": "Players", "value": f"• <@{p2.xid}> ({p2.name})"},
{"inline": True, "name": "Format", "value": "Commander"},
Expand Down
11 changes: 6 additions & 5 deletions tests/cogs/test_lfg_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@

pytestmark = pytest.mark.use_db

SPELLTABLE_PENDING_MSG = (
"_A [SpellTable](https://spelltable.wizards.com/) link will "
"be created when all players have joined._"
)


@pytest.fixture
def cog(bot: SpellBot) -> LookingForGameCog:
Expand Down Expand Up @@ -393,11 +398,7 @@ async def test_join(
mock_call.assert_called_once()
assert mock_call.call_args_list[0].kwargs["embed"].to_dict() == {
"color": settings.PENDING_EMBED_COLOR,
"description": (
"_A SpellTable link will be created when all players have joined._\n"
f"\n{guild.motd}\n"
f"\n{channel.motd}"
),
"description": (f"{SPELLTABLE_PENDING_MSG}\n\n{guild.motd}\n\n{channel.motd}"),
"fields": [
{
"inline": False,
Expand Down
Loading
Loading