309 lines
9.9 KiB
Python
309 lines
9.9 KiB
Python
"""The OnThisDay module. Find out what happened on a certain day, in multiple different years in history."""
|
|
|
|
import asyncio
|
|
import datetime
|
|
import re
|
|
from random import choice
|
|
from typing import Dict, Iterable, Optional, Union
|
|
|
|
import aiohttp
|
|
import dateparser
|
|
import discord
|
|
from redbot.core import commands
|
|
from redbot.core.bot import Red
|
|
from redbot.core.commands import BadArgument, Context, Converter
|
|
from redbot.core.utils import get_end_user_data_statement
|
|
from redbot.core.utils.chat_formatting import inline, warning
|
|
|
|
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
|
|
|
|
ENDPOINT = "https://byabbe.se/on-this-day/{}/events.json"
|
|
|
|
DEFAULT_DESCRIPTION = """
|
|
Please send a valid year from the list of years below.
|
|
These are the most significant years for events occuring
|
|
on this day. Only years above 0 are included.
|
|
"""
|
|
|
|
MONTH_MAPPING = {
|
|
"1": "january",
|
|
"2": "february",
|
|
"3": "march",
|
|
"4": "april",
|
|
"5": "may",
|
|
"6": "june",
|
|
"7": "july",
|
|
"8": "august",
|
|
"9": "september",
|
|
"10": "october",
|
|
"11": "november",
|
|
"12": "december",
|
|
}
|
|
|
|
|
|
def highlight_numerical_data(string):
|
|
return re.sub(r"(([\d{1}],?)+)", r"**\1**", string)
|
|
|
|
|
|
def retrieve_above_0(year):
|
|
return year.strip().isdigit()
|
|
|
|
|
|
def columns(years):
|
|
m = f"{chr(12644)}\n" # padding
|
|
f = lambda s: inline(s.zfill(4))
|
|
for x in range(6, len(years), 6):
|
|
m += "\n" + (" " * 5).join(map(f, years[x - 6 : x]))
|
|
return m
|
|
|
|
|
|
def date_suffix(number) -> str:
|
|
number = int(number)
|
|
suffixes = {0: "th", 1: "st", 2: "nd", 3: "rd"}
|
|
for i in range(4, 10):
|
|
suffixes[i] = "th"
|
|
return str(number) + suffixes[int(str(number)[-1])]
|
|
|
|
|
|
def now() -> datetime.datetime:
|
|
return datetime.datetime.now()
|
|
|
|
|
|
def current_year() -> int:
|
|
return datetime.date.today().year
|
|
|
|
|
|
def yield_chunks(l, n):
|
|
for i in range(0, len(l), n):
|
|
yield l[i : i + n]
|
|
|
|
|
|
class DateConverter(Converter):
|
|
"""Date converter which uses dateparser.parse()."""
|
|
|
|
async def convert(self, ctx: Context, arg: str) -> datetime.datetime:
|
|
parsed = dateparser.parse(arg)
|
|
if parsed is None:
|
|
raise BadArgument("Unrecognized date/time.")
|
|
if parsed.strftime("%Y") != str(now().strftime("%Y")):
|
|
raise BadArgument("Please do not specify a year.")
|
|
return parsed
|
|
|
|
|
|
class YearDropdown(discord.ui.Select):
|
|
def __init__(self, otd: "OnThisDay", /):
|
|
self.otd = otd
|
|
cy = current_year()
|
|
options = [
|
|
discord.SelectOption(
|
|
label=year,
|
|
description=f"On this day, {cy - int(year)} years ago",
|
|
emoji="\N{CLOCK FACE THREE OCLOCK}",
|
|
)
|
|
for year in self.otd.year_data
|
|
if int(year) in self.otd.year_range
|
|
]
|
|
|
|
super().__init__(placeholder="Choose a year...", options=options)
|
|
|
|
async def callback(self, interaction: discord.Interaction):
|
|
await self.otd.display_events(
|
|
interaction,
|
|
year=self.values[0],
|
|
)
|
|
|
|
|
|
class YearDropdownView(discord.ui.View):
|
|
def __init__(self, otd: "OnThisDay"):
|
|
super().__init__()
|
|
self.add_item(YearDropdown(otd))
|
|
|
|
|
|
class YearRangeDropdown(discord.ui.Select):
|
|
"""This exists because the number of years dispatched always
|
|
exceeds the Select options cap of 25.
|
|
"""
|
|
|
|
YEAR_RANGES = [
|
|
range(1901),
|
|
range(1901, 1951),
|
|
range(1951, 2001),
|
|
range(2001, current_year() + 1),
|
|
]
|
|
|
|
YEAR_RANGES_HUMANIZED = ["0 - 1900", "1901 - 1950", "1951 - 2000", "2001 - present"]
|
|
|
|
def __init__(self, otd: "OnThisDay", /):
|
|
self.otd = otd
|
|
|
|
emojis = [
|
|
"\N{LARGE RED CIRCLE}",
|
|
"\N{LARGE ORANGE CIRCLE}",
|
|
"\N{LARGE YELLOW CIRCLE}",
|
|
"\N{LARGE GREEN CIRCLE}",
|
|
]
|
|
|
|
options = [
|
|
discord.SelectOption(
|
|
label=self.YEAR_RANGES_HUMANIZED[i],
|
|
value=i,
|
|
description=f"Get choice from {self.YEAR_RANGES_HUMANIZED[i].replace('-', 'to')}",
|
|
emoji=emojis[i],
|
|
)
|
|
for i in range(4)
|
|
]
|
|
|
|
super().__init__(placeholder="Choose a year range...", options=options)
|
|
|
|
async def callback(self, interaction: discord.Interaction):
|
|
self.otd.year_range = self.YEAR_RANGES[int(self.values[0])]
|
|
await interaction.response.edit_message(
|
|
content=f"Selected year range: **{self.YEAR_RANGES_HUMANIZED[int(self.values[0])]}**\nSelect a year from the list of available years.",
|
|
view=YearDropdownView(self.otd),
|
|
)
|
|
|
|
|
|
class YearRangeDropdownView(discord.ui.View):
|
|
def __init__(self, otd: "OnThisDay"):
|
|
super().__init__()
|
|
self.add_item(YearRangeDropdown(otd))
|
|
|
|
|
|
class ButtonView(discord.ui.View):
|
|
def __init__(self, buttons: dict[str, str]):
|
|
super().__init__()
|
|
for label, url in buttons.items():
|
|
self.add_item(discord.ui.Button(label=label, url=url))
|
|
|
|
|
|
class OnThisDay(commands.Cog):
|
|
"""Find out what happened on a certain day, in multiple different years in history."""
|
|
|
|
__version__ = "2.0.0"
|
|
__author__ = "Kreusada"
|
|
|
|
def __init__(self, bot: Red):
|
|
self.bot = bot
|
|
self.session = aiohttp.ClientSession()
|
|
self.date_number: Optional[str] = None
|
|
self.month_number: Optional[str] = None
|
|
self.year: Optional[str] = None
|
|
self.year_data = {}
|
|
self.year_range: Optional[range] = None
|
|
self.date_wiki: Optional[str] = None
|
|
|
|
def format_help_for_context(self, ctx: commands.Context) -> str:
|
|
context = super().format_help_for_context(ctx)
|
|
return f"{context}\n\nAuthor: {self.__author__}\nVersion: {self.__version__}"
|
|
|
|
async def red_delete_data_for_user(self, **kwargs):
|
|
"""Nothing to delete"""
|
|
return
|
|
|
|
async def cog_unload(self) -> None:
|
|
await self.session.close()
|
|
|
|
async def display_events(
|
|
self,
|
|
ctx: Union[commands.Context, discord.Interaction],
|
|
*,
|
|
year: str,
|
|
):
|
|
event = self.year_data[year]
|
|
years_ago = int(self.year) - int("".join(filter(str.isdigit, year)))
|
|
content = (
|
|
event["content"]
|
|
+ "\n\n"
|
|
+ f"This event occured on the __{date_suffix(self.date_number)} of {MONTH_MAPPING[self.month_number].capitalize()}, {year}__."
|
|
)
|
|
embed = discord.Embed(
|
|
title=f"On this day, {years_ago} years ago...",
|
|
description=highlight_numerical_data(content),
|
|
color=await self.bot.get_embed_colour(ctx.channel),
|
|
)
|
|
embed.set_footer(text="See the below links for related wikipedia articles")
|
|
_d = {
|
|
f"The year '{year}'": "https://en.wikipedia.org/wiki/" + year,
|
|
f"The date '{self.date_number.zfill(2)}/{self.month_number.zfill(2)}'": self.date_wiki,
|
|
}
|
|
embed.add_field(
|
|
name="Other significant events",
|
|
value="\n".join(f"- [{k}]({v})" for k, v in _d.items()),
|
|
inline=False,
|
|
)
|
|
wiki_dict = {w["title"]: w["wikipedia"] for w in event["wikipedia"]}
|
|
if isinstance(ctx, discord.Interaction):
|
|
send_method = ctx.response.edit_message
|
|
else:
|
|
send_method = ctx.send
|
|
await send_method(content=None, embed=embed, view=ButtonView(wiki_dict))
|
|
|
|
async def run_otd(
|
|
self,
|
|
ctx: commands.Context,
|
|
date: Optional[datetime.datetime],
|
|
random: bool = False,
|
|
):
|
|
self.cache_date(date)
|
|
try:
|
|
async with self.session.get(
|
|
ENDPOINT.format(f"{self.month_number}/{self.date_number}")
|
|
) as session:
|
|
if session.status != 200:
|
|
return await ctx.maybe_send_embed(
|
|
warning("An error occured whilst retrieving information for this day.")
|
|
)
|
|
content = await session.json()
|
|
except aiohttp.ClientError:
|
|
return await ctx.maybe_send_embed(
|
|
warning("An error occured whilst retrieving information for this day.")
|
|
)
|
|
else:
|
|
self.date_wiki = content["wikipedia"]
|
|
events = filter(lambda x: retrieve_above_0(x["year"]), content["events"])
|
|
data = {
|
|
e["year"]: {"content": e["description"], "wikipedia": e["wikipedia"]}
|
|
for e in events
|
|
}
|
|
self.year_data = data
|
|
if random:
|
|
await self.display_events(ctx, year=choice(list(data.keys())))
|
|
else:
|
|
await ctx.send(
|
|
"Choose a year range to select from:",
|
|
view=YearRangeDropdownView(self),
|
|
)
|
|
|
|
def cache_date(self, date: Optional[datetime.datetime], /) -> Dict[str, int]:
|
|
if date is None:
|
|
date = now()
|
|
|
|
self.month_number = date.strftime(r"%m").lstrip("0")
|
|
self.date_number = date.strftime(r"%d").lstrip("0")
|
|
self.year = date.strftime(r"%Y")
|
|
|
|
@commands.has_permissions(embed_links=True)
|
|
@commands.group(invoke_without_command=True, aliases=["otd"])
|
|
async def onthisday(self, ctx: commands.Context, *, date: DateConverter = None):
|
|
"""Find out what happened on this day, in various different years!
|
|
|
|
If you want to specify your own date, you can do so by using
|
|
`[p]onthisday [date]`.
|
|
You can also receive a random year by using `[p]onthisday random [day]`.
|
|
"""
|
|
await ctx.typing()
|
|
await self.run_otd(ctx, date=date, random=False)
|
|
|
|
@onthisday.command()
|
|
async def random(self, ctx: commands.Context, *, date: DateConverter = None):
|
|
"""Find out what happened on this day, in a random year.
|
|
|
|
If you want to specify your own date, you can do so by using
|
|
`[p]onthisday [date]`.
|
|
"""
|
|
await ctx.typing()
|
|
await self.run_otd(ctx, date=date, random=True)
|
|
|
|
|
|
async def setup(bot: Red):
|
|
await bot.add_cog(OnThisDay(bot))
|