from __future__ import absolute_import from future import standard_library standard_library.install_aliases() from . import github import json import os import requests import six.moves.configparser import urllib.request, urllib.error, urllib.parse class IssueHandler(object): ''' This plugin facilitates sending issues to github, when an item is prefixed with '@issue' or '@bug' It will also write items to the issues stream, as well as reporting it to github ''' URL = 'https://api.github.com/repos/{}/{}/issues' CHARACTER_LIMIT = 70 CONFIG_FILE = '~/.github-issue-bot/github-issue-bot.conf' REPO_NAME = '' REPO_OWNER = '' def __init__(self): # gets token from config file # Token at CONFIG_FILE address config = six.moves.configparser.ConfigParser() config.read([os.path.expanduser(self.CONFIG_FILE)]) self.REPO_NAME = config.get('github', 'github_repo') self.REPO_OWNER = config.get('github', 'github_repo_owner') def usage(self): return ''' This plugin will allow users to flag messages as being issues with Zulip by using te prefix '@issue' Before running this, make sure to create a stream called "issues" that your API user can send to. Also, make sure that the credentials of the github bot have been typed in correctly, that there is a personal access token with access to public repositories ONLY, and that the repository name is entered correctly. Check ~/.github-issue-bot/github-issue-bot.conf, and make sure there are github_repo (The name of the repo to post to) github_repo_owner (The owner of the repo to post to) github_username (The username of the github bot) github_token (The personal access token for the github bot) ''' def triage_message(self, message, client): # return True if we want to (possibly) respond to this message original_content = message['content'] # This next line of code is defensive, as we # never want to get into an infinite loop of posting follow # ups for own follow ups! if message['display_recipient'] == 'issue': return False is_issue = original_content.startswith('@issue') return is_issue def handle_message(self, message, client, state_handler): original_content = message['content'] original_sender = message['sender_email'] new_content = original_content.replace('@issue', 'by {}:'.format(original_sender,)) # gets the repo url url_new = self.URL.format(self.REPO_OWNER, self.REPO_NAME) # signs into github using the provided username and password session = github.auth() # Gets rid of the @issue in the issue title issue_title = message['content'].replace('@issue', '').strip() issue_content = '' new_issue_title = '' for part_of_title in issue_title.split(): if len(new_issue_title) < self.CHARACTER_LIMIT: new_issue_title += '{} '.format(part_of_title) else: issue_content += '{} '.format(part_of_title) new_issue_title = new_issue_title.strip() issue_content = issue_content.strip() new_issue_title += '...' # Creates the issue json, that is transmitted to the github api servers issue = { 'title': new_issue_title, 'body': '{} **Sent by [{}](https://chat.zulip.org/#) from zulip**'.format(issue_content, original_sender), 'assignee': '', 'milestone': 'none', 'labels': [''], } # Sends the HTTP post request r = session.post(url_new, json.dumps(issue)) if r.ok: # sends the message onto the 'issues' stream so it can be seen by zulip users client.send_message(dict( type='stream', to='issues', subject=message['sender_email'], # Adds a check mark so that the user can verify if it has been sent content='{} :heavy_check_mark:'.format(new_content), )) return # This means that the issue has not been sent # sends the message onto the 'issues' stream so it can be seen by zulip users client.send_message(dict( type='stream', to='issues', subject=message['sender_email'], # Adds a cross so that the user can see that it has failed, and provides a link to a # google search that can (hopefully) direct them to the error content='{} :x: Code: [{}](https://www.google.com/search?q=Github HTTP {} Error {})' .format(new_content, r.status_code, r.status_code, r.content), )) handler_class = IssueHandler