minor fixes

pull/1/head
qwewqa 4 years ago
parent ea31a2e25c
commit 9fb92c449c
  1. 57
      miyu_bot/commands/cogs/event.py
  2. 45
      miyu_bot/commands/cogs/music.py
  3. 4
      miyu_bot/commands/common/fuzzy_matching.py
  4. 25
      miyu_bot/commands/common/master_asset_manager.py
  5. 10
      miyu_bot/commands/common/reaction_message.py

@ -1,16 +1,18 @@
import datetime import datetime as dt
import logging import logging
import discord import discord
import pytz
from d4dj_utils.master.event_master import EventMaster, EventState from d4dj_utils.master.event_master import EventMaster, EventState
from discord.ext import commands from discord.ext import commands
from pytz import UnknownTimeZoneError
from main import asset_manager from main import asset_manager
from miyu_bot.commands.common.emoji import attribute_emoji_ids_by_attribute_id, unit_emoji_ids_by_unit_id, \ from miyu_bot.commands.common.emoji import attribute_emoji_ids_by_attribute_id, unit_emoji_ids_by_unit_id, \
parameter_bonus_emoji_ids_by_parameter_id, \ parameter_bonus_emoji_ids_by_parameter_id, \
event_point_emoji_id event_point_emoji_id
from miyu_bot.commands.common.formatting import format_info from miyu_bot.commands.common.formatting import format_info
from miyu_bot.commands.common.fuzzy_matching import FuzzyFilteredMap, romanize from miyu_bot.commands.common.fuzzy_matching import romanize
from miyu_bot.commands.common.master_asset_manager import MasterAssetManager from miyu_bot.commands.common.master_asset_manager import MasterAssetManager
@ -21,7 +23,8 @@ class Event(commands.Cog):
self.events = MasterAssetManager( self.events = MasterAssetManager(
asset_manager.event_master, asset_manager.event_master,
naming_function=lambda e: e.name, naming_function=lambda e: e.name,
filter_function=lambda e: e.start_datetime < datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=8), filter_function=lambda e: e.start_datetime < dt.datetime.now(
dt.timezone.utc) + dt.timedelta(hours=12),
) )
@commands.command(name='event', @commands.command(name='event',
@ -44,7 +47,11 @@ class Event(commands.Cog):
return return
self.logger.info(f'Found event "{event}" ({romanize(event.name)}).') self.logger.info(f'Found event "{event}" ({romanize(event.name)}).')
logo = discord.File(event.logo_path, filename='logo.png') try:
logo = discord.File(event.logo_path, filename='logo.png')
except FileNotFoundError:
# Just a fallback
logo = discord.File(self.events.get('1', ctx).logo_path, filename='logo.png')
embed = discord.Embed(title=event.name) embed = discord.Embed(title=event.name)
embed.set_thumbnail(url=f'attachment://logo.png') embed.set_thumbnail(url=f'attachment://logo.png')
@ -98,6 +105,29 @@ class Event(commands.Cog):
await ctx.send(files=[logo], embed=embed) await ctx.send(files=[logo], embed=embed)
@commands.command(name='time',
aliases=[],
description='Displays the current time',
help='!time')
async def time(self, ctx: commands.Context, *, arg=''):
embed = discord.Embed(title='Time')
def format_time(t: dt.datetime):
return str(t.replace(microsecond=0))
embed.add_field(name='Asia/Tokyo', value=format_time(dt.datetime.now(pytz.timezone('Asia/Tokyo'))), inline=False)
if arg:
try:
embed.add_field(name=arg, value=format_time(dt.datetime.now(pytz.timezone(arg))), inline=False)
except UnknownTimeZoneError:
await ctx.send(content=f'Invalid timezone "{arg}".')
return
else:
embed.add_field(name='UTC', value=format_time(dt.datetime.now(dt.timezone.utc)), inline=False)
await ctx.send(embed=embed)
@commands.command(name='timeleft', @commands.command(name='timeleft',
aliases=['tl', 'time_left'], aliases=['tl', 'time_left'],
description='Displays the time left in the current event', description='Displays the time left in the current event',
@ -116,28 +146,28 @@ class Event(commands.Cog):
if state == EventState.Upcoming: if state == EventState.Upcoming:
time_delta_heading = 'Time Until Start' time_delta_heading = 'Time Until Start'
time_delta = latest.start_datetime - datetime.datetime.now(datetime.timezone.utc) time_delta = latest.start_datetime - dt.datetime.now(dt.timezone.utc)
date_heading = 'Start Date' date_heading = 'Start Date'
date_value = latest.start_datetime date_value = latest.start_datetime
elif state == EventState.Open: elif state == EventState.Open:
time_delta_heading = 'Time Until Close' time_delta_heading = 'Time Until Close'
time_delta = latest.reception_close_datetime - datetime.datetime.now(datetime.timezone.utc) time_delta = latest.reception_close_datetime - dt.datetime.now(dt.timezone.utc)
progress = 1 - (time_delta / (latest.reception_close_datetime - latest.start_datetime)) progress = 1 - (time_delta / (latest.reception_close_datetime - latest.start_datetime))
date_heading = 'Close Date' date_heading = 'Close Date'
date_value = latest.reception_close_datetime date_value = latest.reception_close_datetime
elif state in (EventState.Closing, EventState.Ranks_Fixed): elif state in (EventState.Closing, EventState.Ranks_Fixed):
time_delta_heading = 'Time Until Results' time_delta_heading = 'Time Until Results'
time_delta = latest.result_announcement_datetime - datetime.datetime.now(datetime.timezone.utc) time_delta = latest.result_announcement_datetime - dt.datetime.now(dt.timezone.utc)
date_heading = 'Results Date' date_heading = 'Results Date'
date_value = latest.result_announcement_datetime date_value = latest.result_announcement_datetime
elif state == EventState.Results: elif state == EventState.Results:
time_delta_heading = 'Time Until End' time_delta_heading = 'Time Until End'
time_delta = latest.end_datetime - datetime.datetime.now(datetime.timezone.utc) time_delta = latest.end_datetime - dt.datetime.now(dt.timezone.utc)
date_heading = 'End Date' date_heading = 'End Date'
date_value = latest.end_datetime date_value = latest.end_datetime
else: else:
time_delta_heading = 'Time Since End' time_delta_heading = 'Time Since End'
time_delta = datetime.datetime.now(datetime.timezone.utc) - latest.end_datetime time_delta = dt.datetime.now(dt.timezone.utc) - latest.end_datetime
date_heading = 'End Date' date_heading = 'End Date'
date_value = latest.end_datetime date_value = latest.end_datetime
@ -160,10 +190,15 @@ class Event(commands.Cog):
def get_latest_event(self, ctx: commands.Context) -> EventMaster: def get_latest_event(self, ctx: commands.Context) -> EventMaster:
"""Returns the oldest event that has not ended or the newest event otherwise.""" """Returns the oldest event that has not ended or the newest event otherwise."""
try: try:
return min((v for v in self.events.values(ctx) if v.state() < EventState.Ended), # NY event overlapped with previous event
return min((v for v in self.events.values(ctx) if v.state() == EventState.Open),
key=lambda e: e.start_datetime) key=lambda e: e.start_datetime)
except ValueError: except ValueError:
return max(self.events.values(ctx), key=lambda v: v.start_datetime) try:
return min((v for v in self.events.values(ctx) if v.state() < EventState.Ended),
key=lambda e: e.start_datetime)
except ValueError:
return max(self.events.values(ctx), key=lambda v: v.start_datetime)
def setup(bot): def setup(bot):

@ -26,6 +26,7 @@ class Music(commands.Cog):
asset_manager.music_master, asset_manager.music_master,
naming_function=lambda m: f'{m.name} {m.special_unit_name}', naming_function=lambda m: f'{m.name} {m.special_unit_name}',
filter_function=lambda m: m.is_released, filter_function=lambda m: m.is_released,
fallback_naming_function=lambda m: m.id,
) )
@property @property
@ -63,7 +64,11 @@ class Music(commands.Cog):
return return
self.logger.info(f'Found song "{song}" ({romanize(song.name)}).') self.logger.info(f'Found song "{song}" ({romanize(song.name)}).')
thumb = discord.File(song.jacket_path, filename='jacket.png') try:
thumb = discord.File(song.jacket_path, filename='jacket.png')
except FileNotFoundError:
# dig delight is just a fallback
thumb = discord.File(self.music.get('110001', ctx).jacket_path, filename='jacket.png')
embed = discord.Embed(title=song.name) embed = discord.Embed(title=song.name)
embed.set_thumbnail(url=f'attachment://jacket.png') embed.set_thumbnail(url=f'attachment://jacket.png')
@ -150,26 +155,42 @@ class Music(commands.Cog):
aliases=['songsearch', 'song_search'], aliases=['songsearch', 'song_search'],
description='Finds songs matching the given name.', description='Finds songs matching the given name.',
help='!songs grgr') help='!songs grgr')
async def songs(self, ctx: commands.Context, *, arg: str = ""): async def songs(self, ctx: commands.Context, *, arg: str = ''):
if arg: self.logger.info(f'Searching for songs "{arg}".' if arg else 'Listing songs.')
self.logger.info(f'Searching for songs "{arg}".') sort = 'relevance'
songs = self.music.get_sorted(arg, ctx) if not arg:
sort = 'default'
elif arg == 'duration':
sort = 'duration'
arg = ''
songs = self.music.get_sorted(arg, ctx)
if sort == 'relevance':
listing = [f'{song.name}{" (" + song.special_unit_name + ")" if song.special_unit_name else ""}' for song in listing = [f'{song.name}{" (" + song.special_unit_name + ")" if song.special_unit_name else ""}' for song in
songs] songs]
asyncio.ensure_future(run_paged_message(ctx, f'Song Search "{arg}"', listing)) elif sort == 'duration':
songs = sorted(songs, key=lambda s: self.get_music_duration(s))
listing = [
f'{self.format_duration(self.get_music_duration(song))} {song.name}{" (" + song.special_unit_name + ")" if song.special_unit_name else ""}'
for song in songs]
else: else:
self.logger.info('Listing songs.') songs = sorted(songs, key=lambda s: -s.default_order)
songs = sorted(self.music.values(ctx), key=lambda m: -m.default_order)
songs = [*songs[1:], songs[0]] # lesson is always first
listing = [f'{song.name}{" (" + song.special_unit_name + ")" if song.special_unit_name else ""}' for song in listing = [f'{song.name}{" (" + song.special_unit_name + ")" if song.special_unit_name else ""}' for song in
songs] [*songs[1:], songs[0]]] # lesson is always first
asyncio.ensure_future(run_paged_message(ctx, f'All Songs', listing)) asyncio.ensure_future(run_paged_message(ctx, f'Song Search "{arg}"' if arg else 'Songs', listing))
return return
def get_chart_embed_info(self, song): def get_chart_embed_info(self, song):
embeds = [] embeds = []
files = [discord.File(song.jacket_path, filename=f'jacket.png')]
try:
thumb = discord.File(song.jacket_path, filename='jacket.png')
except FileNotFoundError:
# dig delight is just a fallback
thumb = discord.File(self.music.get('110001', None).jacket_path, filename='jacket.png')
files = [thumb]
for difficulty in [ChartDifficulty.Easy, ChartDifficulty.Normal, ChartDifficulty.Hard, ChartDifficulty.Expert]: for difficulty in [ChartDifficulty.Easy, ChartDifficulty.Normal, ChartDifficulty.Hard, ChartDifficulty.Expert]:
chart = song.charts[difficulty] chart = song.charts[difficulty]
embed = discord.Embed(title=f'{song.name} [{chart.difficulty.name}]') embed = discord.Embed(title=f'{song.name} [{chart.difficulty.name}]')

