Skip to content

Commit

Permalink
♻️Keep hero/champ emotes in the database
Browse files Browse the repository at this point in the history
  • Loading branch information
Aluerie committed Nov 8, 2024
1 parent 46a5465 commit ee5e196
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 322 deletions.
22 changes: 22 additions & 0 deletions ext/dev/deprecated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from discord.ext import commands

from ._base import DevBaseCog

if TYPE_CHECKING:
from bot import AluBot, AluContext


class Deprecated(DevBaseCog):
@commands.command(name="transfer_hero_emotes", hidden=True)
async def nothing(self, ctx: AluContext) -> None:
await ctx.typing()
await ctx.send("Done")


async def setup(bot: AluBot) -> None:
"""Load AluBot extension. Framework of discord.py."""
await bot.add_cog(Deprecated(bot))
8 changes: 0 additions & 8 deletions ext/fpc/dev_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,7 @@ async def create_emote_helper(
async def hideout_devfpc_dota_emote(
self, interaction: discord.Interaction[AluBot], hero: app_commands.Transform[Hero, HeroTransformer]
) -> None:
"""Create a new discord emote for a Dota 2 hero.

Useful when a new Dota 2 hero gets added to the game, so we can just use this command,
copy-paste the answer to `utils.const` and be happy.
"""
await self.create_emote_helper(
interaction,
hero,
Expand All @@ -97,11 +93,7 @@ async def hideout_devfpc_dota_emote(
async def hideout_devfpc_lol_emote(
self, interaction: discord.Interaction[AluBot], champion: app_commands.Transform[Champion, ChampionTransformer]
) -> None:
"""Create a new discord emote for a League of Legends champion.

Useful when a new LoL champion gets added to the game, so we can just use this command,
copy-paste the answer to `utils.const` and be happy.
"""
await self.create_emote_helper(
interaction,
champion,
Expand Down
6 changes: 6 additions & 0 deletions sql/fpc_dota.sql
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,10 @@ CREATE TABLE IF NOT EXISTS dota_messages (
friend_id INTEGER NOT NULL,
hero_id: INT NOT NULL,
player_name: TEXT --currently only used for logs so we don't double JOIN
);


CREATE TABLE IF NOT EXISTS dota_heroes_info (
id INT PRIMARY KEY,
emote TEXT
);
5 changes: 5 additions & 0 deletions sql/fpc_lol.sql
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ CREATE TABLE IF NOT EXISTS lol_messages (
match_id BIGINT NOT NULL,
platform TEXT NOT NULL,
champion_id INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS lol_champions_info (
id INT PRIMARY KEY,
emote TEXT
);
130 changes: 0 additions & 130 deletions utils/dota/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,133 +55,3 @@ class DotaAsset(ImageAsset):
}

NEW_HERO_EMOTE = "\N{SQUARED NEW}"

HERO_EMOTES: dict[int, str] = {
# mapping dota hero ids to their correlated hero-icon emotes.
1: "<:AntiMage:1202019770787176529>",
2: "<:Axe:1202019772997304471>",
3: "<:Bane:1202019775291592754>",
4: "<:Bloodseeker:1202019777212858398>",
5: "<:CrystalMaiden:1202019779745947749>",
6: "<:DrowRanger:1202019782556385330>",
7: "<:Earthshaker:1202019784766529596>",
8: "<:Juggernaut:1202019787698614403>",
9: "<:Mirana:1202019790080987166>",
10: "<:Morphling:1202019792429797459>",
11: "<:ShadowFiend:1202019795319672952>",
12: "<:PhantomLancer:1202019797764931584>",
13: "<:Puck:1202019799694315522>",
14: "<:Pudge:1202019802378674186>",
15: "<:Razor:1202019804886863872>",
16: "<:SandKing:1202019806602076311>",
17: "<:StormSpirit:1202019809710047314>",
18: "<:Sven:1202019812310786130>",
19: "<:Tiny:1202019815028703254>",
20: "<:VengefulSpirit:1202019817746616362>",
21: "<:Windranger:1202019820510388244>",
22: "<:Zeus:1202019823018573934>",
23: "<:Kunkka:1202019825690615819>",
25: "<:Lina:1202019828496601142>",
26: "<:Lion:1202019832078274601>",
27: "<:ShadowShaman:1202019834465095691>",
28: "<:Slardar:1202019837316960339>",
29: "<:Tidehunter:1202019839422496800>",
30: "<:WitchDoctor:1202019842673086505>",
31: "<:Lich:1202019845265444917>",
32: "<:Riki:1202019847672975380>",
33: "<:Enigma:1202019850034348124>",
34: "<:Tinker:1202019851779182682>",
35: "<:Sniper:1202019854266142740>",
36: "<:Necrophos:1202019856929525811>",
37: "<:Warlock:1202019859529998401>",
38: "<:Beastmaster:1202019862655008819>",
39: "<:QueenOfPain:1202019865146437763>",
40: "<:Venomancer:1202019867822411856>",
41: "<:FacelessVoid:1202019870347108462>",
42: "<:WraithKing:1202019873119535214>",
43: "<:DeathProphet:1202019875355103245>",
44: "<:PhantomAssassin:1202019878052319244>",
45: "<:Pugna:1202019880648577134>",
46: "<:TemplarAssassin:1202019882590539816>",
47: "<:Viper:1202019885106860092>",
48: "<:Luna:1202019886906495006>",
# guild 2
49: "<:DragonKnight:1202026697818636339>",
50: "<:Dazzle:1202026700301668352>",
51: "<:Clockwerk:1202026703204122664>",
52: "<:Leshrac:1202026706337013851>",
53: "<:NaturesProphet:1202026709134610452>",
54: "<:Lifestealer:1202026711508860978>",
55: "<:DarkSeer:1202026713345958019>",
56: "<:Clinkz:1202026715694768218>",
57: "<:Omniknight:1202026718211088437>",
58: "<:Enchantress:1202026720388186204>",
59: "<:Huskar:1202026723441381476>",
60: "<:NightStalker:1202026726075674645>",
61: "<:Broodmother:1202026728625807361>",
62: "<:BountyHunter:1202026732383907940>",
63: "<:Weaver:1202026735072460831>",
64: "<:Jakiro:1202026737572249630>",
65: "<:Batrider:1202026739275153521>",
66: "<:Chen:1202026741854642297>",
67: "<:Spectre:1202026744207376386>",
68: "<:AncientApparition:1202026746791333898>",
69: "<:Doom:1202026750511423488>",
70: "<:Ursa:1202026752776622132>",
71: "<:SpiritBreaker:1202026755595194398>",
72: "<:Gyrocopter:1202026758132465744>",
73: "<:Alchemist:1202026760607109232>",
74: "<:Invoker:1202026763291730000>",
75: "<:Silencer:1202026765791547422>",
76: "<:OutworldDestroyer:1202026767863533568>",
77: "<:Lycan:1202026770031706184>",
78: "<:Brewmaster:1202026772653408297>",
79: "<:ShadowDemon:1202026775412998214>",
80: "<:LoneDruid:1202026778282168341>",
81: "<:ChaosKnight:1202026781033644112>",
82: "<:Meepo:1202026783650893855>",
83: "<:TreantProtector:1202026786188443718>",
84: "<:OgreMagi:1202026788486909982>",
85: "<:Undying:1202026790932205672>",
# guild 3
86: "<:Rubick:1202027073464717373>",
87: "<:Disruptor:1202027076316565575>",
88: "<:NyxAssassin:1202027079475150898>",
89: "<:NagaSiren:1202027082058838117>",
90: "<:KeeperOfTheLight:1202029134101106718>",
91: "<:Io:1202029137280114760>",
92: "<:Visage:1202029140811984918>",
93: "<:Slark:1202029143190155304>",
94: "<:Medusa:1202029145563861012>",
95: "<:TrollWarlord:1202029147937833000>",
96: "<:CentaurWarrunner:1202029150282719283>",
97: "<:Magnus:1202029152694440029>",
98: "<:Timbersaw:1202029155366215752>",
99: "<:Bristleback:1202029157710823464>",
100: "<:Tusk:1202029160072220692>",
101: "<:SkywrathMage:1202029162408464404>",
102: "<:Abaddon:1202029164312674396>",
103: "<:ElderTitan:1202029166044917791>",
104: "<:LegionCommander:1202029169152892928>",
105: "<:Techies:1202029171480731682>",
106: "<:EmberSpirit:1202029174156427284>",
107: "<:EarthSpirit:1202029176711041035>",
108: "<:Underlord:1202029179567358022>",
109: "<:Terrorblade:1202029182381723740>",
110: "<:Phoenix:1202029184621219920>",
111: "<:Oracle:1202029187024556042>",
112: "<:WinterWyvern:1202029189595922492>",
113: "<:ArcWarden:1202029192255119402>",
114: "<:MonkeyKing:1202029194666582087>",
119: "<:DarkWillow:1202029197061787718>",
120: "<:Pangolier:1202029199636824125>",
121: "<:Grimstroke:1202029202031779841>",
123: "<:Hoodwink:1202029204506693685>",
126: "<:VoidSpirit:1202029206469623841>",
128: "<:Snapfire:1202029208574890017>",
129: "<:Mars:1202029210567446558>",
135: "<:Dawnbreaker:1202029213549592586>",
136: "<:Marci:1202029215982293003>",
137: "<:PrimalBeast:1202029218167529502>",
138: "<:Muerta:1202029220616994926>",
}
38 changes: 34 additions & 4 deletions utils/dota/storage.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, override
from typing import TYPE_CHECKING, TypedDict, override

import discord

from bot import AluBot

from .. import const, formats
from ..fpc import Character, CharacterStorage, CharacterTransformer, GameDataStorage
from . import constants

if TYPE_CHECKING:
import discord

from bot import AluBot

class GetHeroEmoteRow(TypedDict):
id: int
emote: str


__all__ = (
"Abilities",
Expand Down Expand Up @@ -92,6 +97,10 @@ class Heroes(CharacterStorage[Hero, PseudoHero]): # CharacterCache
async def fill_data(self) -> dict[int, Hero]:
heroes = await self.bot.dota.stratz.get_heroes()

query = "SELECT id, emote FROM dota_heroes_info"
rows: list[GetHeroEmoteRow] = await self.bot.pool.fetch(query)
hero_emotes = {row["id"]: row["emote"] for row in rows}

return {
hero["id"]: Hero(
id=hero["id"],
Expand All @@ -100,7 +109,7 @@ async def fill_data(self) -> dict[int, Hero]:
talent_ids=[talent["abilityId"] for talent in hero["talents"]],
facet_ids=[facet["facetId"] for facet in hero["facets"]],
# if I don't provide a ready-to-go emote -> assume the hero is new and thus give it a template emote
emote=constants.HERO_EMOTES.get(hero["id"]) or constants.NEW_HERO_EMOTE,
emote=hero_emotes.get(hero["id"]) or await self.create_hero_emote(hero["id"], hero["shortName"]),
)
for hero in heroes["data"]["constants"]["heroes"]
}
Expand Down Expand Up @@ -132,6 +141,27 @@ async def by_id(self, hero_id: int) -> Hero | PseudoHero:
else:
return await super().by_id(hero_id)

async def create_hero_emote(
self,
hero_id: int,
hero_short_name: str,
) -> str:
"""Create a new discord emote for a Dota 2 hero."""
try:
return await self.create_character_emote_helper(
character_id=hero_id,
table="dota_heroes_info",
emote_name=formats.convert_camel_case_to_PascalCase(hero_short_name),
emote_source_url=f"{CDN_REACT}/heroes/icons/{hero_short_name}.png", # copy of `minimap_icon_url` property
guild_id=const.EmoteGuilds.DOTA[3],
)
except Exception as exc:
embed = discord.Embed(
description=f"Something went wrong when creating hero emote for `id={hero_id}, name={hero_short_name}`."
)
await self.bot.exc_manager.register_error(exc, embed=embed)
return constants.NEW_HERO_EMOTE


class HeroTransformer(CharacterTransformer[Hero, PseudoHero]):
@override
Expand Down
42 changes: 41 additions & 1 deletion utils/fpc/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from bot import aluloop

from .. import const, fuzzy
from .. import const, errors, fuzzy

if TYPE_CHECKING:
from bot import AluBot
Expand Down Expand Up @@ -190,6 +190,46 @@ async def all(self) -> list[VT | PseudoVT]:
data = await self.get_cached_data()
return list(data.values())

async def create_character_emote_helper(
self,
*,
character_id: int,
table: str,
emote_name: str,
emote_source_url: str,
guild_id: int,
) -> str:
"""Helper function to create a new discord emote for a game character and remember it in the database."""

guild = self.bot.get_guild(guild_id)
if guild is None:
msg = f"Guild id={guild_id} is `None`."
raise errors.SomethingWentWrong(msg)

new_emote = await guild.create_custom_emoji(
name=emote_name,
image=await self.bot.transposer.url_to_bytes(emote_source_url),
)

# str(emote) is full emote representation, i.e. "<:AntiMage:1202019770787176529>"
query = f"INSERT INTO {table} (id, emote) VALUES ($1, $2)"
await self.bot.pool.execute(query, character_id, str(new_emote))

embed = (
discord.Embed(
colour=const.Colour.blueviolet,
title=f"New emote was added to `{table}` table.",
description=f'```py\n{new_emote.name} = "{new_emote}"```',
)
.add_field(
name=emote_name,
value=str(new_emote),
)
.set_footer(text=f"{character_id=}")
)
await self.bot.hideout.global_logs.send(embed=embed)
return str(new_emote)


class CharacterStorage(GameDataStorage[CharacterT, PseudoCharacterT]):
...
1 change: 1 addition & 0 deletions utils/links.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ async def get_metadata_embed_links(message: discord.Message) -> discord.Message
"twitch.tv": "https://fxtwitch.seria.moe",
"deviantart.com": "https://fixdeviantart.com",
"tumblr.com": "https://tpmblr.com",
"pixiv.net": "https://phixiv.net",
}
# PS. in November 2024 I finally found out that some other people made the same bot/feature for Discord:
# * https://betterdiscord.app/plugin/SocialMediaLinkConverter
Expand Down
Loading

0 comments on commit ee5e196

Please sign in to comment.