mostr-zulip-bot/zulip_bots/zulip_bots/bots/github_detail/github_detail.py
Anders Kaseorg 235f92afb0 ruff: Fix TRY401 Redundant exception object included in logging.exception call.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-10-28 16:29:20 -07:00

111 lines
4.1 KiB
Python

import logging
import re
from typing import Any, Dict, Tuple, Union
import requests
from zulip_bots.lib import BotHandler
class GithubHandler:
"""
This bot provides details on github issues and pull requests when they're
referenced in the chat.
"""
GITHUB_ISSUE_URL_TEMPLATE = "https://api.github.com/repos/{owner}/{repo}/issues/{id}"
HANDLE_MESSAGE_REGEX = re.compile(r"(?:([\w-]+)\/)?([\w-]+)?#(\d+)")
def initialize(self, bot_handler: BotHandler) -> None:
self.config_info = bot_handler.get_config_info("github_detail", optional=True)
self.owner = self.config_info.get("owner", False)
self.repo = self.config_info.get("repo", False)
def usage(self) -> str:
return (
"This plugin displays details on github issues and pull requests. "
"To reference an issue or pull request usename mention the bot then "
"anytime in the message type its id, for example:\n"
"@**Github detail** #3212 zulip#3212 zulip/zulip#3212\n"
f"The default owner is {self.owner} and the default repo is {self.repo}."
)
def format_message(self, details: Dict[str, Any]) -> str:
number = details["number"]
title = details["title"]
link = details["html_url"]
author = details["user"]["login"]
owner = details["owner"]
repo = details["repo"]
description = details["body"]
status = details["state"].title()
message_string = (
f"**[{owner}/{repo}#{number}]",
f"({link}) - {title}**\n",
f"Created by **[{author}](https://github.com/{author})**\n",
f"Status - **{status}**\n```quote\n{description}\n```",
)
return "".join(message_string)
def get_details_from_github(
self, owner: str, repo: str, number: str
) -> Union[None, Dict[str, Union[str, int, bool]]]:
# Gets the details of an issues or pull request
try:
r = requests.get(
self.GITHUB_ISSUE_URL_TEMPLATE.format(owner=owner, repo=repo, id=number)
)
except requests.exceptions.RequestException:
logging.exception("Error connecting to GitHub")
return None
if r.status_code != requests.codes.ok:
return None
return r.json()
def get_owner_and_repo(self, issue_pr: Any) -> Tuple[str, str]:
owner = issue_pr.group(1)
repo = issue_pr.group(2)
if owner is None:
owner = self.owner
if repo is None:
repo = self.repo
return (owner, repo)
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
# Send help message
if message["content"] == "help":
bot_handler.send_reply(message, self.usage())
return
# Capture owner, repo, id
issue_prs = list(re.finditer(self.HANDLE_MESSAGE_REGEX, message["content"]))
bot_messages = []
if len(issue_prs) > 5:
# We limit to 5 requests to prevent denial-of-service
bot_message = "Please ask for <=5 links in any one request"
bot_handler.send_reply(message, bot_message)
return
for issue_pr in issue_prs:
owner, repo = self.get_owner_and_repo(issue_pr)
if owner and repo:
details = self.get_details_from_github(owner, repo, issue_pr.group(3))
if details is not None:
details["owner"] = owner
details["repo"] = repo
bot_messages.append(self.format_message(details))
else:
bot_messages.append(
f"Failed to find issue/pr: {owner}/{repo}#{issue_pr.group(3)}"
)
else:
bot_messages.append("Failed to detect owner and repository name.")
if len(bot_messages) == 0:
bot_messages.append("Failed to find any issue or PR.")
bot_message = "\n".join(bot_messages)
bot_handler.send_reply(message, bot_message)
handler_class = GithubHandler