@ -107,6 +107,7 @@ class FuzzyMatchConfig:
('v', 'b'): 0.0, ('v', 'b'): 0.0,
('l', 'r'): 0.0, ('l', 'r'): 0.0,
('c', 'k'): 0.0, ('c', 'k'): 0.0,
('y', 'i'): 0.4,
}) })
word_match_weight: float = -0.2 word_match_weight: float = -0.2
whole_match_weight: float = -0.25 whole_match_weight: float = -0.25
@ -129,7 +130,8 @@ class FuzzyMatcher:
self.array[0][i] = i * self.config.insertion_weight self.array[0][i] = i * self.config.insertion_weight
def score(self, source: str, target: str, threshold=0.0): def score(self, source: str, target: str, threshold=0.0):
# target must not be empty if not target:
return 1
l_src = len(source) l_src = len(source)
l_tgt = len(target) l_tgt = len(target)

@ -1,4 +1,4 @@
from typing import Callable, Any from typing import Callable, Any, Optional
from d4dj_utils.master.master_asset import MasterDict from d4dj_utils.master.master_asset import MasterDict
from discord.ext import commands from discord.ext import commands
@ -7,19 +7,22 @@ from miyu_bot.commands.common.fuzzy_matching import FuzzyFilteredMap
class MasterAssetManager: class MasterAssetManager:
def __init__(self, masters: MasterDict, naming_function: Callable[[Any], str], filter_function=lambda _: True): def __init__(self, masters: MasterDict, naming_function: Callable[[Any], str], filter_function=lambda _: True,
fallback_naming_function: Optional[Callable[[Any], str]] = None):
self.masters = masters self.masters = masters
self.fuzzy_map = FuzzyFilteredMap(filter_function) self.fuzzy_map = FuzzyFilteredMap(filter_function)
self.unfiltered_fuzzy_map = FuzzyFilteredMap() self.unfiltered_fuzzy_map = FuzzyFilteredMap()
for master in masters.values(): for master in masters.values():
name = naming_function(master) name = naming_function(master)
if self.fuzzy_map.has_exact(name) and fallback_naming_function:
name = fallback_naming_function(master)
if self.fuzzy_map.has_exact(name): if self.fuzzy_map.has_exact(name):
continue continue
self.fuzzy_map[name] = master self.fuzzy_map[name] = master
self.unfiltered_fuzzy_map[name] = master self.unfiltered_fuzzy_map[name] = master
def get(self, name_or_id: str, ctx: commands.Context): def get(self, name_or_id: str, ctx: Optional[commands.Context]):
if ctx.channel.id in no_filter_channels: if ctx and ctx.channel.id in no_filter_channels:
try: try:
return self.masters[int(name_or_id)] return self.masters[int(name_or_id)]
except (KeyError, ValueError): except (KeyError, ValueError):
@ -34,10 +37,16 @@ class MasterAssetManager:
return self.fuzzy_map[name_or_id] return self.fuzzy_map[name_or_id]
def get_sorted(self, name: str, ctx: commands.Context): def get_sorted(self, name: str, ctx: commands.Context):
if ctx.channel.id in no_filter_channels: if name:
return self.unfiltered_fuzzy_map.get_sorted(name) if ctx.channel.id in no_filter_channels:
return self.unfiltered_fuzzy_map.get_sorted(name)
else:
return self.fuzzy_map.get_sorted(name)
else: else:
return self.fuzzy_map.get_sorted(name) if ctx.channel.id in no_filter_channels:
return list(self.unfiltered_fuzzy_map.values())
else:
return list(self.fuzzy_map.values())
def values(self, ctx: commands.Context): def values(self, ctx: commands.Context):
if ctx.channel.id in no_filter_channels: if ctx.channel.id in no_filter_channels:
@ -46,4 +55,4 @@ class MasterAssetManager:
return self.fuzzy_map.values() return self.fuzzy_map.values()
no_filter_channels = {790033228600705048, 790033272376918027} no_filter_channels = {790033228600705048, 790033272376918027, 795640603114864640}

