diff --git a/timezone/info.json b/timezone/info.json index eb5fbff..fa2ce29 100644 --- a/timezone/info.json +++ b/timezone/info.json @@ -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." -} \ No newline at end of file +} diff --git a/timezone/timezone.py b/timezone/timezone.py index 2d1a414..a31658d 100644 --- a/timezone/timezone.py +++ b/timezone/timezone.py @@ -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" + "" + ) + 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 " - "" - ) - 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: " - "" + "That code isn't supported.\nFor a full list, see here: " + "\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 " ) 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 " - ) + 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 " - ) + 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__}.")