Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion githubcards/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class RepoData(commands.Converter):
async def convert(self, ctx: commands.Context, argument: str) -> dict:
cache = ctx.cog.active_prefix_matchers.get(ctx.guild.id, None)
if cache is None:
raise commands.BadArgument("There are no configured repositories on this server.")
raise commands.BadArgument(
"There are no configured repositories on this server."
)
repo_data = cache["data"].get(argument, None)
if repo_data is None:
raise commands.BadArgument(
Expand Down
193 changes: 151 additions & 42 deletions githubcards/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import discord
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import pagify
from redbot.core.utils.views import SimpleMenu

from .converters import RepoData
from .data import IssueType, SearchData
Expand All @@ -34,6 +36,7 @@

class GitHubCards(commands.Cog):
"""GitHub Cards"""

# Oh my god I'm doing it

def __init__(self, bot):
Expand All @@ -51,7 +54,7 @@ def __init__(self, bot):
self.http: GitHubAPI = None # assigned in initialize()

async def initialize(self):
""" cache preloading """
"""cache preloading"""
await self.rebuild_cache_for_guild()
await self._create_client()
self._ready.set()
Expand All @@ -67,7 +70,10 @@ async def rebuild_cache_for_guild(self, *guild_ids):
for guild_id, guild_data in data.items():
partial = "|".join(re.escape(prefix) for prefix in guild_data.keys())
pattern = re.compile(rf"^({partial})#([0-9]+)$", re.IGNORECASE)
self.active_prefix_matchers[int(guild_id)] = {"pattern": pattern, "data": guild_data}
self.active_prefix_matchers[int(guild_id)] = {
"pattern": pattern,
"data": guild_data,
}
finally:
self._ready.set()

Expand Down Expand Up @@ -97,6 +103,33 @@ async def _create_client(self) -> None:
"""Create GitHub API client."""
self.http = GitHubAPI(token=await self._get_token())

async def _add_card(
self, guild: discord.Guild, prefix: str, owner: str, repo: str
) -> str:
"""Adds new cards to the guild"""
try:
await self.http.validate_repo(owner, repo)
except ApiError:
return "The provided GitHub repository doesn't exist, or is unable to be accessed due to permissions."

async with self.config.custom("REPO", guild.id).all() as repos:
if prefix in repos.keys():
return "This prefix already exists in this server. Please use something else."

repos[prefix] = {"owner": owner, "repo": repo}

await self.rebuild_cache_for_guild(guild.id)
return (
f"A GitHub repository (``{owner}/{repo}``) added with a prefix ``{prefix}``"
)

async def _remove_card(self, guild: discord.Guild, prefix: str) -> str:
"""Removes a card from the guild"""
await self.config.custom("REPO", guild.id, prefix).clear()
await self.rebuild_cache_for_guild(guild.id)

return f"A repository with the prefix ``{prefix}`` has been removed."

@commands.guild_only()
@commands.command(usage="<prefix> <search_query>")
async def ghsearch(self, ctx, repo_data: RepoData, *, search_query: str):
Expand All @@ -123,51 +156,61 @@ async def add(self, ctx, prefix: str, github_slug: str):

Format for adding a new GitHub repo is "Username/Repository"
"""
prefix = prefix.lower() # Ensure lowering of prefixes, since fuck anything else.
prefix = (
prefix.lower()
) # Ensure lowering of prefixes, since fuck anything else.
try:
owner, repo = github_slug.split("/")
except ValueError:
await ctx.send('Invalid format. Please use ``Username/Repository``.')
await ctx.send("Invalid format. Please use ``Username/Repository``.")
return

try:
await self.http.validate_repo(owner, repo)
except ApiError:
await ctx.send('The provided GitHub repository doesn\'t exist, or is unable to be accessed due to permissions.')
return

async with self.config.custom("REPO", ctx.guild.id).all() as repos:
if prefix in repos.keys():
await ctx.send('This prefix already exists in this server. Please use something else.')
return

repos[prefix] = {"owner": owner, "repo": repo}
msg = await self._add_card(
guild=ctx.guild, prefix=prefix, owner=owner, repo=repo
)

await self.rebuild_cache_for_guild(ctx.guild.id)
await ctx.send(f"A GitHub repository (``{github_slug}``) added with a prefix ``{prefix}``")
await ctx.send(msg)

@ghc_group.command(name="remove", aliases=["delete"])
async def remove(self, ctx, prefix: str):
"""Remove a GitHub repository with its given prefix.
"""
await self.config.custom("REPO", ctx.guild.id, prefix).clear()
await self.rebuild_cache_for_guild(ctx.guild.id)
"""Remove a GitHub repository with its given prefix."""
msg = await self._remove_card(guild=ctx.guild, prefix=prefix)

# if that prefix doesn't exist, it will still send same message but I don't care
await ctx.send(f"A repository with the prefix ``{prefix}`` has been removed.")
await ctx.send(msg)

@ghc_group.command(name="list")
async def list_prefixes(self, ctx):
"""List all prefixes for GitHub Cards in this server.
"""
async def list_prefixes(self, ctx: commands.Context):
"""List all prefixes for GitHub Cards in this server."""
repos = await self.config.custom("REPO", ctx.guild.id).all()
if not repos:
await ctx.send("There are no configured GitHub repositories on this server.")
await ctx.send(
"There are no configured GitHub repositories on this server."
)
return
msg = "\n".join(
f"``{prefix}``: ``{repo['owner']}/{repo['repo']}``" for prefix, repo in repos.items()
f"``{prefix}``: ``{repo['owner']}/{repo['repo']}``"
for prefix, repo in sorted(repos.items())
)
await ctx.send(f"List of configured prefixes on **{ctx.guild.name}** server:\n{msg}")

pages = pagify(msg, delims=("\n"))
pages = [
{
"embed": discord.Embed(
description=page,
title=f"List of configured prefixes on **{ctx.guild.name}** server:",
color=await self.bot.get_embed_colour(ctx.message),
)
}
for page in pages
]

if len(pages) == 1:
await ctx.send(**pages[0])
else:
selectMenu = len(pages) >= 5 <= 25
await SimpleMenu(
pages, use_select_menu=selectMenu, use_select_only=selectMenu
).start(ctx)

@ghc_group.command(name="instructions")
async def instructions(self, ctx):
Expand Down Expand Up @@ -198,7 +241,9 @@ async def is_eligible_as_command(self, message: discord.Message) -> bool:
and not await self.bot.cog_disabled_in_guild(self, message.guild)
)

def get_matcher_by_message(self, message: discord.Message) -> Optional[Dict[str, Any]]:
def get_matcher_by_message(
self, message: discord.Message
) -> Optional[Dict[str, Any]]:
"""Get matcher from message object.

This also checks if the message is eligible as command and returns None otherwise.
Expand Down Expand Up @@ -229,24 +274,44 @@ async def on_message_without_command(self, message):
if message.content.startswith(f"{prefix}#s "):
async with message.channel.typing():
search_query = message.content.replace(f"{prefix}#s ", "")
if all(issue_type not in search_query for issue_type in ["is:issue", "is:pr", "is:pull-request", "is:pull_request"]):
if all(
issue_type not in search_query
for issue_type in [
"is:issue",
"is:pr",
"is:pull-request",
"is:pull_request",
]
):
search_data_issues = await self.http.search_issues(
data["owner"], data["repo"], search_query, type=IssueType.ISSUE
data["owner"],
data["repo"],
search_query,
type=IssueType.ISSUE,
)
search_data_prs = await self.http.search_issues(
data["owner"], data["repo"], search_query, type=IssueType.PULL_REQUEST
data["owner"],
data["repo"],
search_query,
type=IssueType.PULL_REQUEST,
)
# truncate the results to 15 based on date
combined_results = search_data_issues.results + search_data_prs.results
combined_results = (
search_data_issues.results + search_data_prs.results
)
for result in combined_results:
result["createdAt"] = datetime.strptime(result['createdAt'], '%Y-%m-%dT%H:%M:%SZ')
combined_results.sort(key=lambda x: x["createdAt"], reverse=True)
result["createdAt"] = datetime.strptime(
result["createdAt"], "%Y-%m-%dT%H:%M:%SZ"
)
combined_results.sort(
key=lambda x: x["createdAt"], reverse=True
)
combined_results = combined_results[:15]

search_data = SearchData(
total=search_data_issues.total + search_data_prs.total,
results=combined_results,
query=search_query
query=search_query,
)
else:
search_data = await self.http.search_issues(
Expand All @@ -269,7 +334,7 @@ async def on_message_without_command(self, message):
number = int(match.group(2))

prefix_data = matcher["data"][prefix]
name_with_owner = (prefix_data['owner'], prefix_data['repo'])
name_with_owner = (prefix_data["owner"], prefix_data["repo"])

# Magical fetching aquesition done.
# Ensure that the repo exists as a key
Expand All @@ -281,16 +346,58 @@ async def on_message_without_command(self, message):
"fetchable_issues": {}, # using dict instead of a set since it's ordered
}
# No need to post card for same issue number from the same repo in one message twice
if number in fetchable_repos[name_with_owner]['fetchable_issues']:
if number in fetchable_repos[name_with_owner]["fetchable_issues"]:
continue
fetchable_repos[name_with_owner]['fetchable_issues'][number] = None
fetchable_repos[name_with_owner]["fetchable_issues"][number] = None

if len(fetchable_repos) == 0:
return # End if no repos are found to query over.

async with message.channel.typing():
await self._query_and_post(message, fetchable_repos)

@commands.Cog.listener()
async def on_kowlin_ghc_add(
self,
*,
guild: discord.Guild,
owner: str,
repo: str,
prefix: str,
**_kwargs: Any,
):
"""Sets up listener for new repos

Can be dispatch like
self.bot.dispatch(
"kowlin_ghc_add",
guild=ctx.guild,
owner="Kowlin"
repo="Sentinel",
prefix="Sentinel"
)
"""
msg = await self._add_card(guild=guild, prefix=prefix, owner=owner, repo=repo)

log.info(msg)

@commands.Cog.listener()
async def on_kowlin_ghc_remove(
self, *, guild: discord.Guild, prefix: str, **_kwargs: Any
):
"""Sets up listener for removal of repos

Can be dispatch like
self.bot.dispatch(
"kowlin_ghc_remove",
guild=ctx.guild,
prefix="Sentinel"
)
"""
msg = await self._remove_card(guild=guild, prefix=prefix)

log.info(msg)

async def _query_and_post(self, message, fetchable_repos):
# --- FETCHING ---
query = Query.build_query(fetchable_repos)
Expand Down Expand Up @@ -321,7 +428,9 @@ async def _query_and_post(self, message, fetchable_repos):
issue_embeds.append(e)
continue
else:
overflow.append(f"[{issue.name_with_owner}#{issue.number}]({issue.url})")
overflow.append(
f"[{issue.name_with_owner}#{issue.number}]({issue.url})"
)

for embed in issue_embeds:
await message.channel.send(embed=embed)
Expand Down
6 changes: 3 additions & 3 deletions githubcards/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ class IssueData(object):

@dataclass(init=True)
class IssueStateColour(object):
OPEN: int = 0x6cc644
CLOSED: int = 0xbd2c00
MERGED: int = 0x6e5494
OPEN: int = 0x6CC644
CLOSED: int = 0xBD2C00
MERGED: int = 0x6E5494


class IssueType(Enum):
Expand Down
Loading