diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index 8327eff..0030950 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -1,10 +1,14 @@ -import re -from datetime import datetime +from datetime import datetime, timedelta import gitlab +from ButtonPaginator import Paginator from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option +from discord.ext.tasks import loop +from discord.utils import find +from discord_slash import ComponentContext, SlashContext, cog_ext +from discord_slash.model import ButtonStyle +from discord_slash.utils import manage_components +from discord_slash.utils.manage_commands import create_choice, create_option from jarvis.config import get_config from jarvis.utils import build_embed @@ -13,6 +17,36 @@ from jarvis.utils.field import Field guild_ids = [862402786116763668] +def create_layout(command: str): + buttons = [ + manage_components.create_button( + style=ButtonStyle.gray, emoji="◀", custom_id=f"back_{command}" + ), + manage_components.create_button( + style=ButtonStyle.gray, + emoji="▶", + custom_id=f"next_{command}", + ), + ] + action_row = manage_components.spread_to_rows(*buttons, max_in_row=2) + return action_row + + +def create_issues_layout(): + return create_layout("issues") + + +def edit_paging_components( + components: list, disable_left: bool = False, disable_right: bool = False +): + components[0]["components"][0]["disabled"] = disable_left + components[0]["components"][1]["disabled"] = disable_right + return components + + +command_lookup = {"issues": create_issues_layout} + + class GitlabCog(commands.Cog): def __init__(self, bot): self.bot = bot @@ -22,6 +56,8 @@ class GitlabCog(commands.Cog): ) # J.A.R.V.I.S. GitLab ID is 29 self.project = self._gitlab.projects.get(29) + self.cache = {} + self._expire_interaction.start() @cog_ext.cog_subcommand( base="gl", @@ -43,16 +79,25 @@ class GitlabCog(commands.Cog): assignee = issue.assignee if assignee: assignee = assignee["name"] + else: + assignee = "None" created_at = datetime.strptime( issue.created_at, "%Y-%m-%dT%H:%M:%S.%fZ" ).strftime("%Y-%m-%d %H:%M:%S UTC") + + labels = issue.labels + if labels: + labels = "\n".join(issue.labels) + if not labels: + labels = "None" + fields = [ Field( name="State", value=issue.state[0].upper() + issue.state[1:] ), Field(name="Assignee", value=assignee), - Field(name="Labels", value="\n".join(issue.labels)), + Field(name="Labels", value=labels), ] color = self.project.labels.get(issue.labels[0]).color fields.append(Field(name="Created At", value=created_at)) @@ -108,7 +153,7 @@ class GitlabCog(commands.Cog): try: milestone = self.project.milestones.get(int(id)) except gitlab.exceptions.GitlabGetError: - await ctx.send("Issue does not exist.", hidden=True) + await ctx.send("Milestone does not exist.", hidden=True) return created_at = datetime.strptime( @@ -153,6 +198,257 @@ class GitlabCog(commands.Cog): ) await ctx.send(embed=embed) + @cog_ext.cog_subcommand( + base="gl", + name="mr", + description="Get an merge request from GitLab", + guild_ids=guild_ids, + options=[ + create_option( + name="id", + description="Merge Request ID", + option_type=4, + required=True, + ) + ], + ) + async def _mr(self, ctx: SlashContext, id: int): + try: + mr = self.project.mergerequests.get(int(id)) + except gitlab.exceptions.GitlabGetError: + await ctx.send("Merge request does not exist.", hidden=True) + return + assignee = mr.assignee + if assignee: + assignee = assignee["name"] + else: + assignee = "None" + + created_at = datetime.strptime( + mr.created_at, "%Y-%m-%dT%H:%M:%S.%fZ" + ).strftime("%Y-%m-%d %H:%M:%S UTC") + + labels = mr.labels + if labels: + labels = "\n".join(mr.labels) + if not labels: + labels = "None" + + fields = [ + Field(name="State", value=mr.state[0].upper() + mr.state[1:]), + Field(name="Assignee", value=assignee), + Field(name="Labels", value=labels), + ] + if mr.labels: + color = self.project.labels.get(mr.labels[0]).color + else: + color = "#00FFEE" + fields.append(Field(name="Created At", value=created_at)) + if mr.state == "merged": + merged_at = datetime.strptime( + mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ" + ).strftime("%Y-%m-%d %H:%M:%S UTC") + fields.append(Field(name="Merged At", value=merged_at)) + elif mr.state == "closed": + closed_at = datetime.strptime( + mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ" + ).strftime("%Y-%m-%d %H:%M:%S UTC") + fields.append(Field(name="Closed At", value=closed_at)) + if mr.milestone: + fields.append( + Field( + name="Milestone", + value=f"[{mr.milestone['title']}]" + + f"({mr.milestone['web_url']})", + inline=False, + ) + ) + if len(mr.title) > 200: + mr.title = mr.title[:200] + "..." + embed = build_embed( + title=f"[#{mr.iid}] {mr.title}", + description=mr.description, + fields=fields, + color=color, + url=mr.web_url, + ) + embed.set_author( + name=mr.author["name"], + icon_url=mr.author["avatar_url"], + url=mr.author["web_url"], + ) + embed.set_thumbnail( + url="https://about.gitlab.com/images" + + "/press/logo/png/gitlab-icon-rgb.png" + ) + await ctx.send(embed=embed) + + def build_embed_page(self, issues: list, t_state: str): + fields = [] + for issue in issues: + fields.append( + Field( + name=f"[#{issue.iid}] {issue.title}", + value=issue.description + + f"\n\n[View this issue]({issue.web_url})", + inline=False, + ) + ) + + embed = build_embed( + title=f"{t_state} J.A.R.V.I.S. issues", + description="", + fields=fields, + url="https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./issues", + ) + embed.set_author( + name="J.A.R.V.I.S.", + url="https://git.zevaryx.com/jarvis", + icon_url="https://git.zevaryx.com/uploads/-" + + "/system/user/avatar/11/avatar.png", + ) + embed.set_thumbnail( + url="https://about.gitlab.com/images" + + "/press/logo/png/gitlab-icon-rgb.png" + ) + return embed + + @cog_ext.cog_subcommand( + base="gl", + name="issues", + description="Get open issues from GitLab", + guild_ids=guild_ids, + options=[ + create_option( + name="state", + description="State of issues to get", + option_type=3, + required=False, + choices=[ + create_choice(name="Open", value="opened"), + create_choice(name="Closed", value="closed"), + create_choice(name="All", value="all"), + ], + ) + ], + ) + async def _issues(self, ctx: SlashContext, state: str = "opened"): + exists = find( + lambda x: x["_command"] == "issues" + and x["user"] == ctx.author.id + and x["state"] == state, + self.cache.values(), + ) + if exists: + await ctx.defer(hidden=True) + await ctx.send( + "Please use existing interaction: " + + f"{exists['_message'].jump_url}", + hidden=True, + ) + return + await ctx.defer() + m_state = state + if m_state == "all": + m_state = None + # issues = [] + # page = 1 + try: + # issues += self.project.issues.list(page=page, state=m_state, order_by="created_at", sort="desc", per_page=100) + issues = self.project.issues.list( + page=1, + state=m_state, + order_by="created_at", + sort="desc", + per_page=5, + ) + next_issues = self.project.issues.list( + page=2, + state=m_state, + order_by="created_at", + sort="desc", + per_page=5, + ) + except gitlab.exceptions.GitlabGetError: + await ctx.send("Unable to get issues", hidden=True) + return + t_state = state + if t_state == "opened": + t_state = "open" + + t_state = t_state[0].upper() + t_state[1:] + + components = create_issues_layout() + components = edit_paging_components( + components, True, next_issues == [] + ) + embed = self.build_embed_page(issues, t_state=t_state) + message = await ctx.send(embed=embed, components=components) + self.cache[message.id] = { + "user": ctx.author.id, + "state": state, + "t_state": t_state, + "page": 1, + "timeout": datetime.utcnow() + timedelta(minutes=5), + "_command": "issues", + "_message": message, + } + + @cog_ext.cog_component(components=create_issues_layout()) + async def _process(self, ctx: ComponentContext): + await ctx.defer(edit_origin=True) + cache = self.cache[ctx.origin_message.id] + if ctx.author.id != cache["user"]: + return + + state = cache["state"] + page = cache["page"] + if ctx.custom_id == "back": + page -= 1 + else: + page += 1 + try: + issues = self.project.issues.list( + page=page, + state=state, + order_by="created_at", + sort="desc", + per_page=5, + ) + next_issues = self.project.issues.list( + page=page + 1, + state=state, + order_by="created_at", + sort="desc", + per_page=5, + ) + except gitlab.exceptions.GitlabGetError: + await ctx.edit_origin(content="Unable to get issues", hidden=True) + return + components = create_issues_layout() + components = edit_paging_components( + components=components, + disable_left=page == 1, + disable_right=next_issues == [], + ) + embed = self.build_embed_page( + issues=issues, t_state=self.cache[ctx.origin_message.id]["t_state"] + ) + self.cache[ctx.origin_message.id]["page"] = page + await ctx.edit_origin(embed=embed, components=components) + + @loop(minutes=1) + async def _expire_interaction(self): + keys = list(self.cache.keys()) + for key in keys: + if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta( + minutes=1 + ): + components = create_layout(self.cache[key]["_command"]) + components = edit_paging_components(components, True, True) + await self.cache[key]["_message"].edit(components=components) + del self.cache[key] + def setup(bot): if get_config().gitlab_token: