Example #1
0
 def __init__(self, *args, **kwargs):
     super(WikipediaPlugin, self).__init__(*args, **kwargs)
     self.store = KeyValueStore(self.name)
     if not self.store.has('apiurl'):
         self.store.set(
             'apiurl', 'https://%s.wikipedia.org/w/api.php'
         )
     self.lang = 'en'
Example #2
0
 def __init__(self, *args, **kwargs):
     super(PrometheusPlugin, self).__init__(*args, **kwargs)
     self.store = KeyValueStore(self.name)
     self.rooms = RoomContextStore(
         [PrometheusPlugin.TYPE_TRACK]
     )
     self.queue_counter = 1L
     self.consumer = MessageConsumer(self.matrix)
     self.consumer.daemon = True
     self.consumer.start()
Example #3
0
    def __init__(self, *args, **kwargs):
        super(JenkinsPlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        self.rooms = RoomContextStore([JenkinsPlugin.TYPE_TRACK])

        if not self.store.has("known_projects"):
            self.store.set("known_projects", [])

        if not self.store.has("secret_token"):
            self.store.set("secret_token", "")

        self.failed_builds = {
            # projectName:branch: { commit:x }
        }
Example #4
0
    def __init__(self, *args, **kwargs):
        super(GithubPlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        self.rooms = RoomContextStore([GithubPlugin.TYPE_TRACK])

        if not self.store.has("known_projects"):
            self.store.set("known_projects", [])

        if not self.store.has("secret_token"):
            self.store.set("secret_token", "")

        if not self.store.has("github_access_token"):
            log.info(
                "A github access_token is required to create github issues.")
            log.info("Issues will be created as this user.")
            token = raw_input("(Optional) Github token: ").strip()
            if token:
                self.store.set("github_access_token", token)
            else:
                log.info("You will not be able to create Github issues.")
Example #5
0
    def __init__(self, *args, **kwargs):
        super(GooglePlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        self.search_state = {}

        if not self.store.has('apiurl'):
            self.store.set('apiurl',
                           'https://www.googleapis.com/customsearch/v1')

        if not self.store.has('apikey'):
            print('API key is required to search with google customserchapi.')
            apikey = raw_input('Google Custom Search API key: ').strip()
            if apikey:
                self.store.set('apikey', apikey)

        if not self.store.has('cx'):
            print('Custom search engine ID required'
                  'to search with google customserchapi.')
            cx = raw_input(
                'Google Custom Search id (https://cse.google.com): ').strip()
            if cx:
                self.store.set('cx', cx)
Example #6
0
class PrometheusPlugin(Plugin):
    """Plugin for interacting with Prometheus."""
    name = "prometheus"

    # Webhooks:
    #    /neb/prometheus
    TYPE_TRACK = "org.matrix.neb.plugin.prometheus.projects.tracking"

    def __init__(self, *args, **kwargs):
        super(PrometheusPlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        self.rooms = RoomContextStore(
            [PrometheusPlugin.TYPE_TRACK]
        )
        self.queue_counter = 1L
        self.consumer = MessageConsumer(self.matrix)
        self.consumer.daemon = True
        self.consumer.start()

    def on_event(self, event, event_type):
        self.rooms.update(event)

    def on_sync(self, sync):
        log.debug("Plugin: Prometheus sync state:")
        self.rooms.init_from_sync(sync)

    def get_webhook_key(self):
        return "prometheus"

    def on_receive_webhook(self, url, data, ip, headers):
        json_data = json.loads(data)
        log.info("recv %s", json_data)
        template = Template(self.store.get("message_template"))
        for alert in json_data.get("alert", []):
            for room_id in self.rooms.get_room_ids():
                log.debug(
                    "Queued message for room "
                    + room_id + " at "
                    + str(self.queue_counter) + ": %s",
                    alert
                )
                queue.put((self.queue_counter, room_id, template.render(alert)))
                self.queue_counter += 1
Example #7
0
class GithubPlugin(Plugin):
    """Plugin for interacting with Github.
    github show projects : Show which github projects this bot recognises.
    github show track|tracking : Show which projects are being tracked.
    github track "owner/repo" "owner/repo" : Track the given projects.
    github add owner/repo : Add the given repo to the tracking list.
    github remove owner/repo : Remove the given repo from the tracking list.
    github stop track|tracking : Stop tracking github projects.
    github create owner/repo "Bug title" "Bug desc" : Create an issue on Github.
    github label add|remove owner/repo issue# label : Label an issue on Github.
    """
    name = "github"
    # New events:
    #    Type: org.matrix.neb.plugin.github.projects.tracking
    #    State: Yes
    #    Content: {
    #        projects: [projectName1, projectName2, ...]
    #    }

    # Webhooks:
    #    /neb/github
    TYPE_TRACK = "org.matrix.neb.plugin.github.projects.tracking"
    TYPE_COLOR = "org.matrix.neb.plugin.github.projects.color"

    TRACKING = ["track", "tracking"]

    def __init__(self, *args, **kwargs):
        super(GithubPlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        self.rooms = RoomContextStore([GithubPlugin.TYPE_TRACK])

        if not self.store.has("known_projects"):
            self.store.set("known_projects", [])

        if not self.store.has("secret_token"):
            self.store.set("secret_token", "")

        if not self.store.has("github_access_token"):
            log.info(
                "A github access_token is required to create github issues.")
            log.info("Issues will be created as this user.")
            token = raw_input("(Optional) Github token: ").strip()
            if token:
                self.store.set("github_access_token", token)
            else:
                log.info("You will not be able to create Github issues.")

    def on_receive_github_push(self, info):
        log.info("recv %s", info)

        # add the project if we didn't know about it before
        if info["repo"] not in self.store.get("known_projects"):
            log.info("Added new repo: %s", info["repo"])
            projects = self.store.get("known_projects")
            projects.append(info["repo"])
            self.store.set("known_projects", projects)

        push_message = ""

        if info["type"] == "delete":
            push_message = '[<u>%s</u>] %s <font color="red"><b>deleted</font> %s</b>' % (
                info["repo"], info["commit_username"], info["branch"])
        elif info["type"] == "commit":
            # form the template:
            # [<repo>] <username> pushed <num> commits to <branch>: <git.io link>
            # 1<=3 of <branch name> <short hash> <full username>: <comment>
            if info["num_commits"] == 1:
                push_message = "[<u>%s</u>] %s pushed to <b>%s</b>: %s  - %s" % (
                    info["repo"], info["commit_username"], info["branch"],
                    info["commit_msg"], info["commit_link"])
            else:
                summary = ""
                max_commits = 3
                count = 0
                for c in info["commits_summary"]:
                    if count == max_commits:
                        break
                    summary += "\n%s: %s" % (c["author"], c["summary"])
                    count += 1

                push_message = "[<u>%s</u>] %s pushed %s commits to <b>%s</b>: %s %s" % (
                    info["repo"], info["commit_username"], info["num_commits"],
                    info["branch"], info["commit_link"], summary)
        else:
            log.warn("Unknown push type. %s", info["type"])
            return

        self.send_message_to_repos(info["repo"], push_message)

    def send_message_to_repos(self, repo, push_message):
        # send messages to all rooms registered with this project.
        for room_id in self.rooms.get_room_ids():
            try:
                if repo in self.rooms.get_content(
                        room_id, GithubPlugin.TYPE_TRACK)["projects"]:
                    self.matrix.send_message_event(
                        room_id, "m.room.message",
                        self.matrix.get_html_body(push_message,
                                                  msgtype="m.notice"))
            except KeyError:
                pass

    def cmd_show(self, event, action):
        """Show information on projects or projects being tracked.
        Show which projects are being tracked. 'github show tracking'
        Show which proejcts are recognised so they could be tracked. 'github show projects'
        """
        if action == "projects":
            projects = self.store.get("known_projects")
            return "Available projects: %s - To add more projects, you must register a webhook on Github." % json.dumps(
                projects)
        elif action in self.TRACKING:
            return self._get_tracking(event["room_id"])
        else:
            return self.cmd_show.__doc__

    @admin_only
    def cmd_add(self, event, repo):
        """Add a repo for tracking. 'github add owner/repo'"""
        if repo not in self.store.get("known_projects"):
            return "Unknown project name: %s." % repo

        try:
            room_repos = self.rooms.get_content(
                event["room_id"], GithubPlugin.TYPE_TRACK)["projects"]
        except KeyError:
            room_repos = []

        if repo in room_repos:
            return "%s is already being tracked." % repo

        room_repos.append(repo)
        self._send_track_event(event["room_id"], room_repos)

        return "Added %s. Commits for projects %s will be displayed as they are commited." % (
            repo, room_repos)

    @admin_only
    def cmd_remove(self, event, repo):
        """Remove a repo from tracking. 'github remove owner/repo'"""
        try:
            room_repos = self.rooms.get_content(
                event["room_id"], GithubPlugin.TYPE_TRACK)["projects"]
        except KeyError:
            room_repos = []

        if repo not in room_repos:
            return "Cannot remove %s : It isn't being tracked." % repo

        room_repos.remove(repo)
        self._send_track_event(event["room_id"], room_repos)

        return "Removed %s. Commits for projects %s will be displayed as they are commited." % (
            repo, room_repos)

    @admin_only
    def cmd_track(self, event, *args):
        if len(args) == 0:
            return self._get_tracking(event["room_id"])

        for project in args:
            if project not in self.store.get("known_projects"):
                return "Unknown project name: %s." % project

        self._send_track_event(event["room_id"], args)

        return "Commits for projects %s will be displayed as they are commited." % (
            args, )

    @admin_only
    def cmd_stop(self, event, action):
        """Stop tracking projects. 'github stop tracking'"""
        if action in self.TRACKING:
            self._send_track_event(event["room_id"], [])
            return "Stopped tracking projects."
        else:
            return self.cmd_stop.__doc__

    @admin_only
    def cmd_create(self, event, *args):
        """Create a new issue. Format: 'create <owner/repo> <title> <desc(optional)>'
        E.g. 'create matrix-org/synapse A bug goes here
        'create matrix-org/synapse "Title here" "desc here" """
        if not args or len(args) < 2:
            return self.cmd_create.__doc__
        project = args[0]
        others = args[1:]
        # others must contain a title, may contain a description. If it contains
        # a description, it MUST be in [1] and be longer than 1 word.
        title = ' '.join(others)
        desc = ""
        try:
            possible_desc = others[1]
            if ' ' in possible_desc:
                desc = possible_desc
                title = others[0]
        except:
            pass

        return self._create_issue(event["user_id"], project, title, desc)

    @admin_only
    def cmd_label_remove(self, event, repo, issue_num, *args):
        """
            Remove a label on an issue.
            Format: 'label remove <owner/repo> <issue num> <label> <label> <label>'
            E.g. 'label remove matrix-org/synapse 323 bug p2 blocked'
        """
        e = self._is_valid_issue_request(repo, issue_num)
        if e:
            return e
        if len(args) == 0:
            return "You must specify at least one label."

        errs = []

        for label in args:
            url = "https://api.github.com/repos/%s/issues/%s/labels/%s" % (
                repo, issue_num, label)
            res = requests.delete(url,
                                  headers={
                                      "Authorization":
                                      "token %s" %
                                      self.store.get("github_access_token")
                                  })
            if res.status_code < 200 or res.status_code >= 300:
                errs.append("Problem removing label %s : HTTP %s" %
                            (label, res.status_code))
                return errs

        if len(errs) == 0:
            return "Removed labels %s" % (json.dumps(args), )
        else:
            return "There was a problem removing some labels:\n" + "\n".join(
                errs)

    @admin_only
    def cmd_label_add(self, event, repo, issue_num, *args):
        """Label an issue. Format: 'label add <owner/repo> <issue num> <label> <label> <label>'
        E.g. 'label add matrix-org/synapse 323 bug p2 blocked'
        """
        e = self._is_valid_issue_request(repo, issue_num)
        if e:
            return e
        if len(args) == 0:
            return "You must specify at least one label."

        url = "https://api.github.com/repos/%s/issues/%s/labels" % (repo,
                                                                    issue_num)
        res = requests.post(url,
                            data=json.dumps(args),
                            headers={
                                "Content-Type":
                                "application/json",
                                "Authorization":
                                "token %s" %
                                self.store.get("github_access_token")
                            })
        if res.status_code < 200 or res.status_code >= 300:
            err = "%s Failed: HTTP %s" % (
                url,
                res.status_code,
            )
            log.error(err)
            return err

        return "Added labels %s" % (json.dumps(args), )

    def _create_issue(self, user_id, project, title, desc=""):
        if not self.store.has("github_access_token"):
            return "This plugin isn't configured to create Github issues."

        # Add a space after the @ to avoid pinging people on Github!
        user_id = user_id.replace("@", "@ ")

        desc = "Created by %s.\n\n%s" % (user_id, desc)
        info = {"title": title, "body": desc}

        url = "https://api.github.com/repos/%s/issues" % project
        res = requests.post(url,
                            data=json.dumps(info),
                            headers={
                                "Content-Type":
                                "application/json",
                                "Authorization":
                                "token %s" %
                                self.store.get("github_access_token")
                            })
        if res.status_code < 200 or res.status_code >= 300:
            err = "%s Failed: HTTP %s" % (
                url,
                res.status_code,
            )
            log.error(err)
            return err

        response = json.loads(res.text)
        return "Created issue: %s" % response["html_url"]

    def _is_valid_issue_request(self, repo, issue_num):
        issue_is_num = True
        try:
            issue_is_num = int(issue_num)
        except ValueError:
            issue_is_num = False

        if "/" not in repo:
            return "Repo must be in the form 'owner/repo' e.g. 'matrix-org/synapse'."

        if not issue_is_num:
            return "Issue number must be a number"

        if not self.store.has("github_access_token"):
            return "This plugin isn't configured to interact with Github issues."

    def _send_track_event(self, room_id, project_names):
        self.matrix.send_state_event(room_id, self.TYPE_TRACK,
                                     {"projects": project_names})

    def _get_tracking(self, room_id):
        try:
            return ("Currently tracking %s" % json.dumps(
                self.rooms.get_content(room_id,
                                       GithubPlugin.TYPE_TRACK)["projects"]))
        except KeyError:
            return "Not tracking any projects currently."

    def on_event(self, event, event_type):
        self.rooms.update(event)

    def on_sync(self, sync):
        log.debug("Plugin: Github sync state:")
        self.rooms.init_from_sync(sync)

    def get_webhook_key(self):
        return "github"

    def on_receive_pull_request(self, data):
        action = data["action"]
        pull_req_num = data["number"]
        repo_name = data["repository"]["full_name"]
        pr = data["pull_request"]
        pr_url = pr["html_url"]
        pr_state = pr["state"]
        pr_title = pr["title"]

        user = data["sender"]["login"]

        action_target = ""
        if pr.get("assignee") and pr["assignee"].get("login"):
            action_target = " to %s" % (pr["assignee"]["login"], )

        msg = "[<u>%s</u>] %s %s <b>pull request #%s</b>: %s [%s]%s - %s" % (
            repo_name, user, action, pull_req_num, pr_title, pr_state,
            action_target, pr_url)

        self.send_message_to_repos(repo_name, msg)

    def on_receive_create(self, data):
        if data["ref_type"] != "branch":
            return  # only echo branch creations for now.

        branch_name = data["ref"]
        user = data["sender"]["login"]
        repo_name = data["repository"]["full_name"]

        msg = '[<u>%s</u>] %s <font color="green">created</font> a new branch: <b>%s</b>' % (
            repo_name, user, branch_name)

        self.send_message_to_repos(repo_name, msg)

    def on_receive_ping(self, data):
        repo_name = data.get("repository", {}).get("full_name")
        # add the project if we didn't know about it before
        if repo_name and repo_name not in self.store.get("known_projects"):
            log.info("Added new repo: %s", repo_name)
            projects = self.store.get("known_projects")
            projects.append(repo_name)
            self.store.set("known_projects", projects)

    def on_receive_comment(self, data):
        repo_name = data["repository"]["full_name"]
        issue = data["issue"]
        comment = data["comment"]
        is_pull_request = "pull_request" in issue
        if not is_pull_request:
            return  # don't bother displaying issue comments

        pr_title = issue["title"]
        pr_num = issue["number"]
        pr_username = issue["user"]["login"]
        comment_url = comment["html_url"]
        username = comment["user"]["login"]

        msg = "[<u>%s</u>] %s commented on %s's <b>pull request #%s</b>: %s - %s" % (
            repo_name, username, pr_username, pr_num, pr_title, comment_url)
        self.send_message_to_repos(repo_name, msg)

    def on_receive_pull_request_comment(self, data):
        repo_name = data["repository"]["full_name"]
        username = data["sender"]["login"]
        pull_request = data["pull_request"]
        pr_username = pull_request["user"]["login"]
        pr_num = pull_request["number"]
        assignee = "None"
        if data["pull_request"].get("assignee"):
            assignee = data["pull_request"]["assignee"]["login"]
        pr_title = pull_request["title"]
        comment_url = data["comment"]["html_url"]

        msg = "[<u>%s</u>] %s made a line comment on %s's <b>pull request #%s</b> (assignee: %s): %s - %s" % (
            repo_name, username, pr_username, pr_num, assignee, pr_title,
            comment_url)
        self.send_message_to_repos(repo_name, msg)

    def on_receive_issue(self, data):
        action = data["action"]
        repo_name = data["repository"]["full_name"]
        issue = data["issue"]
        title = issue["title"]
        issue_num = issue["number"]
        url = issue["html_url"]

        user = data["sender"]["login"]

        if action == "assigned":
            try:
                assignee = data["assignee"]["login"]
                msg = "[<u>%s</u>] %s assigned issue #%s to %s: %s - %s" % (
                    repo_name, user, issue_num, assignee, title, url)
                self.send_message_to_repos(repo_name, msg)
                return
            except:
                pass

        msg = "[<u>%s</u>] %s %s issue #%s: %s - %s" % (
            repo_name, user, action, issue_num, title, url)

        self.send_message_to_repos(repo_name, msg)

    def on_receive_webhook(self, url, data, ip, headers):
        if self.store.get("secret_token"):
            token_sha1 = headers.get('X-Hub-Signature')
            payload_body = data
            calc = hmac.new(str(self.store.get("secret_token")), payload_body,
                            sha1)
            calc_sha1 = "sha1=" + calc.hexdigest()
            if token_sha1 != calc_sha1:
                log.warn("GithubWebServer: FAILED SECRET TOKEN AUTH. IP=%s",
                         ip)
                return ("", 403, {})

        json_data = json.loads(data)
        is_private_repo = json_data.get("repository", {}).get("private")
        if is_private_repo:
            log.info("Received private repo event for %s",
                     json_data["repository"].get("name"))
            return

        event_type = headers.get('X-GitHub-Event')
        if event_type == "pull_request":
            self.on_receive_pull_request(json_data)
            return
        elif event_type == "issues":
            self.on_receive_issue(json_data)
            return
        elif event_type == "create":
            self.on_receive_create(json_data)
            return
        elif event_type == "ping":
            self.on_receive_ping(json_data)
            return
        elif event_type == "issue_comment":
            # INCLUDES PR COMMENTS!!!
            # But not line comments!
            self.on_receive_comment(json_data)
            return
        elif event_type == "pull_request_review_comment":
            self.on_receive_pull_request_comment(json_data)
            return

        j = json_data
        repo_name = j["repository"]["full_name"]
        # strip 'refs/heads' from 'refs/heads/branch_name'
        branch = '/'.join(j["ref"].split('/')[2:])

        commit_msg = ""
        commit_name = ""
        commit_link = ""
        short_hash = ""
        push_type = "commit"

        if j["head_commit"]:
            commit_msg = j["head_commit"]["message"]
            commit_name = j["head_commit"]["committer"]["name"]
            commit_link = j["head_commit"]["url"]
            # short hash please
            short_hash = commit_link.split('/')[-1][0:8]
            commit_link = '/'.join(
                commit_link.split('/')[0:-1]) + "/" + short_hash
        elif j["deleted"]:
            # looks like this branch was deleted, no commit and deleted=true
            commit_name = j["pusher"]["name"]
            push_type = "delete"

        commit_uname = None
        try:
            commit_uname = j["head_commit"]["committer"]["username"]
        except Exception:
            # possible if they haven't tied up with a github account
            commit_uname = commit_name

        # look for multiple commits
        num_commits = 1
        commits_summary = []
        if "commits" in j and len(j["commits"]) > 1:
            num_commits = len(j["commits"])
            for c in j["commits"]:
                cname = None
                try:
                    cname = c["author"]["username"]
                except:
                    cname = c["author"]["name"]
                commits_summary.append({
                    "author": cname,
                    "summary": c["message"]
                })

        self.on_receive_github_push({
            "branch": branch,
            "repo": repo_name,
            "commit_msg": commit_msg,
            "commit_username": commit_uname,
            "commit_name": commit_name,
            "commit_link": commit_link,
            "commit_hash": short_hash,
            "type": push_type,
            "num_commits": num_commits,
            "commits_summary": commits_summary
        })
Example #8
0
class GooglePlugin(Plugin):
    """Google search.
    (Default) google search <text to search>
    google image <text to search>
    google next"""

    name = 'google'
    default_method = 'cmd_search'

    def __init__(self, *args, **kwargs):
        super(GooglePlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        self.search_state = {}

        if not self.store.has('apiurl'):
            self.store.set('apiurl',
                           'https://www.googleapis.com/customsearch/v1')

        if not self.store.has('apikey'):
            print('API key is required to search with google customserchapi.')
            apikey = raw_input('Google Custom Search API key: ').strip()
            if apikey:
                self.store.set('apikey', apikey)

        if not self.store.has('cx'):
            print('Custom search engine ID required'
                  'to search with google customserchapi.')
            cx = raw_input(
                'Google Custom Search id (https://cse.google.com): ').strip()
            if cx:
                self.store.set('cx', cx)

    def cmd_search(self, event, *args):
        """Google search (default method). 'google search <text>'"""
        query = {}
        query['q'] = ' '.join(args)
        query['num'] = 1
        query['start'] = 1
        query['alt'] = 'json'
        query['key'] = self.store.get('apikey')
        query['cx'] = self.store.get('cx')
        res = self._gquery(query, event)
        if 'items' in res:
            self.matrix.send_message(event['room_id'], res['items'][0]['link'])
            return None
        else:
            return self.tr.trans('Nothing found...')

    def cmd_image(self, event, *args):
        """Google search image. 'google image <text>'"""
        query = {}
        query['q'] = ' '.join(args)
        query['num'] = 1
        query['start'] = 1
        query['searchType'] = 'image'
        query['imgSize'] = 'large'
        query['alt'] = 'json'
        query['key'] = self.store.get('apikey')
        query['cx'] = self.store.get('cx')
        res = self._gquery(query, event)

        if 'items' in res:
            if self._send_image(res, event):
                return None
            else:
                return self.tr.trans('Image upload error.')
        else:
            return self.tr.trans('Nothing found...')

    def cmd_next(self, event, *args):
        """Search next. 'google next'"""
        if event['room_id'] in self.search_state:
            query = self.search_state[event['room_id']]
            query['start'] = query['start'] + 1
            res = self._gquery(query, event)
            if 'items' in res:
                if 'searchType' in query:
                    if query['searchType'] == 'image':
                        # image query
                        if self._send_image(res, event):
                            return None
                        else:
                            return self.tr.trans('Image upload error.')
                    else:
                        log.error('Strange query %r in room %s' %
                                  (query, event['room_id']))
                else:
                    # simple query
                    self.matrix.send_message(event['room_id'],
                                             res['items'][0]['link'])
                    return None
            else:
                return self.tr.trans('Nothing found...')
        else:
            return self.tr.trans('No pervous search in this room.')

    cmd_more = cmd_next
    """Search next. 'google more'"""

    def _send_image(self, res, event):
        """Send image to room"""
        # TODO add more error handling
        try:
            i = requests.get(res['items'][0]['link'], timeout=30)
        except ConnectionError as err:
            log.error('Image %s download error: %r' %
                      (res['items'][0]['link'], err))
            return False
        ires = self.matrix.media_upload(i.content, res['items'][0]['mime'])
        if 'content_uri' in ires:
            self.matrix.send_content(event['room_id'], ires['content_uri'],
                                     res['items'][0]['title'], 'm.image')
            return True
        else:
            log.error('Image upload error: %r' % ires)
            return False

    def _gquery(self, query, event):
        """Google query func."""
        self.search_state[event['room_id']] = query
        r = requests.get(self.store.get('apiurl'), params=query)
        if 'error' in r.json():
            log.error('Google search error: %r' % r.json()['error'])

        log.debug('Google query result: %r' % r.json())
        return r.json()
Example #9
0
class JenkinsPlugin(Plugin):
    """ Plugin for receiving Jenkins notifications via the Notification Plugin.
    jenkins show projects : Display which projects this bot recognises.
    jenkins show track|tracking : Display which projects this bot is tracking.
    jenkins track project1 project2 ... : Track Jenkins notifications for the named projects.
    jenkins stop track|tracking : Stop tracking Jenkins notifications.
    jenkins add projectName : Start tracking projectName.
    jenkins remove projectName : Stop tracking projectName.
    """
    name = "jenkins"

    # https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin

    # New events:
    #    Type: org.matrix.neb.plugin.jenkins.projects.tracking
    #    State: Yes
    #    Content: {
    #        projects: [projectName1, projectName2, ...]
    #    }

    # Webhooks:
    #    /neb/jenkins

    TRACKING = ["track", "tracking"]
    TYPE_TRACK = "org.matrix.neb.plugin.jenkins.projects.tracking"

    def __init__(self, *args, **kwargs):
        super(JenkinsPlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        self.rooms = RoomContextStore([JenkinsPlugin.TYPE_TRACK])

        if not self.store.has("known_projects"):
            self.store.set("known_projects", [])

        if not self.store.has("secret_token"):
            self.store.set("secret_token", "")

        self.failed_builds = {
            # projectName:branch: { commit:x }
        }

    def cmd_show(self, event, action):
        """Show information on projects or projects being tracked.
        Show which projects are being tracked. 'jenkins show tracking'
        Show which proejcts are recognised so they could be tracked.
        'jenkins show projects'
        """
        if action in self.TRACKING:
            return self._get_tracking(event["room_id"])
        elif action == "projects":
            projects = self.store.get("known_projects")
            return "Available projects: %s" % json.dumps(projects)
        else:
            return "Invalid arg '%s'.\n %s" % (action, self.cmd_show.__doc__)

    @admin_only
    def cmd_track(self, event, *args):
        """Track projects. 'jenkins track Foo "bar with spaces"'"""
        if len(args) == 0:
            return self._get_tracking(event["room_id"])

        for project in args:
            if project not in self.store.get("known_projects"):
                return "Unknown project name: %s." % project

        self._send_track_event(event["room_id"], args)

        return "Jenkins notifications for projects %s will be displayed when they fail." % (
            args)

    @admin_only
    def cmd_add(self, event, project):
        """Add a project for tracking. 'jenkins add projectName'"""
        if project not in self.store.get("known_projects"):
            return "Unknown project name: %s." % project

        try:
            room_projects = self.rooms.get_content(
                event["room_id"], JenkinsPlugin.TYPE_TRACK)["projects"]
        except KeyError:
            room_projects = []

        if project in room_projects:
            return "%s is already being tracked." % project

        room_projects.append(project)
        self._send_track_event(event["room_id"], room_projects)

        return "Added %s. Jenkins notifications for projects %s will be displayed when they fail." % (
            project, room_projects)

    @admin_only
    def cmd_remove(self, event, project):
        """Remove a project from tracking. 'jenkins remove projectName'"""
        try:
            room_projects = self.rooms.get_content(
                event["room_id"], JenkinsPlugin.TYPE_TRACK)["projects"]
        except KeyError:
            room_projects = []

        if project not in room_projects:
            return "Cannot remove %s : It isn't being tracked." % project

        room_projects.remove(project)
        self._send_track_event(event["room_id"], room_projects)

        return "Removed %s. Jenkins notifications for projects %s will be displayed when they fail." % (
            project, room_projects)

    @admin_only
    def cmd_stop(self, event, action):
        """Stop tracking projects. 'jenkins stop tracking'"""
        if action in self.TRACKING:
            self._send_track_event(event["room_id"], [])
            return "Stopped tracking projects."
        else:
            return "Invalid arg '%s'.\n %s" % (action, self.cmd_stop.__doc__)

    def _get_tracking(self, room_id):
        try:
            return ("Currently tracking %s" % json.dumps(
                self.rooms.get_content(room_id,
                                       JenkinsPlugin.TYPE_TRACK)["projects"]))
        except KeyError:
            return "Not tracking any projects currently."

    def _send_track_event(self, room_id, project_names):
        self.matrix.send_state_event(room_id, self.TYPE_TRACK,
                                     {"projects": project_names})

    def send_message_to_repos(self, repo, push_message):
        # send messages to all rooms registered with this project.
        for room_id in self.rooms.get_room_ids():
            try:
                if (repo in self.rooms.get_content(
                        room_id, JenkinsPlugin.TYPE_TRACK)["projects"]):
                    self.matrix.send_message_event(
                        room_id, "m.room.message",
                        self.matrix.get_html_body(push_message,
                                                  msgtype="m.notice"))
            except KeyError:
                pass

    def on_event(self, event, event_type):
        self.rooms.update(event)

    def on_sync(self, sync):
        log.debug("Plugin: Jenkins sync state:")
        self.rooms.init_from_sync(sync)

    def get_webhook_key(self):
        return "jenkins"

    def on_receive_webhook(self, url, data, ip, headers):
        # data is of the form:
        # {
        #   "name":"Synapse",
        #   "url":"job/Synapse/",
        #   "build": {
        #     "full_url":"http://*****:*****@github.com:matrix-org/synapse.git",
        #        "branch":"origin/develop",
        #        "commit":"72aef114ab1201f5a5cd734220c9ec738c4e2910"
        #      },
        #     "artifacts":{}
        #    }
        # }
        log.info("URL: %s", url)
        log.info("Data: %s", data)
        log.info("Headers: %s", headers)

        j = json.loads(data)
        name = j["name"]

        query_dict = urlparse.parse_qs(urlparse.urlparse(url).query)
        if self.store.get("secret_token"):
            if "secret" not in query_dict:
                log.warn("Jenkins webhook: Missing secret.")
                return ("", 403, {})

            # The jenkins Notification plugin does not support any sort of
            # "execute this code on this json object before you send" so we can't
            # send across HMAC SHA1s like with github :( so a secret token will
            # have to do.
            secrets = query_dict["secret"]
            if len(secrets) > 1:
                log.warn(
                    "Jenkins webhook: FAILED SECRET TOKEN AUTH. Too many secrets. IP=%s",
                    ip)
                return ("", 403, {})
            elif secrets[0] != self.store.get("secret_token"):
                log.warn(
                    "Jenkins webhook: FAILED SECRET TOKEN AUTH. Mismatch. IP=%s",
                    ip)
                return ("", 403, {})
            else:
                log.info("Jenkins webhook: Secret verified.")

        # add the project if we didn't know about it before
        if name not in self.store.get("known_projects"):
            log.info("Added new job: %s", name)
            projects = self.store.get("known_projects")
            projects.append(name)
            self.store.set("known_projects", projects)

        status = j["build"]["status"]
        branch = None
        commit = None
        git_url = None
        jenkins_url = None
        info = ""
        try:
            branch = j["build"]["scm"]["branch"]
            commit = j["build"]["scm"]["commit"]
            git_url = j["build"]["scm"]["url"]
            jenkins_url = j["build"]["full_url"]
            # try to format the git url nicely
            if (git_url.startswith("*****@*****.**")
                    and git_url.endswith(".git")):
                # [email protected]:matrix-org/synapse.git
                org_and_repo = git_url.split(":")[1][:-4]
                commit = "https://github.com/%s/commit/%s" % (org_and_repo,
                                                              commit)

            info = "%s commit %s - %s" % (branch, commit, jenkins_url)
        except KeyError:
            pass

        fail_key = "%s:%s" % (name, branch)

        if status.upper() != "SUCCESS":
            # complain
            msg = '<font color="red">[%s] <b>%s - %s</b></font>' % (
                name, status, info)

            if fail_key in self.failed_builds:
                info = "%s failing since commit %s - %s" % (
                    branch, self.failed_builds[fail_key]["commit"],
                    jenkins_url)
                msg = '<font color="red">[%s] <b>%s - %s</b></font>' % (
                    name, status, info)
            else:  # add it to the list
                self.failed_builds[fail_key] = {"commit": commit}

            self.send_message_to_repos(name, msg)
        else:
            # do we need to prod people?
            if fail_key in self.failed_builds:
                info = "%s commit %s" % (branch, commit)
                msg = '<font color="green">[%s] <b>%s - %s</b></font>' % (
                    name, status, info)
                self.send_message_to_repos(name, msg)
                self.failed_builds.pop(fail_key)
Example #10
0
class WikipediaPlugin(Plugin):
    """ Wikipedia search plugin.
        Usage:
        'wiki <text to search>'
        Example:
        'wiki russian desman'
    """
    name = 'wiki'

    default_method = 'cmd_search'

    def __init__(self, *args, **kwargs):
        super(WikipediaPlugin, self).__init__(*args, **kwargs)
        self.store = KeyValueStore(self.name)
        if not self.store.has('apiurl'):
            self.store.set(
                'apiurl', 'https://%s.wikipedia.org/w/api.php'
            )
        self.lang = 'en'

    def _qwiki(self, query):
        """ Query wikipedia API """
        r = requests.get(self.store.get('apiurl') % self.lang, params=query)
        if 'error' in r.json():
            log.error('Wikipedia search: %r' % r.json()['error'])
            # return self.tr.trans('When searching on Wikipedia, an error occurred.')
        else:
            if 'warnings' in r.json():
                log.warn('Wikipedia search: %r' % r.json()['warnings'])
            if 'query' in r.json():
                if 'normalized' in r.json()['query']:
                    query['titles'] = r.json()['query']['normalized'][0]['to']
                    log.debug("Wikipedia query to %s is normalized to %s" % (
                        r.json()['query']['normalized'][0]['from'],
                        r.json()['query']['normalized'][0]['to']
                    ))
                    return self._qwiki(query)
                if 'pages' in r.json()['query']:
                    return r.json()['query']['pages']
                else:
                    log.debug('Wikipedia result without pages: %r %r' % (query, r.json()))
                    # return self.tr.trans('Nothing found.')

    def _get_url(self, pageid):
        """ Get wiki URL by pageid """
        query = {}
        query['action'] = 'query'
        query['prop'] = 'info'
        query['format'] = 'json'
        query['inprop'] = 'url'
        query['pageids'] = pageid

        r = requests.get(self.store.get('apiurl') % self.lang, params=query)
        if 'error' in r.json():
            log.error('Wikipedia search: %r' % r.json()['error'])
        else:
            if 'warnings' in r.json():
                log.warn('Wikipedia search: %r' % r.json()['warnings'])
            if 'query' in r.json():
                if 'pages' in r.json()['query']:
                    for page in r.json()['query']['pages']:
                        if 'fullurl' in r.json()['query']['pages'][page]:
                            return r.json()['query']['pages'][page]['fullurl']
        return None

    @civility
    def cmd_search(self, event, *args):
        """Search in wikipedia. 'wiki <text to search>'"""
        self.lang = self.tr.detect_lang(' '.join(args))
        query = {}
        query['action'] = 'query'
        query['prop'] = 'extracts'
        query['format'] = 'json'
        query['redirects'] = 1
        query['titles'] = ' '.join(args)

        res = []
        pages = self._qwiki(query)
        for page in pages:
            if 'extract' in pages[page]:
                self.send_html(
                    event['room_id'],
                    pages[page]['extract']
                )
            link = self._get_url(page)
            if link:
                res.append(link)
            if 'missing' in pages[page]:
                log.debug('Wikipedia result missing page: %r' % pages[page])
                return self.tr.trans('Page %s is missing') % ' '.join(args)
        # After text return collected links
        return res