aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/events/hacktoberfest/_cog.py
diff options
context:
space:
mode:
Diffstat (limited to 'bot/exts/events/hacktoberfest/_cog.py')
-rw-r--r--bot/exts/events/hacktoberfest/_cog.py150
1 files changed, 150 insertions, 0 deletions
diff --git a/bot/exts/events/hacktoberfest/_cog.py b/bot/exts/events/hacktoberfest/_cog.py
new file mode 100644
index 00000000..33c1142e
--- /dev/null
+++ b/bot/exts/events/hacktoberfest/_cog.py
@@ -0,0 +1,150 @@
+import logging
+import random
+from datetime import datetime
+
+from async_rediscache import RedisCache
+from discord.ext import commands
+
+import bot.exts.events.hacktoberfest._utils as utils
+from bot.bot import Bot
+from bot.constants import Client, Month
+from bot.utils.decorators import in_month
+from bot.utils.extensions import invoke_help_command
+
+log = logging.getLogger()
+
+
+class Hacktoberfest(commands.Cog):
+ """Cog containing all Hacktober-related commands."""
+
+ # Caches for `.hacktoberfest stats`
+ linked_accounts = RedisCache()
+
+ # Caches for `.hacktoberfest issue`
+ cache_normal = None
+ cache_timer_normal = datetime(1, 1, 1)
+ cache_beginner = None
+ cache_timer_beginner = datetime(1, 1, 1)
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ @commands.group(aliases=('hacktober',))
+ async def hacktoberfest(self, ctx: commands.Context) -> None:
+ """Handler of all Hacktoberfest commands."""
+ if not ctx.invoked_subcommand:
+ await invoke_help_command(ctx)
+ return
+
+ @in_month(Month.OCTOBER)
+ @hacktoberfest.command(aliases=('issues',))
+ async def issue(self, ctx: commands.Context, option: str = "") -> None:
+ """
+ Get a random python hacktober issue from Github.
+
+ If the command is run with beginner (`.hacktoberfest issue beginner`):
+ It will also narrow it down to the "first good issue" label.
+ """
+ async with ctx.typing():
+ issues = await utils.get_issues(ctx, option)
+ if issues is None:
+ return
+ issue = random.choice(issues["items"])
+ embed = utils.format_issues_embed(issue)
+ await ctx.send(embed=embed)
+
+ @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER)
+ @hacktoberfest.group(invoke_without_command=True)
+ async def stats(self, ctx: commands.Context, github_username: str = None) -> None:
+ """
+ Display an embed for a user's Hacktoberfest contributions.
+
+ If invoked without a subcommand or github_username, get the invoking user's stats if they've
+ linked their Discord name to GitHub using `.hacktoberfest stats link`. If invoked with a github_username,
+ get that user's contributions
+ """
+ if not github_username:
+ author_id, author_mention = utils.author_mention_from_context(ctx) # in _utils.py
+
+ if await self.linked_accounts.contains(author_id):
+ github_username = await self.linked_accounts.get(author_id)
+ log.info(f"Getting stats for {author_id} linked GitHub account '{github_username}'")
+ else:
+ command_string = Client.prefix + ctx.command.qualified_name
+ msg = (
+ f"{author_mention}, you have not linked a GitHub account\n\n"
+ f"You can link your GitHub account using:\n```\n{command_string} link github_username\n```\n"
+ f"Or query GitHub stats directly using:\n```\n{command_string} github_username\n```"
+ )
+ await ctx.send(msg)
+ return
+
+ await utils.get_stats(ctx, github_username)
+
+ @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER)
+ @stats.command()
+ async def link(self, ctx: commands.Context, github_username: str = None) -> None:
+ """
+ Link the invoking user's Github github_username to their Discord ID.
+
+ Linked users are stored in Redis: User ID => GitHub Username.
+ """
+ author_id, author_mention = utils.author_mention_from_context(ctx)
+ if github_username:
+ if await self.linked_accounts.contains(author_id):
+ old_username = await self.linked_accounts.get(author_id)
+ log.info(f"{author_id} has changed their github link from '{old_username}' to '{github_username}'")
+ await ctx.send(f"{author_mention}, your GitHub username has been updated to: '{github_username}'")
+ else:
+ log.info(f"{author_id} has added a github link to '{github_username}'")
+ await ctx.send(f"{author_mention}, your GitHub username has been added")
+
+ await self.linked_accounts.set(author_id, github_username)
+ else:
+ log.info(f"{author_id} tried to link a GitHub account but didn't provide a username")
+ await ctx.send(f"{author_mention}, a GitHub username is required to link your account")
+
+ @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER)
+ @stats.command()
+ async def unlink(self, ctx: commands.Context) -> None:
+ """Remove the invoking user's account link from the log."""
+ author_id, author_mention = utils.author_mention_from_context(ctx)
+
+ stored_user = await self.linked_accounts.pop(author_id, None)
+ if stored_user:
+ await ctx.send(f"{author_mention}, your GitHub profile has been unlinked")
+ log.info(f"{author_id} has unlinked their GitHub account")
+ else:
+ await ctx.send(f"{author_mention}, you do not currently have a linked GitHub account")
+ log.info(f"{author_id} tried to unlink their GitHub account but no account was linked")
+
+ @hacktoberfest.command()
+ async def timeleft(self, ctx: commands.Context) -> None:
+ """
+ Calculates the time left until the end of Hacktober.
+
+ Whilst in October, displays the days, hours and minutes left.
+ Only displays the days left until the beginning and end whilst in a different month.
+
+ This factors in that Hacktoberfest starts when it is October anywhere in the world
+ and ends with the same rules. It treats the start as UTC+14:00 and the end as
+ UTC-12.
+ """
+ now, end, start = utils.load_date()
+ diff = end - now
+ days, seconds = diff.days, diff.seconds
+ if utils.in_hacktober():
+ minutes = seconds // 60
+ hours, minutes = divmod(minutes, 60)
+
+ await ctx.send(
+ f"There are {days} days, {hours} hours and {minutes}"
+ f" minutes left until the end of Hacktober."
+ )
+ else:
+ start_diff = start - now
+ start_days = start_diff.days
+ await ctx.send(
+ f"It is not currently Hacktober. However, the next one will start in {start_days} days "
+ f"and will finish in {days} days."
+ )