[Timezone] Add fuzzy timezone searching (#231)
* Add fuzzy timezone searching * remove unused imports * Cleaner code * Adjustments * Adjustments Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
This commit is contained in:
@@ -14,4 +14,4 @@
|
||||
],
|
||||
"type": "COG",
|
||||
"end_user_data_statement": "This cog stores data provided by users for the express purpose of redisplaying. It does not store user data which was not provided through a command. Users may remove their own content without making a data removal request. This cog does not support data requests, but will respect deletion requests."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import discord
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
from pytz import common_timezones
|
||||
from pytz import country_timezones
|
||||
from typing import Optional, Literal, Tuple, Union
|
||||
from redbot.core import Config, commands, checks
|
||||
from fuzzywuzzy import fuzz, process
|
||||
from typing import Optional, Literal
|
||||
from redbot.core import Config, commands
|
||||
from redbot.core.utils.chat_formatting import pagify
|
||||
from redbot.core.utils.menus import close_menu, menu, DEFAULT_CONTROLS
|
||||
|
||||
|
||||
__version__ = "2.1.0"
|
||||
|
||||
|
||||
class Timezone(commands.Cog):
|
||||
"""Gets times across the world..."""
|
||||
|
||||
async def red_delete_data_for_user(
|
||||
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
|
||||
):
|
||||
await self.config.user_from_id(user_id).clear()
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, 278049241001, force_registration=True)
|
||||
default_user = {"usertime": None}
|
||||
self.config.register_user(**default_user)
|
||||
|
||||
async def red_delete_data_for_user(
|
||||
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
|
||||
):
|
||||
await self.config.user_from_id(user_id).clear()
|
||||
|
||||
async def get_usertime(self, user: discord.User):
|
||||
tz = None
|
||||
@@ -29,120 +32,138 @@ class Timezone(commands.Cog):
|
||||
|
||||
return usertime, tz
|
||||
|
||||
def fuzzy_timezone_search(self, tz: str):
|
||||
fuzzy_results = process.extract(tz.replace(" ", "_"), pytz.common_timezones, limit=500, scorer=fuzz.partial_ratio)
|
||||
matches = [x for x in fuzzy_results if x[1] > 98]
|
||||
return matches
|
||||
|
||||
async def format_results(self, ctx, tz):
|
||||
if not tz:
|
||||
await ctx.send(
|
||||
"Error: Incorrect format or no matching timezones found.\n"
|
||||
"Use: **Continent/City** with correct capitals or a partial timezone name to search. "
|
||||
"e.g. `America/New_York` or `New York`\nSee the full list of supported timezones here:\n"
|
||||
"<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
|
||||
)
|
||||
return None
|
||||
elif len(tz) == 1:
|
||||
# command specific response, so don't do anything here
|
||||
return tz
|
||||
else:
|
||||
msg = ""
|
||||
for timezone in tz:
|
||||
msg += f"{timezone[0]}\n"
|
||||
|
||||
embed_list = []
|
||||
for page in pagify(msg, delims=["\n"], page_length=500):
|
||||
e = discord.Embed(title=f"{len(tz)} results, please be more specific.", description=page)
|
||||
e.set_footer(text="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones")
|
||||
embed_list.append(e)
|
||||
if len(embed_list) == 1:
|
||||
close_control = {"\N{CROSS MARK}": close_menu}
|
||||
await menu(ctx, embed_list, close_control)
|
||||
else:
|
||||
await menu(ctx, embed_list, DEFAULT_CONTROLS)
|
||||
return None
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.group()
|
||||
async def time(self, ctx):
|
||||
"""
|
||||
Checks the time.
|
||||
Checks the time.
|
||||
|
||||
For the list of supported timezones, see here:
|
||||
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
For the list of supported timezones, see here:
|
||||
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
"""
|
||||
pass
|
||||
|
||||
@time.command()
|
||||
async def tz(self, ctx, *, tz: Optional[str] = None):
|
||||
async def tz(self, ctx, *, timezone_name: Optional[str] = None):
|
||||
"""Gets the time in any timezone."""
|
||||
try:
|
||||
if tz is None:
|
||||
time = datetime.now()
|
||||
fmt = "**%H:%M** %d-%B-%Y"
|
||||
await ctx.send(f"Current system time: {time.strftime(fmt)}")
|
||||
else:
|
||||
if "'" in tz:
|
||||
tz = tz.replace("'", "")
|
||||
if len(tz) > 4 and "/" not in tz:
|
||||
await ctx.send(
|
||||
"Error: Incorrect format. Use:\n **Continent/City** with correct capitals. "
|
||||
"e.g. `America/New_York`\n See the full list of supported timezones here:\n "
|
||||
"<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
|
||||
)
|
||||
else:
|
||||
tz = tz.title() if "/" in tz else tz.upper()
|
||||
if tz not in common_timezones:
|
||||
raise Exception(tz)
|
||||
fmt = "**%H:%M** %d-%B-%Y **%Z (UTC %z)**"
|
||||
time = datetime.now(pytz.timezone(tz))
|
||||
await ctx.send(time.strftime(fmt))
|
||||
except Exception as e:
|
||||
await ctx.send(f"**Error:** {str(e)} is an unsupported timezone.")
|
||||
if timezone_name is None:
|
||||
time = datetime.now()
|
||||
fmt = "**%H:%M** %d-%B-%Y"
|
||||
await ctx.send(f"Current system time: {time.strftime(fmt)}")
|
||||
else:
|
||||
tz_results = self.fuzzy_timezone_search(timezone_name)
|
||||
tz_resp = await self.format_results(ctx, tz_results)
|
||||
if tz_resp:
|
||||
time = datetime.now(pytz.timezone(tz_resp[0][0]))
|
||||
fmt = "**%H:%M** %d-%B-%Y **%Z (UTC %z)**"
|
||||
await ctx.send(time.strftime(fmt))
|
||||
|
||||
@time.command()
|
||||
async def iso(self, ctx, *, code=None):
|
||||
async def iso(self, ctx, *, iso_code=None):
|
||||
"""Looks up ISO3166 country codes and gives you a supported timezone."""
|
||||
if code is None:
|
||||
if iso_code is None:
|
||||
await ctx.send("That doesn't look like a country code!")
|
||||
else:
|
||||
exist = True if code in country_timezones else False
|
||||
exist = True if iso_code.upper() in pytz.country_timezones else False
|
||||
if exist is True:
|
||||
tz = str(country_timezones(code))
|
||||
tz = str(pytz.country_timezones(iso_code.upper()))
|
||||
msg = (
|
||||
f"Supported timezones for **{code}:**\n{tz[:-1][1:]}"
|
||||
f"Supported timezones for **{iso_code.upper()}:**\n{tz[:-1][1:]}"
|
||||
f"\n**Use** `{ctx.prefix}time tz Continent/City` **to display the current time in that timezone.**"
|
||||
)
|
||||
await ctx.send(msg)
|
||||
else:
|
||||
await ctx.send(
|
||||
"That code isn't supported. For a full list, see here: "
|
||||
"<https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes>"
|
||||
"That code isn't supported.\nFor a full list, see here: "
|
||||
"<https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes>\n"
|
||||
"Use the two-character code under the `Alpha-2 code` column."
|
||||
)
|
||||
|
||||
@time.command()
|
||||
async def me(self, ctx, *, tz=None):
|
||||
async def me(self, ctx, *, timezone_name=None):
|
||||
"""
|
||||
Sets your timezone.
|
||||
Usage: [p]time me Continent/City
|
||||
Sets your timezone.
|
||||
Usage: [p]time me Continent/City
|
||||
Using the command with no timezone will show your current timezone, if any.
|
||||
"""
|
||||
if tz is None:
|
||||
usertime, tz = await self.get_usertime(ctx.author)
|
||||
if timezone_name is None:
|
||||
usertime, timezone_name = await self.get_usertime(ctx.author)
|
||||
if not usertime:
|
||||
await ctx.send(
|
||||
f"You haven't set your timezone. Do `{ctx.prefix}time me Continent/City`: "
|
||||
"see <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
|
||||
)
|
||||
else:
|
||||
time = datetime.now(tz)
|
||||
time = datetime.now(timezone_name)
|
||||
time = time.strftime("**%H:%M** %d-%B-%Y **%Z (UTC %z)**")
|
||||
msg = f"Your current timezone is **{usertime}.**\n" f"The current time is: {time}"
|
||||
await ctx.send(msg)
|
||||
else:
|
||||
exist = True if tz.title() in common_timezones else False
|
||||
if exist:
|
||||
if "'" in tz:
|
||||
tz = tz.replace("'", "")
|
||||
await self.config.user(ctx.author).usertime.set(tz.title())
|
||||
await ctx.send(f"Successfully set your timezone to **{tz.title()}**.")
|
||||
else:
|
||||
await ctx.send(
|
||||
f"**Error:** Unrecognized timezone. Try `{ctx.prefix}time me Continent/City`: "
|
||||
"see <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
|
||||
)
|
||||
tz_results = self.fuzzy_timezone_search(timezone_name)
|
||||
tz_resp = await self.format_results(ctx, tz_results)
|
||||
if tz_resp:
|
||||
await self.config.user(ctx.author).usertime.set(tz_resp[0][0])
|
||||
await ctx.send(f"Successfully set your timezone to **{tz_resp[0][0]}**.")
|
||||
|
||||
@time.command()
|
||||
@commands.is_owner()
|
||||
async def set(self, ctx, user: discord.Member, *, tz=None):
|
||||
"""Allows the mods to edit timezones."""
|
||||
async def set(self, ctx, user: discord.User, *, timezone_name=None):
|
||||
"""
|
||||
Allows the bot owner to edit users' timezones.
|
||||
Use a user id for the user if they are not present in your server.
|
||||
"""
|
||||
if not user:
|
||||
user = ctx.author
|
||||
if tz is None:
|
||||
await ctx.send("That timezone is invalid.")
|
||||
return
|
||||
if len(self.bot.users) == 1:
|
||||
return await ctx.send("This cog requires Discord's Privileged Gateway Intents to function properly.")
|
||||
if user not in self.bot.users:
|
||||
return await ctx.send("I can't see that person anywhere.")
|
||||
if timezone_name is None:
|
||||
return await ctx.send_help()
|
||||
else:
|
||||
exist = True if tz.title() in common_timezones else False
|
||||
if exist:
|
||||
if "'" in tz:
|
||||
tz = tz.replace("'", "")
|
||||
await self.config.user(user).usertime.set(tz.title())
|
||||
await ctx.send(f"Successfully set {user.name}'s timezone to **{tz.title()}**.")
|
||||
else:
|
||||
await ctx.send(
|
||||
f"**Error:** Unrecognized timezone. Try `{ctx.prefix}time set @user Continent/City`: "
|
||||
"see <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
|
||||
)
|
||||
tz_results = self.fuzzy_timezone_search(timezone_name)
|
||||
tz_resp = await self.format_results(ctx, tz_results)
|
||||
if tz_resp:
|
||||
await self.config.user(user).usertime.set(tz_resp[0][0])
|
||||
await ctx.send(f"Successfully set {user.name}'s timezone to **{tz_resp[0][0]}**.")
|
||||
|
||||
@time.command()
|
||||
async def user(self, ctx, user: discord.Member = None):
|
||||
"""Shows the current time for user."""
|
||||
"""Shows the current time for the specified user."""
|
||||
if not user:
|
||||
await ctx.send("That isn't a user!")
|
||||
else:
|
||||
@@ -178,7 +199,7 @@ class Timezone(commands.Cog):
|
||||
user_diff = user_now.utcoffset().total_seconds() / 60 / 60
|
||||
other_now = datetime.now(other_tz)
|
||||
other_diff = other_now.utcoffset().total_seconds() / 60 / 60
|
||||
time_diff = int(abs(user_diff - other_diff))
|
||||
time_diff = str(abs(user_diff - other_diff)).rstrip(".0")
|
||||
fmt = "**%H:%M %Z (UTC %z)**"
|
||||
other_time = other_now.strftime(fmt)
|
||||
plural = "" if time_diff == 1 else "s"
|
||||
@@ -187,3 +208,8 @@ class Timezone(commands.Cog):
|
||||
position_text = "" if time_diff == 0 else f" {position} you"
|
||||
|
||||
await ctx.send(f"{user.display_name}'s time is {other_time} which is {time_amt}{position_text}.")
|
||||
|
||||
@time.command()
|
||||
async def version(self, ctx):
|
||||
"""Show the cog version."""
|
||||
await ctx.send(f"Timezone version: {__version__}.")
|
||||
|
||||
Reference in New Issue
Block a user