From 550b63f60cded235dc751cf23daeff6362c6b534 Mon Sep 17 00:00:00 2001 From: qwewqa <198e559dbd446d973355f415bdfa34@gmail.com> Date: Fri, 25 Dec 2020 14:37:31 -0500 Subject: [PATCH] add event command --- main.py | 1 + miyu_bot/commands/cogs/event.py | 95 ++++++++++++++++++++ miyu_bot/commands/cogs/music.py | 95 ++++++++++---------- miyu_bot/commands/common/emoji.py | 41 +++++++++ miyu_bot/commands/common/formatting.py | 2 + miyu_bot/commands/common/fuzzy_matching.py | 13 ++- miyu_bot/commands/common/reaction_message.py | 29 +++--- 7 files changed, 212 insertions(+), 64 deletions(-) create mode 100644 miyu_bot/commands/cogs/event.py create mode 100644 miyu_bot/commands/common/emoji.py create mode 100644 miyu_bot/commands/common/formatting.py diff --git a/main.py b/main.py index 6051f04..7f40d50 100644 --- a/main.py +++ b/main.py @@ -15,6 +15,7 @@ bot = commands.Bot(command_prefix='!', case_insensitive=True) asset_manager = AssetManager('assets') bot.load_extension('miyu_bot.commands.cogs.card') +bot.load_extension('miyu_bot.commands.cogs.event') bot.load_extension('miyu_bot.commands.cogs.music') bot.load_extension('miyu_bot.commands.cogs.utility') diff --git a/miyu_bot/commands/cogs/event.py b/miyu_bot/commands/cogs/event.py new file mode 100644 index 0000000..69639bd --- /dev/null +++ b/miyu_bot/commands/cogs/event.py @@ -0,0 +1,95 @@ +import datetime +import logging + +import discord +from d4dj_utils.master.event_master import EventMaster +from discord.ext import commands + +from main import asset_manager +from miyu_bot.commands.common.emoji import attribute_emoji_by_id, unit_emoji_by_id, parameter_bonus_emoji_by_id, \ + event_point_emoji +from miyu_bot.commands.common.formatting import format_info +from miyu_bot.commands.common.fuzzy_matching import FuzzyMap, romanize + + +class Event(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.logger = logging.getLogger(__name__) + self.events = FuzzyMap( + lambda e: e.start_datetime < datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=-8) + ) + for e in asset_manager.event_master.values(): + self.events[e.name] = e + + @commands.command(name='event', + aliases=['ev'], + description='Finds the event with the given name.', + help='!event pkcooking') + async def event(self, ctx: commands.Context, *, arg: str): + self.logger.info(f'Searching for event "{arg}".') + + event: EventMaster + try: + event = asset_manager.event_master[int(arg)] + if event not in self.events.values(): + event = self.events[arg] + except (ValueError, KeyError): + event = self.events[arg] + + if not event: + msg = f'Failed to find event "{arg}".' + await ctx.send(msg) + self.logger.info(msg) + return + self.logger.info(f'Found event "{event}" ({romanize(event.name)}).') + + logo = discord.File(event.logo_path, filename='logo.png') + + embed = discord.Embed(title=event.name) + embed.set_thumbnail(url=f'attachment://logo.png') + + embed.add_field(name='Dates', + value=format_info({ + 'Start': event.start_datetime, + 'Close': event.reception_close_datetime, + 'Rank Fix': event.rank_fix_start_datetime, + 'Results': event.result_announcement_datetime, + 'End': event.end_datetime, + 'Story Unlock': event.story_unlock_datetime, + 'Status': event.state().name, + }), + inline=False) + embed.add_field(name='Event Type', + value=event.event_type.name, + inline=True) + embed.add_field(name='Bonus Characters', + value='\n'.join( + f'{self.bot.get_emoji(unit_emoji_by_id[char.unit_id])} {char.full_name_english}' + for char in event.bonus.characters + ), + inline=True) + embed.add_field(name='Bonus Attribute', + value=f'{self.bot.get_emoji(attribute_emoji_by_id[event.bonus.attribute_id])} ' + f'{event.bonus.attribute.en_name.capitalize()}' if event.bonus.attribute else 'None', + inline=True) + embed.add_field(name='Point Bonus', + value=format_info({ + 'Attribute': f'{self.bot.get_emoji(event_point_emoji)} +{event.bonus.attribute_match_point_bonus_value}%' if event.bonus.attribute_match_point_bonus_value else 'None', + 'Character': f'{self.bot.get_emoji(event_point_emoji)} +{event.bonus.character_match_point_bonus_value}%' if event.bonus.character_match_point_bonus_value else 'None', + 'Both': f'{self.bot.get_emoji(event_point_emoji)} +{event.bonus.all_match_point_bonus_value}%' if event.bonus.all_match_point_bonus_value else 'None', + }), + inline=True) + embed.add_field(name='Parameter Bonus', + value=format_info({ + 'Attribute': f'{self.bot.get_emoji(parameter_bonus_emoji_by_id[event.bonus.attribute_match_parameter_bonus_id])} +{event.bonus.attribute_match_parameter_bonus_value}%' if event.bonus.attribute_match_parameter_bonus_value else 'None', + 'Character': f'{self.bot.get_emoji(parameter_bonus_emoji_by_id[event.bonus.character_match_parameter_bonus_id])} +{event.bonus.attribute_match_parameter_bonus_value}%' if event.bonus.attribute_match_parameter_bonus_value else 'None', + 'Both': f'{self.bot.get_emoji(parameter_bonus_emoji_by_id[event.bonus.all_match_parameter_bonus_id])} +{event.bonus.all_match_parameter_bonus_value}%' if event.bonus.all_match_parameter_bonus_value else 'None', + }), + inline=True) + + await ctx.send(files=[logo], embed=embed) + + +def setup(bot): + bot.add_cog(Event(bot)) diff --git a/miyu_bot/commands/cogs/music.py b/miyu_bot/commands/cogs/music.py index f2a4673..c0c27b6 100644 --- a/miyu_bot/commands/cogs/music.py +++ b/miyu_bot/commands/cogs/music.py @@ -1,5 +1,6 @@ import asyncio import logging +from typing import Optional, Tuple import discord from d4dj_utils.master.chart_master import ChartDifficulty, ChartMaster @@ -8,6 +9,8 @@ from d4dj_utils.master.music_master import MusicMaster from discord.ext import commands from main import asset_manager +from miyu_bot.commands.common.emoji import difficulty_emoji_id +from miyu_bot.commands.common.formatting import format_info from miyu_bot.commands.common.fuzzy_matching import romanize, FuzzyMap from miyu_bot.commands.common.reaction_message import make_tabbed_message @@ -16,13 +19,9 @@ class Music(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot self.logger = logging.getLogger(__name__) - self.music = self.get_music() - - def get_music(self): - music = FuzzyMap(lambda m: m.is_released) + self.music = FuzzyMap(lambda m: m.is_released) for m in asset_manager.music_master.values(): - music[f'{m.name} {m.special_unit_name}'] = m - return music + self.music[f'{m.name} {m.special_unit_name}'] = m difficulty_names = { 'expert': ChartDifficulty.Expert, @@ -39,10 +38,6 @@ class Music(commands.Cog): 'es': ChartDifficulty.Easy, } - @staticmethod - def format_info(info_entries: dict): - return '\n'.join(f'{k}: {v}' for k, v in info_entries.items() if v) - @commands.command(name='song', aliases=['music'], description='Finds the song with the given name.', @@ -50,13 +45,14 @@ class Music(commands.Cog): async def song(self, ctx: commands.Context, *, arg: str): self.logger.info(f'Searching for song "{arg}".') - song: MusicMaster = self.music[arg] + song = self.get_song(arg) + if not song: msg = f'Failed to find song "{arg}".' await ctx.send(msg) self.logger.info(msg) return - self.logger.info(f'Found "{song}" ({romanize(song.name)[1]}).') + self.logger.info(f'Found song "{song}" ({romanize(song.name)}).') thumb = discord.File(song.jacket_path, filename='jacket.png') @@ -81,10 +77,10 @@ class Music(commands.Cog): } embed.add_field(name='Artist', - value=self.format_info(artist_info), + value=format_info(artist_info), inline=False) embed.add_field(name='Info', - value=self.format_info(music_info), + value=format_info(music_info), inline=False) await ctx.send(files=[thumb], embed=embed) @@ -96,35 +92,28 @@ class Music(commands.Cog): async def chart(self, ctx: commands.Context, *, arg: str): self.logger.info(f'Searching for chart "{arg}".') - split_args = arg.split() + name, difficulty = self.parse_chart_args(arg) + song = self.get_song(name) - difficulty = ChartDifficulty.Expert - if len(split_args) >= 2: - final_word = split_args[-1] - if final_word in self.difficulty_names: - difficulty = self.difficulty_names[final_word] - arg = ''.join(split_args[:-1]) - - song: MusicMaster = self.music[arg] if not song: - msg = f'Failed to find chart "{arg}".' + msg = f'Failed to find chart "{name}".' await ctx.send(msg) self.logger.info(msg) return - self.logger.info(f'Found "{song}" ({romanize(song.name)[1]}).') + self.logger.info(f'Found song "{song}" ({romanize(song.name)}).') embeds, files = self.get_chart_embed_info(song) message = await ctx.send(files=files, embed=embeds[difficulty - 1]) - reaction_emote_ids = [ + reaction_emoji_ids = [ 790050636568723466, 790050636489555998, 790050636548276252, 790050636225052694, ] - asyncio.ensure_future(make_tabbed_message(ctx, message, reaction_emote_ids, embeds)) + asyncio.ensure_future(make_tabbed_message(ctx, message, reaction_emoji_ids, embeds)) @commands.command(name='sections', aliases=['mixes'], @@ -133,18 +122,11 @@ class Music(commands.Cog): async def sections(self, ctx: commands.Context, *, arg: str): self.logger.info(f'Searching for chart sections "{arg}".') - split_args = arg.split() + name, difficulty = self.parse_chart_args(arg) + song = self.get_song(name) - difficulty = ChartDifficulty.Expert - if len(split_args) >= 2: - final_word = split_args[-1] - if final_word in self.difficulty_names: - difficulty = self.difficulty_names[final_word] - arg = ''.join(split_args[:-1]) - - song: MusicMaster = self.music[arg] if not song: - msg = f'Failed to find chart "{arg}".' + msg = f'Failed to find chart "{name}".' await ctx.send(msg) self.logger.info(msg) return @@ -153,20 +135,15 @@ class Music(commands.Cog): await ctx.send(msg) self.logger.info(msg) return - self.logger.info(f'Found "{song}" ({romanize(song.name)[1]}).') + self.logger.info(f'Found song "{song}" ({romanize(song.name)}).') embeds, files = self.get_mix_embed_info(song) message = await ctx.send(files=files, embed=embeds[difficulty - 1]) - reaction_emote_ids = [ - 790050636568723466, - 790050636489555998, - 790050636548276252, - 790050636225052694, - ] + reaction_emoji_ids = difficulty_emoji_id.values() - asyncio.ensure_future(make_tabbed_message(ctx, message, reaction_emote_ids, embeds)) + asyncio.ensure_future(make_tabbed_message(ctx, message, reaction_emoji_ids, embeds)) def get_chart_embed_info(self, song): embeds = [] @@ -245,16 +222,16 @@ class Music(commands.Cog): } embed.add_field(name='Info', - value=self.format_info(info), + value=format_info(info), inline=False) embed.add_field(name='Begin', - value=self.format_info(begin), + value=format_info(begin), inline=True) embed.add_field(name='Middle', - value=self.format_info(middle), + value=format_info(middle), inline=True) embed.add_field(name='End', - value=self.format_info(end), + value=format_info(end), inline=True) embed.set_footer(text='1 column = 10 seconds') @@ -262,6 +239,26 @@ class Music(commands.Cog): return embeds, files + def parse_chart_args(self, arg: str) -> Tuple[str, ChartDifficulty]: + split_args = arg.split() + + difficulty = ChartDifficulty.Expert + if len(split_args) >= 2: + final_word = split_args[-1] + if final_word in self.difficulty_names: + difficulty = self.difficulty_names[final_word] + arg = ''.join(split_args[:-1]) + return arg, difficulty + + def get_song(self, name_or_id: str) -> Optional[MusicMaster]: + try: + song = asset_manager.music_master[int(name_or_id)] + if song not in self.music.values(): + song = self.music[name_or_id] + return song + except (KeyError, ValueError): + return self.music[name_or_id] + def setup(bot): bot.add_cog(Music(bot)) diff --git a/miyu_bot/commands/common/emoji.py b/miyu_bot/commands/common/emoji.py new file mode 100644 index 0000000..45c4165 --- /dev/null +++ b/miyu_bot/commands/common/emoji.py @@ -0,0 +1,41 @@ +from d4dj_utils.master.chart_master import ChartDifficulty + +difficulty_emoji_id = { + ChartDifficulty.Easy: 790050636568723466, + ChartDifficulty.Normal: 790050636489555998, + ChartDifficulty.Hard: 790050636548276252, + ChartDifficulty.Expert: 790050636225052694, +} + +# \:buff_power: \:buff_heart: \:buff_technique: \:buff_physical: +parameter_bonus_emoji = { + 'all': 792095555634331668, + 'heart': 792096971040620564, + 'technique': 792096971090558986, + 'physical': 792096971002216488, +} + +parameter_bonus_emoji_by_id = {i: v for i, v in enumerate(parameter_bonus_emoji.values())} + +unit_emoji = { + 'happy_around': 792069679442821121, + 'peaky_pkey': 792076165916524544, + 'photon_maiden': 792069679455535136, + 'merm4id': 792069679874310184, + 'rondo': 792069679770238976, + 'lyrical_lily': 792069679673114644, +} + +unit_emoji_by_id = {i + 1: v for i, v in enumerate(unit_emoji.values())} + +attribute_emoji = { + 'street': 791903477986361345, + 'party': 791903477999599677, + 'cute': 791903477743616003, + 'cool': 791903477700755466, + 'elegant': 791903477969321985, +} + +attribute_emoji_by_id = {i + 1: v for i, v in enumerate(attribute_emoji.values())} + +event_point_emoji = 792097816931598336 diff --git a/miyu_bot/commands/common/formatting.py b/miyu_bot/commands/common/formatting.py new file mode 100644 index 0000000..f0669cb --- /dev/null +++ b/miyu_bot/commands/common/formatting.py @@ -0,0 +1,2 @@ +def format_info(info_entries: dict): + return '\n'.join(f'{k}: {v}' for k, v in info_entries.items() if v) \ No newline at end of file diff --git a/miyu_bot/commands/common/fuzzy_matching.py b/miyu_bot/commands/common/fuzzy_matching.py index b3ea293..f051489 100644 --- a/miyu_bot/commands/common/fuzzy_matching.py +++ b/miyu_bot/commands/common/fuzzy_matching.py @@ -17,7 +17,7 @@ class FuzzyMap: self.logger = logging.getLogger(__name__) def values(self): - return (v for v in self._values.values() if self.filter(v)) + return FuzzyDictValuesView(self) def __delitem__(self, key): k = romanize(key) @@ -42,6 +42,17 @@ class FuzzyMap: return self._values[result] +class FuzzyDictValuesView: + def __init__(self, map: FuzzyMap): + self._map = map + + def __contains__(self, item): + return item in self._map._values.values() and self._map.filter(item) + + def __iter__(self): + yield from (v for v in self._map._values.values() if self._map.filter(v)) + + @dataclass class FuzzyMatchConfig: base_score: float = 0.0 diff --git a/miyu_bot/commands/common/reaction_message.py b/miyu_bot/commands/common/reaction_message.py index 947aece..496457d 100644 --- a/miyu_bot/commands/common/reaction_message.py +++ b/miyu_bot/commands/common/reaction_message.py @@ -1,32 +1,33 @@ import asyncio from typing import List, Callable, Awaitable -from discord import Message, Embed +from discord import Message, Embed, Emoji from discord.ext.commands import Context -async def make_tabbed_message(ctx: Context, message: Message, emote_ids: List[int], embeds: List[Embed], timeout=300): - async def callback(index, _ctx, _message): - await message.edit(embed=embeds[index]) +async def make_tabbed_message(ctx: Context, message: Message, emoji_ids: List[int], embeds: List[Embed], timeout=300): + emoji_ids = list(emoji_ids) - await make_reaction_message(ctx, message, emote_ids, callback, timeout) + async def callback(emoji_id, _ctx, _message): + await message.edit(embed=embeds[emoji_ids.index(emoji_id)]) + await make_reaction_message(ctx, message, emoji_ids, callback, timeout) -async def make_reaction_message(ctx: Context, message: Message, emote_ids: List[int], - callback: Callable[[int, Context, Message], Awaitable[None]], timeout = 300): - for emote_id in emote_ids: - await message.add_reaction(ctx.bot.get_emoji(emote_id)) + +async def make_reaction_message(ctx: Context, message: Message, emoji_ids: List[int], + callback: Callable[[int, Context, Message], Awaitable[None]], timeout=300): + for emoji_id in emoji_ids: + await message.add_reaction(ctx.bot.get_emoji(emoji_id)) def check(rxn, usr): - return usr == ctx.author and rxn.emoji.id in emote_ids and rxn.message.id == message.id + return usr == ctx.author and rxn.emoji.id in emoji_ids and rxn.message.id == message.id while True: try: reaction, user = await ctx.bot.wait_for('reaction_add', timeout=timeout, check=check) - emote_index = emote_ids.index(reaction.emoji.id) - await callback(emote_index, ctx, message) + await callback(reaction.emoji.id, ctx, message) await message.remove_reaction(reaction, user) except asyncio.TimeoutError: - for emote_id in emote_ids: - await message.remove_reaction(ctx.bot.get_emoji(emote_id), ctx.bot.user) + for emoji_id in emoji_ids: + await message.remove_reaction(ctx.bot.get_emoji(emoji_id), ctx.bot.user) break