minor fixes
This commit is contained in:
parent
ea31a2e25c
commit
9fb92c449c
miyu_bot/commands
@ -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…
x
Reference in New Issue
Block a user