1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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."
)
|