Skip to content

Commit

Permalink
Merge f2f6d67 into ec3e5e8
Browse files Browse the repository at this point in the history
  • Loading branch information
zacps committed Dec 31, 2016
2 parents ec3e5e8 + f2f6d67 commit 95614a3
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 0 deletions.
27 changes: 27 additions & 0 deletions contrib_bots/lib/github_detail.md
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.
123 changes: 123 additions & 0 deletions contrib_bots/lib/github_detail.py
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

0 comments on commit 95614a3

Please sign in to comment.