From 14f2fd6a03002f410125ec5cce1e6150b3178887 Mon Sep 17 00:00:00 2001 From: jesopo Date: Mon, 24 Jun 2019 14:52:37 +0100 Subject: [PATCH] split github webhook logic in to a more general webhook module --- modules/github.py | 223 +++++++++++ modules/github/ghwebhook.md | 53 --- modules/webhooks/__init__.py | 190 ++++++++++ .../__init__.py => webhooks/github.py} | 350 ++---------------- 4 files changed, 440 insertions(+), 376 deletions(-) create mode 100644 modules/github.py delete mode 100644 modules/github/ghwebhook.md create mode 100644 modules/webhooks/__init__.py rename modules/{github/__init__.py => webhooks/github.py} (54%) diff --git a/modules/github.py b/modules/github.py new file mode 100644 index 00000000..14cb2314 --- /dev/null +++ b/modules/github.py @@ -0,0 +1,223 @@ +#--depends-on channel_access +#--depends-on check_mode +#--depends-on commands +#--depends-on config +#--depends-on permissions +#--depends-on rest_api + +import datetime, itertools, json, math, re, urllib.parse +from src import EventManager, ModuleManager, utils + +COLOR_BRANCH = utils.consts.ORANGE +COLOR_REPO = utils.consts.GREY +COLOR_POSITIVE = utils.consts.GREEN +COLOR_NEUTRAL = utils.consts.LIGHTGREY +COLOR_NEGATIVE = utils.consts.RED +COLOR_ID = utils.consts.PINK + +API_ISSUE_URL = "https://api.github.com/repos/%s/%s/issues/%s" +API_PULL_URL = "https://api.github.com/repos/%s/%s/pulls/%s" + +@utils.export("channelset", {"setting": "github-default-repo", + "help": "Set the default github repo for the current channel", + "example": "jesopo/bitbot"}) +@utils.export("channelset", {"setting": "auto-github", + "help": "Enable/disable automatically getting github issue/PR info", + "validate": utils.bool_or_none, "example": "on"}) +@utils.export("channelset", {"setting": "auto-github-cooldown", + "help": "Set amount of seconds between auto-github duplicates", + "validate": utils.int_or_none, "example": "300"}) +class Module(ModuleManager.BaseModule): + def _parse_ref(self, channel, ref): + repo, _, number = ref.rpartition("#") + org, _, repo = repo.partition("/") + + default_repo = channel.get_setting("github-default-repo", "") + default_org, _, default_repo = default_repo.partition("/") + + if org and not repo: + repo = org or default_repo + org = default_org + else: + org = org or default_org + repo = repo or default_repo + + if not org or not repo or not number: + raise utils.EventError("Please provide username/repo#number") + if not number.isdigit(): + raise utils.EventError("Issue number must be a number") + return org, repo, number + + def _short_url(self, url): + try: + page = utils.http.request("https://git.io", method="POST", + post_data={"url": url}) + return page.headers["Location"] + except utils.http.HTTPTimeoutException: + self.log.warn( + "HTTPTimeoutException while waiting for github short URL", []) + return url + + def _change_count(self, n, symbol, color): + return utils.irc.color("%s%d" % (symbol, n), color)+utils.irc.bold("") + def _added(self, n): + return self._change_count(n, "+", COLOR_POSITIVE) + def _removed(self, n): + return self._change_count(n, "-", COLOR_NEGATIVE) + def _modified(self, n): + return self._change_count(n, "~", utils.consts.PURPLE) + + def _parse_issue(self, page, username, repository, number): + repo = utils.irc.color("%s/%s" % (username, repository), COLOR_REPO) + number = utils.irc.color("#%s" % number, COLOR_ID) + labels = [label["name"] for label in page.data["labels"]] + labels_str = "" + if labels: + labels_str = "[%s] " % ", ".join(labels) + + url = self._short_url(page.data["html_url"]) + + state = page.data["state"] + if state == "open": + state = utils.irc.color("open", COLOR_NEUTRAL) + elif state == "closed": + state = utils.irc.color("closed", COLOR_NEGATIVE) + + return "(%s issue%s, %s) %s %s%s" % ( + repo, number, state, page.data["title"], labels_str, url) + def _get_issue(self, username, repository, number): + return utils.http.request( + API_ISSUE_URL % (username, repository, number), + json=True) + + @utils.hook("received.command.ghissue", min_args=1) + def github_issue(self, event): + if event["target"].get_setting("github-hide-prefix", False): + event["stdout"].hide_prefix() + event["stderr"].hide_prefix() + + username, repository, number = self._parse_ref( + event["target"], event["args_split"][0]) + + page = self._get_issue(username, repository, number) + if page and page.code == 200: + self._parse_issue(page, username, repository, number) + else: + event["stderr"].write("Could not find issue") + + def _parse_pull(self, page, username, repository, number): + repo = utils.irc.color("%s/%s" % (username, repository), COLOR_REPO) + number = utils.irc.color("#%s" % number, COLOR_ID) + branch_from = page.data["head"]["label"] + branch_to = page.data["base"]["label"] + added = self._added(page.data["additions"]) + removed = self._removed(page.data["deletions"]) + url = self._short_url(page.data["html_url"]) + + state = page.data["state"] + if page.data["merged"]: + state = utils.irc.color("merged", COLOR_POSITIVE) + elif state == "open": + state = utils.irc.color("open", COLOR_NEUTRAL) + elif state == "closed": + state = utils.irc.color("closed", COLOR_NEGATIVE) + + return "(%s PR%s, %s) %s → %s [%s/%s] %s %s" % ( + repo, number, state, branch_from, branch_to, added, removed, + page.data["title"], url) + def _get_pull(self, username, repository, number): + return utils.http.request( + API_PULL_URL % (username, repository, number), + json=True) + @utils.hook("received.command.ghpull", min_args=1) + def github_pull(self, event): + if event["target"].get_setting("github-hide-prefix", False): + event["stdout"].hide_prefix() + event["stderr"].hide_prefix() + + username, repository, number = self._parse_ref( + event["target"], event["args_split"][0]) + page = self._get_pull(username, repository, number) + + if page and page.code == 200: + self._parse_pull(page, username, repository, number) + else: + event["stderr"].write("Could not find pull request") + + def _get_info(self, target, ref): + username, repository, number = self._parse_ref(target, ref) + page = self._get_issue(username, repository, number) + if page and page.code == 200: + if "pull_request" in page.data: + pull = self._get_pull(username, repository, number) + return self._parse_pull(pull, username, repository, number) + else: + return self._parse_issue(page, username, repository, number) + else: + return None + + @utils.hook("received.command.gh", alias_of="github") + @utils.hook("received.command.github", min_args=1) + def github(self, event): + if event["target"].get_setting("github-hide-prefix", False): + event["stdout"].hide_prefix() + event["stderr"].hide_prefix() + result = self._get_info(event["target"], event["args_split"][0]) + if not result == None: + event["stdout"].write(result) + else: + event["stderr"].write("Issue/PR not found") + + def _cache_ref(self, ref): + return "auto-github-%s" % ref.lower() + def _auto_github_cooldown(self, channel, ref): + cooldown = channel.get_setting("auto-github-cooldown", None) + if not cooldown == None: + cache = self._cache_ref(ref) + if not self.bot.cache.has_item(cache): + self.bot.cache.temporary_cache(cache, cooldown) + return True + else: + return False + else: + return True + + @utils.hook("command.regex", ignore_action=False) + def url_regex(self, event): + """ + :command: github + :pattern: https?://github.com/([^/]+)/([^/]+)/(pull|issues)/(\d+) + """ + if event["target"].get_setting("auto-github", False): + event.eat() + ref = "%s/%s#%s" % (event["match"].group(1), + event["match"].group(2), event["match"].group(4)) + if self._auto_github_cooldown(event["target"], ref): + try: + result = self._get_info(event["target"], ref) + except utils.EventError: + return + if result: + if event["target"].get_setting("github-hide-prefix", False): + event["stdout"].hide_prefix() + event["stdout"].write(result) + + @utils.hook("command.regex", ignore_action=False) + def ref_regex(self, event): + """ + :command: github + :pattern: (?:\S+(?:\/\S+)?)?#\d+ + """ + if event["target"].get_setting("auto-github", False): + event.eat() + ref = event["match"].group(0) + if self._auto_github_cooldown(event["target"], ref): + try: + result = self._get_info(event["target"], + event["match"].group(0)) + except utils.EventError: + return + if result: + if event["target"].get_setting("github-hide-prefix", False): + event["stdout"].hide_prefix() + event["stdout"].write(result) diff --git a/modules/github/ghwebhook.md b/modules/github/ghwebhook.md deleted file mode 100644 index f533cc81..00000000 --- a/modules/github/ghwebhook.md +++ /dev/null @@ -1,53 +0,0 @@ -# !ghwebhook - -## List registered web hooks -`!ghwebhook list` - -## Adding a web hook - -`!ghwebhook add [name]` where `[name]` is either a full repository name (e.g. `jesopo/bitbot`) to get a specific repository or a user/organisation name (e.g. `jesopo`) to get all repositories for that user/organisation - -## Removing a web hook -Same as above but with `remove` instead of `add` - -## Modifying shown events -`!ghwebhook events [hook] [events]` where `[hook]` is the `[name]` used in the `add` command and where `[events]` is a space-separated list of either raw events (see [here](https://developer.github.com/v3/activity/events/types/)) or the following (default: "ping code pr issue repo") - -#### ping -Shows when a newly registered web hook first hits BitBot - -#### code -Shows for commits and comments on commits - -#### pr-minimal -Shows minimal pull request actions; opened, closed, reopened - -#### pr -Shows the same actions as `pr-minimal` and also: edited, assigned, unassigned, review requests, comments on review requests - -#### pr-all -Shows the same actions as `pr` and also: labeled, unlabeled, new commits - -#### issue-minimal -Shows minimal issue actions; opened, closed, reopened, deleted - -#### issue -Shows the same actions as `issue-minimal` and also: edited, assigned, unassigned, comments on issues - -#### issue-all -Shows the same actions as `issue` and also: transferred, pinned, unpinned, labeled, unlabeled, milestoned, demilestoned - -#### repo -Shows events related repositories themselves; repository/branch/tag created, repository/branch/tag deleted, release created, fork created - -#### team -Shows when users are added or removed from teams - -## List shown events -`!ghwebhook events [hook]` - -## Modify shown branches -`!ghwebhook branches [hook] [branches]` where `[hook]` is the `[name]` used in the `add` command and where `[branches]` is a space-separated list of branch names - -## List shown branches -`!ghwebhook branches [hook]` diff --git a/modules/webhooks/__init__.py b/modules/webhooks/__init__.py new file mode 100644 index 00000000..5b840470 --- /dev/null +++ b/modules/webhooks/__init__.py @@ -0,0 +1,190 @@ +from src import ModuleManager, utils +from . import github + +@utils.export("channelset", {"setting": "git-prevent-highlight", + "help": "Enable/disable preventing highlights", + "validate": utils.bool_or_none, "example": "on"}) +@utils.export("channelset", {"setting": "git-hide-organisation", + "help": "Hide/show organisation in repository names", + "validate": utils.bool_or_none, "example": "on"}) +@utils.export("channelset", {"setting": "git-hide-prefix", + "help": "Hide/show command-like prefix on git webhook outputs", + "validate": utils.bool_or_none, "example": "on"}) +class Module(ModuleManager.BaseModule): + def on_load(self): + self._github = github.GitHub() + + @utils.hook("api.post.github") + def github_webhook(self, event): + return self._webhook("github", "GitHub", self._github, + event["data"], event["headers"]) + + def _webhook(self, webhook_type, webhook_name, handler, payload_str, + headers): + payload = event["data"].decode("utf8") + if event["headers"]["Content-Type"] == FORM_ENCODED: + payload = urllib.parse.unquote(urllib.parse.parse_qs(payload)[ + "payload"][0]) + data = json.loads(payload) + + full_name, repo_username, report_name, organisation = handler.names( + data, headers) + branch = handler.branch(data, headers) + event, event_action = handler.event(data, headers) + + hooks = self.bot.database.channel_settings.find_by_setting( + "%s-hooks" % webhook_type) + + targets = [] + repo_hooked = False + + for server_id, channel_name, hooked_repos in hooks: + found_hook = None + if full_name and full_name in hooked_repos: + found_hook = hooked_repos[full_name] + elif repo_username and repo_username in hooked_repos: + found_hook = hooked_repos[repo_username] + elif organisation and organisation in hooked_repos: + found_hook = hooked_repos[organisation] + + repo_hooked = True + server = self.bot.get_server_by_id(server_id) + if server and channel_name in server.channels: + if (branch and + found_hook["branches"] and + not branch in found_hook["branches"]): + continue + + events = [] + for hooked_event in found_hook["events"]: + events.append(EVENT_CATEGORIES.get(hooked_event, + [hooked_event])) + events = list(itertools.chain(*events)) + + channel = server.channels.get(channel_name) + if (current_event in events or + (event_action and event_action in events)): + targets.append([server, channel]) + + if not targets: + if not repo_hooked: + return None + else: + return {"state": "success", "deliveries": 0} + + outputs = handler.webhook(data, headers) + + if outputs: + for server, channel in targets: + source = full_name or organisation + hide_org = channel.get_setting("git-hide-organisation", False) + if repo_name and hide_org: + source = repo_name + + for output in outputs: + output = "(%s) %s" % ( + utils.irc.color(source, COLOR_REPO), output) + + if channel.get_setting("git-prevent-highlight", False): + output = self._prevent_highlight(server, channel, + output) + + hide_prefix = channel.get_setting("git-hide-prefix", False) + self.events.on("send.stdout").call(target=channel, + module_name=webhook_name, server=server, message=output, + hide_prefix=hide_prefix) + + def _prevent_highlight(self, server, channel, s): + for user in channel.users: + if len(user.nickname) == 1: + # if we don't ignore 1-letter nicknames, the below while loop + # will fire indefininitely. + continue + + regex = re.compile(r"(.)\b(%s)(%s)" % ( + re.escape(user.nickname[0]), re.escape(user.nickname[1:])), + re.I) + s = regex.sub("\\1\\2\u200c\\3", s) + + return s + + @utils.hook("received.command.webhook", min_args=1, channel_only=True) + def github_webhook(self, event): + """ + :help: Add/remove/modify a git webhook + :require_mode: high + :require_access: git-webhook + :permission: gitoverride + :usage: list + :usage: add + :usage: remove + :usage: events [category [category ...]] + :usage: branches [branch [branch ...]] + """ + all_hooks = event["target"].get_setting("webhooks", {}) + hook_name = None + existing_hook = None + if len(event["args_split"]) > 1: + hook_name = event["args_split"][1] + for existing_hook_name in all_hooks.keys(): + if existing_hook_name.lower() == hook_name.lower(): + existing_hook = existing_hook_name + break + + success_message = None + + subcommand = event["args_split"][0].lower() + if subcommand == "list": + event["stdout"].write("Registered web hooks: %s" % + ", ".join(all_hooks.keys())) + elif subcommand == "add": + if existing_hook: + raise utils.EventError("There's already a hook for %s" % + hook_name) + + all_hooks[hook_name] = { + "events": DEFAULT_EVENT_CATEGORIES.copy(), + "branches": [], + } + success_message = "Added hook for %s" % hook_name + + elif subcommand == "remove": + if not existing_hook: + raise utils.EventError("No hook found for %s" % hook_name) + + del all_hooks[existing_hook] + success_message = "Removed hook for %s" % hook_name + + elif subcommand == "events": + if not existing_hook: + raise utils.EventError("No hook found for %s" % hook_name) + + if len(event["args_split"]) < 3: + event["stdout"].write("Events for hook %s: %s" % + (hook_name, " ".join(all_hooks[existing_hook]["events"]))) + else: + new_events = [e.lower() for e in event["args_split"][2:]] + all_hooks[existing_hook]["events"] = new_events + sucess_message = "Updated events for hook %s" % hook_name + elif subcommand == "branches": + if not existing_hook: + raise utils.EventError("No hook found for %s" % hook_name) + + if len(event["args_split"]) < 3: + branches = ",".join(all_hooks[existing_hook]["branches"]) + event["stdout"].write("Branches shown for hook %s: %s" % + (hook_name, branches)) + else: + all_hooks[existing_hook]["branches"] = event["args_split"][2:] + success_message = "Updated branches for hook %s" % hook_name + else: + event["stderr"].write("Unknown command '%s'" % + event["args_split"][0]) + + if not success_message == None: + if all_hooks: + event["target"].set_setting("webhooks", all_hooks) + else: + event["target"].del_setting("webhooks") + + event["stdout"].write(success_message) diff --git a/modules/github/__init__.py b/modules/webhooks/github.py similarity index 54% rename from modules/github/__init__.py rename to modules/webhooks/github.py index 46106a91..42587da1 100644 --- a/modules/github/__init__.py +++ b/modules/webhooks/github.py @@ -1,19 +1,5 @@ -#--depends-on channel_access -#--depends-on check_mode -#--depends-on commands -#--depends-on config -#--depends-on permissions -#--depends-on rest_api - -import datetime, itertools, json, math, re, urllib.parse -from src import EventManager, ModuleManager, utils - -COLOR_BRANCH = utils.consts.ORANGE -COLOR_REPO = utils.consts.GREY -COLOR_POSITIVE = utils.consts.GREEN -COLOR_NEUTRAL = utils.consts.LIGHTGREY -COLOR_NEGATIVE = utils.consts.RED -COLOR_ID = utils.consts.PINK +#--depends-on-github +from src import ModuleManager, utils FORM_ENCODED = "application/x-www-form-urlencoded" @@ -21,9 +7,6 @@ COMMIT_URL = "https://github.com/%s/commit/%s" COMMIT_RANGE_URL = "https://github.com/%s/compare/%s...%s" CREATE_URL = "https://github.com/%s/tree/%s" -API_ISSUE_URL = "https://api.github.com/repos/%s/%s/issues/%s" -API_PULL_URL = "https://api.github.com/repos/%s/%s/pulls/%s" - DEFAULT_EVENT_CATEGORIES = [ "ping", "code", "pr", "issue", "repo" ] @@ -98,289 +81,34 @@ CHECK_RUN_CONCLUSION = { } CHECK_RUN_FAILURES = ["failure", "cancelled", "timed_out", "action_required"] -@utils.export("channelset", {"setting": "github-hide-prefix", - "help": "Hide/show command-like prefix on Github hook outputs", - "validate": utils.bool_or_none, "example": "on"}) -@utils.export("channelset", {"setting": "github-hide-organisation", - "help": "Hide/show organisation in repository names", - "validate": utils.bool_or_none, "example": "on"}) -@utils.export("channelset", {"setting": "github-default-repo", - "help": "Set the default github repo for the current channel", - "example": "jesopo/bitbot"}) -@utils.export("channelset", {"setting": "github-prevent-highlight", - "help": "Enable/disable preventing highlights", - "validate": utils.bool_or_none, "example": "on"}) -@utils.export("channelset", {"setting": "auto-github", - "help": "Enable/disable automatically getting github issue/PR info", - "validate": utils.bool_or_none, "example": "on"}) -@utils.export("channelset", {"setting": "auto-github-cooldown", - "help": "Set amount of seconds between auto-github duplicates", - "validate": utils.int_or_none, "example": "300"}) -class Module(ModuleManager.BaseModule): - def _parse_ref(self, channel, ref): - repo, _, number = ref.rpartition("#") - org, _, repo = repo.partition("/") +class GitHub(object): + def names(self, data, headers): + full_name = None + repo_username = None + repo_name = None + if "repository" in data: + full_name = data["repository"]["full_name"] + repo_username, repo_name = full_name.split("/", 1) - default_repo = channel.get_setting("github-default-repo", "") - default_org, _, default_repo = default_repo.partition("/") + organisation = None + if "organization" in data: + organisation = data["organization"]["login"] + return full_name, repo_username, repo_name, oraganisation - if org and not repo: - repo = org or default_repo - org = default_org - else: - org = org or default_org - repo = repo or default_repo + def branch(self, data, headers): + if "ref" in data: + return data["ref"].rpartition("/")[2] + return None - if not org or not repo or not number: - raise utils.EventError("Please provide username/repo#number") - if not number.isdigit(): - raise utils.EventError("Issue number must be a number") - return org, repo, number + def event(self, data, headers): + event = headers["X-GitHub-Event"] + event_action = None + if "action" in data: + event_action = "%s/%s" % (event, data["action"]) + return event, event_action - def _parse_issue(self, page, username, repository, number): - repo = utils.irc.color("%s/%s" % (username, repository), COLOR_REPO) - number = utils.irc.color("#%s" % number, COLOR_ID) - labels = [label["name"] for label in page.data["labels"]] - labels_str = "" - if labels: - labels_str = "[%s] " % ", ".join(labels) - - url = self._short_url(page.data["html_url"]) - - state = page.data["state"] - if state == "open": - state = utils.irc.color("open", COLOR_NEUTRAL) - elif state == "closed": - state = utils.irc.color("closed", COLOR_NEGATIVE) - - return "(%s issue%s, %s) %s %s%s" % ( - repo, number, state, page.data["title"], labels_str, url) - def _get_issue(self, username, repository, number): - return utils.http.request( - API_ISSUE_URL % (username, repository, number), - json=True) - - @utils.hook("received.command.ghissue", min_args=1) - def github_issue(self, event): - if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stderr"].hide_prefix() - - username, repository, number = self._parse_ref( - event["target"], event["args_split"][0]) - - page = self._get_issue(username, repository, number) - if page and page.code == 200: - self._parse_issue(page, username, repository, number) - else: - event["stderr"].write("Could not find issue") - - def _parse_pull(self, page, username, repository, number): - repo = utils.irc.color("%s/%s" % (username, repository), COLOR_REPO) - number = utils.irc.color("#%s" % number, COLOR_ID) - branch_from = page.data["head"]["label"] - branch_to = page.data["base"]["label"] - added = self._added(page.data["additions"]) - removed = self._removed(page.data["deletions"]) - url = self._short_url(page.data["html_url"]) - - state = page.data["state"] - if page.data["merged"]: - state = utils.irc.color("merged", COLOR_POSITIVE) - elif state == "open": - state = utils.irc.color("open", COLOR_NEUTRAL) - elif state == "closed": - state = utils.irc.color("closed", COLOR_NEGATIVE) - - return "(%s PR%s, %s) %s → %s [%s/%s] %s %s" % ( - repo, number, state, branch_from, branch_to, added, removed, - page.data["title"], url) - def _get_pull(self, username, repository, number): - return utils.http.request( - API_PULL_URL % (username, repository, number), - json=True) - @utils.hook("received.command.ghpull", min_args=1) - def github_pull(self, event): - if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stderr"].hide_prefix() - - username, repository, number = self._parse_ref( - event["target"], event["args_split"][0]) - page = self._get_pull(username, repository, number) - - if page and page.code == 200: - self._parse_pull(page, username, repository, number) - else: - event["stderr"].write("Could not find pull request") - - def _get_info(self, target, ref): - username, repository, number = self._parse_ref(target, ref) - page = self._get_issue(username, repository, number) - if page and page.code == 200: - if "pull_request" in page.data: - pull = self._get_pull(username, repository, number) - return self._parse_pull(pull, username, repository, number) - else: - return self._parse_issue(page, username, repository, number) - else: - return None - - @utils.hook("received.command.gh", alias_of="github") - @utils.hook("received.command.github", min_args=1) - def github(self, event): - if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stderr"].hide_prefix() - result = self._get_info(event["target"], event["args_split"][0]) - if not result == None: - event["stdout"].write(result) - else: - event["stderr"].write("Issue/PR not found") - - def _cache_ref(self, ref): - return "auto-github-%s" % ref.lower() - def _auto_github_cooldown(self, channel, ref): - cooldown = channel.get_setting("auto-github-cooldown", None) - if not cooldown == None: - cache = self._cache_ref(ref) - if not self.bot.cache.has_item(cache): - self.bot.cache.temporary_cache(cache, cooldown) - return True - else: - return False - else: - return True - - @utils.hook("command.regex", ignore_action=False) - def url_regex(self, event): - """ - :command: github - :pattern: https?://github.com/([^/]+)/([^/]+)/(pull|issues)/(\d+) - """ - if event["target"].get_setting("auto-github", False): - event.eat() - ref = "%s/%s#%s" % (event["match"].group(1), - event["match"].group(2), event["match"].group(4)) - if self._auto_github_cooldown(event["target"], ref): - try: - result = self._get_info(event["target"], ref) - except utils.EventError: - return - if result: - if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stdout"].write(result) - - @utils.hook("command.regex", ignore_action=False) - def ref_regex(self, event): - """ - :command: github - :pattern: (?:\S+(?:\/\S+)?)?#\d+ - """ - if event["target"].get_setting("auto-github", False): - event.eat() - ref = event["match"].group(0) - if self._auto_github_cooldown(event["target"], ref): - try: - result = self._get_info(event["target"], - event["match"].group(0)) - except utils.EventError: - return - if result: - if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stdout"].write(result) - - @utils.hook("received.command.ghwebhook", min_args=1, channel_only=True) - def github_webhook(self, event): - """ - :help: Add/remove/modify a github webhook - :require_mode: high - :require_access: github-webhook - :permission: githuboverride - :usage: list - :usage: add - :usage: remove - :usage: events [category [category ...]] - :usage: branches [branch [branch ...]] - """ - all_hooks = event["target"].get_setting("github-hooks", {}) - hook_name = None - existing_hook = None - if len(event["args_split"]) > 1: - hook_name = event["args_split"][1] - for existing_hook_name in all_hooks.keys(): - if existing_hook_name.lower() == hook_name.lower(): - existing_hook = existing_hook_name - break - - subcommand = event["args_split"][0].lower() - if subcommand == "list": - event["stdout"].write("Registered web hooks: %s" % - ", ".join(all_hooks.keys())) - elif subcommand == "add": - if existing_hook: - event["stderr"].write("There's already a hook for %s" % - hook_name) - return - - all_hooks[hook_name] = { - "events": DEFAULT_EVENT_CATEGORIES.copy(), - "branches": [] - } - event["target"].set_setting("github-hooks", all_hooks) - event["stdout"].write("Added hook for %s" % hook_name) - elif subcommand == "remove": - if not existing_hook: - event["stderr"].write("No hook found for %s" % hook_name) - return - del all_hooks[existing_hook] - if all_hooks: - event["target"].set_setting("github-hooks", all_hooks) - else: - event["target"].del_setting("github-hooks") - event["stdout"].write("Removed hook for %s" % hook_name) - elif subcommand == "events": - if not existing_hook: - event["stderr"].write("No hook found for %s" % hook_name) - return - - if len(event["args_split"]) < 3: - event["stdout"].write("Events for hook %s: %s" % - (hook_name, " ".join(all_hooks[existing_hook]["events"]))) - else: - new_events = [e.lower() for e in event["args_split"][2:]] - all_hooks[existing_hook]["events"] = new_events - event["target"].set_setting("github-hooks", all_hooks) - event["stdout"].write("Updated events for hook %s" % hook_name) - elif subcommand == "branches": - if not existing_hook: - event["stderr"].write("No hook found for %s" % hook_name) - return - - if len(event["args_split"]) < 3: - branches = ",".join(all_hooks[existing_hook]["branches"]) - event["stdout"].write("Branches shown for hook %s: %s" % - (hook_name, branches)) - else: - all_hooks[existing_hook]["branches"] = event["args_split"][2:] - event["target"].set_setting("github-hooks", all_hooks) - event["stdout"].write("Updated shown branches for hook %s" % - hook_name) - else: - event["stderr"].write("Unknown command '%s'" % - event["args_split"][0]) - - @utils.hook("api.post.github") - def webhook(self, event): - payload = event["data"].decode("utf8") - if event["headers"]["Content-Type"] == FORM_ENCODED: - payload = urllib.parse.unquote(urllib.parse.parse_qs(payload)[ - "payload"][0]) - data = json.loads(payload) - - github_event = event["headers"]["X-GitHub-Event"] + def webhook(self, data, headers): + github_event = headers["X-GitHub-Event"] full_name = None repo_username = None @@ -472,30 +200,7 @@ class Module(ModuleManager.BaseModule): outputs = self.membership(organisation, data) elif github_event == "watch": outputs = self.watch(data) - - - if outputs: - for server, channel in targets: - source = full_name or organisation - hide_org = channel.get_setting( - "github-hide-organisation", False) - if repo_name and hide_org: - source = repo_name - - for output in outputs: - output = "(%s) %s" % ( - utils.irc.color(source, COLOR_REPO), output) - - if channel.get_setting("github-prevent-highlight", False): - output = self._prevent_highlight(server, channel, - output) - - self.events.on("send.stdout").call(target=channel, - module_name="Github", server=server, message=output, - hide_prefix=channel.get_setting( - "github-hide-prefix", False)) - - return {"state": "success", "deliveries": len(targets)} + return outputs def _prevent_highlight(self, server, channel, s): for user in channel.users: @@ -576,7 +281,6 @@ class Module(ModuleManager.BaseModule): return outputs - def commit_comment(self, full_name, data): action = data["action"] commit = self._short_hash(data["comment"]["commit_id"])