| 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
 | import datetime
import logging
import random
from typing import Dict, Optional
import aiohttp
import discord
from discord.ext import commands
from bot.constants import Month, Tokens
from bot.utils.decorators import in_month
log = logging.getLogger(__name__)
URL = "https://api.github.com/search/issues?per_page=100&q=is:issue+label:hacktoberfest+language:python+state:open"
REQUEST_HEADERS = {
    "User-Agent": "Python Discord Hacktoberbot",
    "Accept": "application / vnd.github.v3 + json"
}
if GITHUB_TOKEN := Tokens.github:
    REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}"
class HacktoberIssues(commands.Cog):
    """Find a random hacktober python issue on GitHub."""
    def __init__(self, bot: commands.Bot):
        self.bot = bot
        self.cache_normal = None
        self.cache_timer_normal = datetime.datetime(1, 1, 1)
        self.cache_beginner = None
        self.cache_timer_beginner = datetime.datetime(1, 1, 1)
    @in_month(Month.OCTOBER)
    @commands.command()
    async def hacktoberissues(self, ctx: commands.Context, option: str = "") -> None:
        """
        Get a random python hacktober issue from Github.
        If the command is run with beginner (`.hacktoberissues beginner`):
        It will also narrow it down to the "first good issue" label.
        """
        with ctx.typing():
            issues = await self.get_issues(ctx, option)
            if issues is None:
                return
            issue = random.choice(issues["items"])
            embed = self.format_embed(issue)
        await ctx.send(embed=embed)
    async def get_issues(self, ctx: commands.Context, option: str) -> Optional[Dict]:
        """Get a list of the python issues with the label 'hacktoberfest' from the Github api."""
        if option == "beginner":
            if (ctx.message.created_at - self.cache_timer_beginner).seconds <= 60:
                log.debug("using cache")
                return self.cache_beginner
        elif (ctx.message.created_at - self.cache_timer_normal).seconds <= 60:
            log.debug("using cache")
            return self.cache_normal
        async with aiohttp.ClientSession() as session:
            if option == "beginner":
                url = URL + '+label:"good first issue"'
                if self.cache_beginner is not None:
                    page = random.randint(1, min(1000, self.cache_beginner["total_count"]) // 100)
                    url += f"&page={page}"
            else:
                url = URL
                if self.cache_normal is not None:
                    page = random.randint(1, min(1000, self.cache_normal["total_count"]) // 100)
                    url += f"&page={page}"
            log.debug(f"making api request to url: {url}")
            async with session.get(url, headers=REQUEST_HEADERS) as response:
                if response.status != 200:
                    log.error(f"expected 200 status (got {response.status}) from the GitHub api.")
                    await ctx.send(f"ERROR: expected 200 status (got {response.status}) from the GitHub api.")
                    await ctx.send(await response.text())
                    return None
                data = await response.json()
                if len(data["items"]) == 0:
                    log.error(f"no issues returned from GitHub api. with url: {response.url}")
                    await ctx.send(f"ERROR: no issues returned from GitHub api. with url: {response.url}")
                    return None
                if option == "beginner":
                    self.cache_beginner = data
                    self.cache_timer_beginner = ctx.message.created_at
                else:
                    self.cache_normal = data
                    self.cache_timer_normal = ctx.message.created_at
                return data
    @staticmethod
    def format_embed(issue: Dict) -> discord.Embed:
        """Format the issue data into a embed."""
        title = issue["title"]
        issue_url = issue["url"].replace("api.", "").replace("/repos/", "/")
        body = issue["body"]
        labels = [label["name"] for label in issue["labels"]]
        embed = discord.Embed(title=title)
        embed.description = body[:500] + '...' if len(body) > 500 else body
        embed.add_field(name="labels", value="\n".join(labels))
        embed.url = issue_url
        embed.set_footer(text=issue_url)
        return embed
def setup(bot: commands.Bot) -> None:
    """Hacktober issue finder Cog Load."""
    bot.add_cog(HacktoberIssues(bot))
 |