|
3 | 3 | from dataclasses import dataclass |
4 | 4 | from datetime import UTC, datetime |
5 | 5 | from urllib.parse import quote |
| 6 | +from pathlib import Path |
| 7 | +import json |
6 | 8 |
|
7 | 9 | import discord |
8 | 10 | from aiohttp import ClientResponse |
9 | | -from discord.ext import commands |
| 11 | +from discord.ext import commands, tasks |
10 | 12 | from pydis_core.utils.logging import get_logger |
11 | 13 |
|
12 | 14 | from bot.bot import Bot |
|
21 | 23 | } |
22 | 24 |
|
23 | 25 | REPOSITORY_ENDPOINT = "https://api.github.com/orgs/{org}/repos?per_page=100&type=public" |
| 26 | +FETCH_MOST_STARRED_ENDPOINT = "https://api.github.com/search/repositories?q={name}&sort=stars&order=desc&per_page=1" |
24 | 27 | ISSUE_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/issues/{number}" |
25 | 28 | PR_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/pulls/{number}" |
26 | 29 |
|
@@ -78,6 +81,16 @@ def __init__(self, bot: Bot): |
78 | 81 | self.bot = bot |
79 | 82 | self.repos = [] |
80 | 83 |
|
| 84 | + async def cog_load(self): |
| 85 | + self.refresh_repos.start() |
| 86 | + |
| 87 | + self.stored_repos_json = Path(__file__).parent.parent.parent / "resources" / "utilities" / "stored_repos.json" |
| 88 | + |
| 89 | + with open(self.stored_repos_json, "r") as f: |
| 90 | + self.stored_repos = json.load(f) |
| 91 | + log.info("Loaded stored repos in memory.") |
| 92 | + |
| 93 | + |
81 | 94 | @staticmethod |
82 | 95 | def remove_codeblocks(message: str) -> str: |
83 | 96 | """Remove any codeblock in a message.""" |
@@ -293,78 +306,144 @@ async def github_user_info(self, ctx: commands.Context, username: str) -> None: |
293 | 306 |
|
294 | 307 | await ctx.send(embed=embed) |
295 | 308 |
|
| 309 | + @tasks.loop(hours=24) |
| 310 | + async def refresh_repos(self): |
| 311 | + self.repos, _ = await self.fetch_data(REPOSITORY_ENDPOINT.format(org="python-discord")) |
| 312 | + log.info(f"Loaded {len(self.repos)} repos from Python Discord org into memory.") |
| 313 | + |
296 | 314 | @github_group.command(name="repository", aliases=("repo",)) |
297 | 315 | async def github_repo_info(self, ctx: commands.Context, *repo: str) -> None: |
298 | 316 | """ |
299 | | - Fetches a repositories' GitHub information. |
| 317 | + Fetches a repository's GitHub information. |
300 | 318 |
|
301 | 319 | The repository should look like `user/reponame` or `user reponame`. |
| 320 | + If it's not a stored repo or PyDis repo, it will fetch the most-starred repo |
| 321 | + matching the search query from GitHub. |
302 | 322 | """ |
303 | | - repo = "/".join(repo) |
304 | | - if repo.count("/") != 1: |
305 | | - embed = discord.Embed( |
306 | | - title=random.choice(NEGATIVE_REPLIES), |
307 | | - description="The repository should look like `user/reponame` or `user reponame`.", |
308 | | - colour=Colours.soft_red |
309 | | - ) |
310 | | - |
311 | | - await ctx.send(embed=embed) |
312 | | - return |
| 323 | + is_pydis = False |
| 324 | + fetch_most_starred = False |
| 325 | + repo_query = "/".join(repo) |
| 326 | + |
| 327 | + # Determine type of repo |
| 328 | + if repo_query.count("/") != 1: |
| 329 | + if repo_query in self.stored_repos: |
| 330 | + repo_query = self.stored_repos[repo_query] |
| 331 | + else: |
| 332 | + for each in self.repos: |
| 333 | + if repo_query == each['name']: |
| 334 | + repo_query = each['full_name'] |
| 335 | + is_pydis = True |
| 336 | + break |
| 337 | + else: |
| 338 | + fetch_most_starred = True |
313 | 339 |
|
314 | 340 | async with ctx.typing(): |
315 | | - repo_data, _ = await self.fetch_data(f"{GITHUB_API_URL}/repos/{quote(repo)}") |
316 | | - |
317 | | - # There won't be a message key if this repo exists |
318 | | - if "message" in repo_data: |
| 341 | + # Case 1: PyDis repo |
| 342 | + if is_pydis: |
| 343 | + for each in self.repos: |
| 344 | + if repo_query == each['full_name']: |
| 345 | + repo_data = each |
| 346 | + break |
| 347 | + |
| 348 | + # Case 2: Not stored or PyDis, fetch most-starred matching repo |
| 349 | + elif fetch_most_starred: |
| 350 | + repo_data, _ = await self.fetch_data(FETCH_MOST_STARRED_ENDPOINT.format(name=quote(repo_query))) |
| 351 | + |
| 352 | + if not repo_data['items']: |
| 353 | + embed = discord.Embed( |
| 354 | + title=random.choice(NEGATIVE_REPLIES), |
| 355 | + description=f"No repositories found matching `{repo_query}`.", |
| 356 | + colour=Colours.soft_red |
| 357 | + ) |
| 358 | + await ctx.send(embed=embed) |
| 359 | + return |
| 360 | + |
| 361 | + repo_item = repo_data['items'][0] # Top result |
319 | 362 | embed = discord.Embed( |
320 | | - title=random.choice(NEGATIVE_REPLIES), |
321 | | - description="The requested repository was not found.", |
322 | | - colour=Colours.soft_red |
| 363 | + title=repo_item["name"], |
| 364 | + description=repo_item["description"] or "No description provided.", |
| 365 | + colour=discord.Colour.og_blurple(), |
| 366 | + url=repo_item["html_url"] |
323 | 367 | ) |
324 | 368 |
|
325 | | - await ctx.send(embed=embed) |
326 | | - return |
327 | | - |
328 | | - embed = discord.Embed( |
329 | | - title=repo_data["name"], |
330 | | - description=repo_data["description"], |
331 | | - colour=discord.Colour.og_blurple(), |
332 | | - url=repo_data["html_url"] |
333 | | - ) |
| 369 | + repo_owner = repo_item["owner"] |
| 370 | + embed.set_author( |
| 371 | + name=repo_owner["login"], |
| 372 | + url=repo_owner["html_url"], |
| 373 | + icon_url=repo_owner["avatar_url"] |
| 374 | + ) |
334 | 375 |
|
335 | | - # If it's a fork, then it will have a parent key |
336 | | - try: |
337 | | - parent = repo_data["parent"] |
338 | | - embed.description += f"\n\nForked from [{parent['full_name']}]({parent['html_url']})" |
339 | | - except KeyError: |
340 | | - log.debug("Repository is not a fork.") |
| 376 | + repo_created_at = datetime.strptime( |
| 377 | + repo_item["created_at"], "%Y-%m-%dT%H:%M:%SZ" |
| 378 | + ).replace(tzinfo=UTC).strftime("%d/%m/%Y") |
| 379 | + last_pushed = datetime.strptime( |
| 380 | + repo_item["pushed_at"], "%Y-%m-%dT%H:%M:%SZ" |
| 381 | + ).replace(tzinfo=UTC).strftime("%d/%m/%Y at %H:%M") |
| 382 | + |
| 383 | + embed.set_footer( |
| 384 | + text=( |
| 385 | + f"{repo_item['forks_count']} ⑂ " |
| 386 | + f"• {repo_item['stargazers_count']} ⭐ " |
| 387 | + f"• Created At {repo_created_at} " |
| 388 | + f"• Last Commit {last_pushed}" |
| 389 | + ) |
| 390 | + ) |
341 | 391 |
|
342 | | - repo_owner = repo_data["owner"] |
| 392 | + await ctx.send(embed=embed) |
| 393 | + return |
343 | 394 |
|
344 | | - embed.set_author( |
345 | | - name=repo_owner["login"], |
346 | | - url=repo_owner["html_url"], |
347 | | - icon_url=repo_owner["avatar_url"] |
348 | | - ) |
| 395 | + # Case 3: Regular GitHub repo |
| 396 | + else: |
| 397 | + repo_data, _ = await self.fetch_data(f"{GITHUB_API_URL}/repos/{quote(repo_query)}") |
| 398 | + |
| 399 | + if "message" in repo_data: |
| 400 | + embed = discord.Embed( |
| 401 | + title=random.choice(NEGATIVE_REPLIES), |
| 402 | + description="The requested repository was not found.", |
| 403 | + colour=Colours.soft_red |
| 404 | + ) |
| 405 | + await ctx.send(embed=embed) |
| 406 | + return |
| 407 | + |
| 408 | + # Embed creation for PyDis or regular GitHub repo |
| 409 | + embed = discord.Embed( |
| 410 | + title=repo_data["name"], |
| 411 | + description=repo_data["description"] or "No description provided.", |
| 412 | + colour=discord.Colour.og_blurple(), |
| 413 | + url=repo_data["html_url"] |
| 414 | + ) |
349 | 415 |
|
350 | | - repo_created_at = datetime.strptime( |
351 | | - repo_data["created_at"], "%Y-%m-%dT%H:%M:%SZ" |
352 | | - ).replace(tzinfo=UTC).strftime("%d/%m/%Y") |
353 | | - last_pushed = datetime.strptime( |
354 | | - repo_data["pushed_at"], "%Y-%m-%dT%H:%M:%SZ" |
355 | | - ).replace(tzinfo=UTC).strftime("%d/%m/%Y at %H:%M") |
356 | | - |
357 | | - embed.set_footer( |
358 | | - text=( |
359 | | - f"{repo_data['forks_count']} ⑂ " |
360 | | - f"• {repo_data['stargazers_count']} ⭐ " |
361 | | - f"• Created At {repo_created_at} " |
362 | | - f"• Last Commit {last_pushed}" |
| 416 | + # Fork info |
| 417 | + try: |
| 418 | + parent = repo_data["parent"] |
| 419 | + embed.description += f"\n\nForked from [{parent['full_name']}]({parent['html_url']})" |
| 420 | + except KeyError: |
| 421 | + log.debug("Repository is not a fork.") |
| 422 | + |
| 423 | + repo_owner = repo_data["owner"] |
| 424 | + embed.set_author( |
| 425 | + name=repo_owner["login"], |
| 426 | + url=repo_owner["html_url"], |
| 427 | + icon_url=repo_owner["avatar_url"] |
363 | 428 | ) |
364 | | - ) |
365 | 429 |
|
366 | | - await ctx.send(embed=embed) |
| 430 | + repo_created_at = datetime.strptime( |
| 431 | + repo_data["created_at"], "%Y-%m-%dT%H:%M:%SZ" |
| 432 | + ).replace(tzinfo=UTC).strftime("%d/%m/%Y") |
| 433 | + last_pushed = datetime.strptime( |
| 434 | + repo_data["pushed_at"], "%Y-%m-%dT%H:%M:%SZ" |
| 435 | + ).replace(tzinfo=UTC).strftime("%d/%m/%Y at %H:%M") |
| 436 | + |
| 437 | + embed.set_footer( |
| 438 | + text=( |
| 439 | + f"{repo_data['forks_count']} ⑂ " |
| 440 | + f"• {repo_data['stargazers_count']} ⭐ " |
| 441 | + f"• Created At {repo_created_at} " |
| 442 | + f"• Last Commit {last_pushed}" |
| 443 | + ) |
| 444 | + ) |
367 | 445 |
|
| 446 | + await ctx.send(embed=embed) |
368 | 447 |
|
369 | 448 | async def setup(bot: Bot) -> None: |
370 | 449 | """Load the GithubInfo cog.""" |
|
0 commit comments