[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:
Vexed
2021-05-14 20:49:48 +01:00
committed by GitHub
parent 9570a7bfe1
commit 07ee745b9e
2 changed files with 106 additions and 80 deletions

View File

@@ -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."
}
}

View File

@@ -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__}.")