-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# GitHub detail bot | ||
This bot links and details issues and pull requests. | ||
To use it username mention the bot then type an id: | ||
Ids can be specified in three different forms: | ||
- Id only: `#2000` | ||
- Repo and id: `zulip#2000` | ||
- Owner and repo and id `zulip/zulip#2000` | ||
|
||
Both the username mention and the id can occur at any time in the message. You | ||
can also mention multiple ids in a single message. | ||
|
||
The bot *requires* a default owner and repo to be configured. | ||
The configuration file should be located at `~/.contrib_bots/github_detail.ini`. | ||
It should look like this: | ||
```ini | ||
[GitHub] | ||
owner = <owner> | ||
repo = <repo> | ||
``` | ||
If you don't want a default repo you can define one that doesn't exist, the bot | ||
fails silently. | ||
|
||
The bot won't reply to any other bots to avoid infinite loops. | ||
|
||
Zulip also has a realm feature which will show information about linked sites. | ||
Because of this the bot escapes it's links. Once issue [#2968](https://github.com/zulip/zulip/issues/2968) is resolved this can be | ||
changed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
from __future__ import print_function | ||
|
||
import re | ||
import os | ||
import sys | ||
import logging | ||
import six.moves.configparser | ||
|
||
import requests | ||
|
||
class GithubHandler(object): | ||
''' | ||
This bot provides details on github issues and pull requests when they're | ||
refrenced in the chat. | ||
''' | ||
|
||
config_path = os.path.expanduser('~/.contrib_bots/github_detail.ini') | ||
GITHUB_ISSUE_URL_TEMPLATE = 'https://api.github.com/repos/{owner}/{repo}/issues/{id}' | ||
GITHUB_PULL_URL_TEMPLATE = 'https://api.github.com/repos/{owner}/{reepo}/pulls/{id}' | ||
|
||
def __init__(self): | ||
config = six.moves.configparser.ConfigParser() | ||
config.readfp(open(self.config_path)) | ||
if config.get('Github', 'owner'): | ||
self.owner = config.get('Github', 'owner') | ||
else: | ||
# Allowing undefined default repos would require multiple triage_message regexs. | ||
# It's simpler to require them to be defined. | ||
sys.exit('Default owner not defined') | ||
|
||
if config.get('Github', 'repo'): | ||
self.repo = config.get('Github', 'repo') | ||
else: | ||
sys.exit('Default repo not defined') | ||
|
||
def usage(self): | ||
# type: () -> None | ||
return ("This plugin displays details on github issues and pull requests. " | ||
"To refrence an issue or pr type @github-detail then anytime in the message it's id like '#2561'." | ||
"If you want to refrence an issues from a repository other than the default use" | ||
"the user/repo#id form.") | ||
|
||
def triage_message(self, message, client): | ||
# type: () -> bool | ||
# Check the message contains a username mention and #<number> | ||
# replying to another bot | ||
regex = re.search("(?:@(?:\*\*{}.+?#\d+".format(re.escape(client.full_name))) | ||
return re.search(regex, message['content']) and not message['sender_email'].endswith('-bot@zulip.com') | ||
|
||
def format_message(self, details): | ||
# type: (Dict[Text, Union[Text, int, bool]]) -> Text | ||
|
||
number = details['number'] | ||
title = details['title'] | ||
link = details['html_url'] | ||
# Truncate if longer than 200 characters. | ||
description = (details['body'][:200] + | ||
'..') if len(details['body']) > 202 else details['body'] | ||
status = details['state'].title() | ||
|
||
return '**{id} | {title}** `{link}` - **{status}**\n```quote\n{description}\n```'\ | ||
.format(id=number, title=title, link=link, status=status, description=description) | ||
|
||
def get_details_from_github(self, owner, repo, number): | ||
# type: (Text, Text, Text) -> Dict[Text, Union[Text, Int, Bool]] | ||
# Gets the details of an issues or pull request | ||
|
||
# Try to get an issue, try to get a pull if that fails | ||
try: | ||
r = requests.get( | ||
self.GITHUB_ISSUE_URL_TEMPLATE.format(owner=owner, repo=repo, id=number)) | ||
except requests.exceptions.RequestException as e: | ||
logging.exception(e) | ||
return | ||
|
||
if r.status_code == 404: | ||
try: | ||
r = requests.get( | ||
self.GITHUB_PULL_URL_TEMPLATE.format(owner=owner, repo=repo, id=number)) | ||
except requests.exceptions.RequestException as e: | ||
logging.exception(e) | ||
return | ||
|
||
if r.status_code != requests.codes.ok: | ||
return | ||
|
||
return r.json() | ||
|
||
def get_owner_and_repo(self, issue_pr): | ||
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, client, state_handler): | ||
# type: () -> None | ||
# Capture owner, repo, id | ||
issue_prs = re.finditer( | ||
"([\w-]+)?\/?([\w-]+)?#(\d+)", message['content']) | ||
|
||
bot_messages = [] | ||
for issue_pr in issue_prs: | ||
owner, repo = self.get_owner_and_repo(issue_pr) | ||
details = self.get_details_from_github(owner, repo, issue_pr.group(3)) | ||
bot_messages.append(self.format_message(details)) | ||
# Fail silently | ||
if not bot_messages: | ||
return | ||
bot_message = '\n'.join(bot_messages) | ||
|
||
client.send_message(dict( | ||
type='stream', | ||
to=message['display_recipient'], | ||
subject=message['subject'], | ||
content=bot_message, | ||
|
||
)) | ||
|
||
|
||
handler_class = GithubHandler |