@ -15,7 +15,7 @@ async def run_tabbed_message(ctx: Context, message: Message, emojis: List[Emoji]
await run_reaction_message(ctx, message, emojis, callback, timeout) await run_reaction_message(ctx, message, emojis, callback, timeout)
async def run_paged_message(ctx: Context, title: str, content: List[str], page_size: int = 15, async def run_paged_message(ctx: Context, title: str, content: List[str], page_size: int = 15, numbered: bool = True,
timeout=300, double_arrow_threshold=4): timeout=300, double_arrow_threshold=4):
if not content: if not content:
embed = discord.Embed(title=title).set_footer(text='Page 0/0') embed = discord.Embed(title=title).set_footer(text='Page 0/0')
@ -25,14 +25,18 @@ async def run_paged_message(ctx: Context, title: str, content: List[str], page_s
page_contents = [content[i:i + page_size] for i in range(0, len(content), page_size)] page_contents = [content[i:i + page_size] for i in range(0, len(content), page_size)]
item_number = 0 item_number = 0
max_item_number_length = len(str(len(content)))
def format_item(item): def format_item(item):
nonlocal item_number nonlocal item_number
item_number += 1 item_number += 1
return f'{item_number}. {item}' if numbered:
return f'{item_number}.{" " * (max_item_number_length - len(str(item_number)))} {item}'
else:
return str(item)
embeds = [ embeds = [
discord.Embed(title=title, description='\n'.join((format_item(i) for i in page))).set_footer( discord.Embed(title=title, description='```' + '\n'.join((format_item(i) for i in page)) + '```').set_footer(
text=f'Page {i + 1}/{len(page_contents)}') text=f'Page {i + 1}/{len(page_contents)}')
for i, page in enumerate(page_contents)] for i, page in enumerate(page_contents)]

Loading…
Cancel
Save