From 03aad42d43471714aba380159b2e68ef1ac2d4a7 Mon Sep 17 00:00:00 2001 From: kongr45gpen Date: Sun, 7 May 2017 14:43:14 +0300 Subject: Add support for Netlify --- __init__.py | 2 ++ config.py | 4 +++ docs/configuration.rst | 14 +++++++++ local/handler/GithubHandler.py | 3 ++ local/handler/NetlifyHandler.py | 13 +++++++++ local/theme/CompactTheme.py | 13 +++++++++ local/theme/DefaultTheme.py | 13 +++++++++ local/utility.py | 65 ++++++++++++++++++++++++++++------------- 8 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 local/handler/NetlifyHandler.py diff --git a/__init__.py b/__init__.py index e035536..87b80ca 100644 --- a/__init__.py +++ b/__init__.py @@ -38,6 +38,7 @@ import local.handler.IssueHandler import local.handler.StatusHandler import local.handler.TravisHandler import local.handler.MessageHandler +import local.handler.NetlifyHandler import local.handler.ReleaseHandler import local.handler.UnknownHandler import local.handler.AppVeyorHandler @@ -56,6 +57,7 @@ reload(local.handler.IssueHandler) reload(local.handler.StatusHandler) reload(local.handler.TravisHandler) reload(local.handler.MessageHandler) +reload(local.handler.NetlifyHandler) reload(local.handler.ReleaseHandler) reload(local.handler.UnknownHandler) reload(local.handler.AppVeyorHandler) diff --git a/config.py b/config.py index 9b6eef6..85c3a02 100644 --- a/config.py +++ b/config.py @@ -55,6 +55,10 @@ conf.registerChannelValue(Github, 'showSuccessfulBuildMessages', registry.String('change', """Whether to show successful build messages - can be never, change or always""")) +conf.registerChannelValue(Github, 'showSuccessfulDeployMessages', + registry.String('always', + """Whether to show successful deployment messages - can be never, change or always""")) + conf.registerGlobalValue(Github, 'port', registry.Integer(8093, """The port where Github will send HTTP requests""")) diff --git a/docs/configuration.rst b/docs/configuration.rst index fc9993b..35f146b 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -111,6 +111,8 @@ Available configuration values :Default value: `False` :Type: Boolean +.. _showSuccessfulBuildMessages-option: + ``showSuccessfulBuildMessages`` Whether to show build messages for non-failing builds on CI services, such as Travis and AppVeyor. Setting to ``never`` will not show any message when @@ -122,6 +124,18 @@ Available configuration values :Possible values: ``never``, ``change``, ``always`` :Type: Enum +``showSuccessfulDeployMessages`` + Whether to show build messages for non-failing builds on deployment services, + such as Netlify. Works exactly like :ref:`showSuccessfulBuildMessages `: + Setting to ``never`` will not show any message when a deployment is + successful, setting to ``always`` will show all success messages, and setting + this to ``change`` will only notify about successful deployments, when the + previous one was broken (i.e. whenever the build is fixed). + + :Default value: `always` + :Possible values: ``never``, ``change``, ``always`` + :Type: Enum + ``brackets`` A set of characters to use instead of parentheses to enclose URLs. This may be useful if your IRC client considers the default parentheses part of the diff --git a/local/handler/GithubHandler.py b/local/handler/GithubHandler.py index 362917f..0c3a0dc 100644 --- a/local/handler/GithubHandler.py +++ b/local/handler/GithubHandler.py @@ -34,6 +34,7 @@ import IssueHandler import StatusHandler import TravisHandler import MessageHandler +import NetlifyHandler import ReleaseHandler import UnknownHandler import AppVeyorHandler @@ -166,6 +167,8 @@ class GithubHandler(BaseHTTPServer.BaseHTTPRequestHandler): TravisHandler.handle(data, theme) elif 'pages' in data: WikiHandler.handle(data, theme) + elif 'screenshot_url' in data: + NetlifyHandler.handle(data, theme) elif 'state' in data: StatusHandler.handle(data, theme) elif 'commits' in data: diff --git a/local/handler/NetlifyHandler.py b/local/handler/NetlifyHandler.py new file mode 100644 index 0000000..daa802f --- /dev/null +++ b/local/handler/NetlifyHandler.py @@ -0,0 +1,13 @@ +from ..utility import * + +def handle(data, theme): + if isStatusVisible(data['site_id'], data['state'].lower(), 'showSuccessfulDeployMessages'): + theme.deployment( + branch = data['branch'], + repo = data['name'], + status = data['state'], + commitId = data['commit_ref'], + commitMessage = data['title'], + commitAuthor = data['commit_url'].split('/')[3], #TODO: Make this show the proper author + url = getShortURL(data['url']) + ) diff --git a/local/theme/CompactTheme.py b/local/theme/CompactTheme.py index a1090c9..4b1c86b 100644 --- a/local/theme/CompactTheme.py +++ b/local/theme/CompactTheme.py @@ -24,3 +24,16 @@ class CompactTheme(DefaultTheme): buildUrl )) )) + + def deployment(self, branch, repo, status, commitId, commitMessage, commitAuthor, url): + self.msgs.append( "%s @ %s: %s %s: deploy %s %s" % ( + ircutils.bold(ircutils.mircColor(branch, "blue")), + ircutils.bold(repo), + ircutils.mircColor(commitAuthor, "green"), + ircutils.mircColor(commitId[0:6], "dark grey"), + colorAction(status.lower()), + self.enclose("%s - %s" % ( + ircutils.mircColor(maxLen(commitMessage, 50), "dark gray"), + url + )) + )) diff --git a/local/theme/DefaultTheme.py b/local/theme/DefaultTheme.py index e6269d7..096311c 100644 --- a/local/theme/DefaultTheme.py +++ b/local/theme/DefaultTheme.py @@ -161,6 +161,19 @@ class DefaultTheme(Theme): )) )) + def deployment(self, branch, repo, status, commitId, commitMessage, commitAuthor, url): + self.msgs.append( "%s @ %s: Deployment status: %s * %s by %s %s") % ( + ircutils.bold(ircutils.mircColor(branch, "blue")), + ircutils.bold(repo), + colorAction(status.lower()), + ircutils.bold(commitId[0:6]), + ircutils.mircColor(commitAuthor, "green"), + self.enclose("%s - %s" % ( + ircutils.mircColor(maxLen(commitMessage, 50), "dark gray"), + url + )) + ) + def status(self, status, description, url): self.msgs.append( "%s: %s - %s %s" % ( self.repo(), diff --git a/local/utility.py b/local/utility.py index 0f3a0ca..0ebfbe8 100644 --- a/local/utility.py +++ b/local/utility.py @@ -13,6 +13,7 @@ import supybot.registry as registry import globals + def registryValue(plugin, name, channel=None, value=True): group = conf.supybot.plugins.get(plugin) names = registry.split(name) @@ -32,6 +33,7 @@ def registryValue(plugin, name, channel=None, value=True): else: return group + def configValue(name, channel=None, repo=None, type=None, module=None): if globals.configOverrides and name.lower() in globals.configOverrides: return globals.configOverrides[name.lower()] @@ -41,6 +43,7 @@ def configValue(name, channel=None, repo=None, type=None, module=None): return registryValue("Github", name, channel) + def addConfigOverride(name, value): if value.lower() == 'false': value = False; @@ -49,33 +52,40 @@ def addConfigOverride(name, value): name = name.strip().lower() - if name in [ 'passcode', 'disallowConfigOverride', 'allowArbitraryMessages' ]: + if name in ['passcode', 'disallowConfigOverride', 'allowArbitraryMessages']: return globals.configOverrides[name] = value + def resetConfigOverrides(): globals.configOverrides = {} + def plural(number, s, p): if number != 1: return p return s + def parseBrackets(bracketConfig): if "M" in bracketConfig: return tuple(bracketConfig.split('M', 1)) else: - mid = len(bracketConfig)/2 + mid = len(bracketConfig) / 2 if len(bracketConfig) % 2 == 0: return (bracketConfig[:mid], bracketConfig[mid:]) else: # Do not include the middle character - return (bracketConfig[:mid], bracketConfig[(mid+1):]) + return (bracketConfig[:mid], bracketConfig[(mid + 1):]) + def maxLen(msg, maxn=400, splitLines=True): """Cut down a string if its longer than `maxn` chars""" + if msg is None: + return None + if splitLines is True: lines = msg.splitlines() line = lines[0] if lines else "" @@ -83,31 +93,33 @@ def maxLen(msg, maxn=400, splitLines=True): line = msg if len(line) > maxn: - ret = "%s..." % (line[0:(maxn-3)]) + ret = "%s..." % (line[0:(maxn - 3)]) elif splitLines is True and len(lines) > 1: ret = "%s..." % (line) else: ret = msg return ret + def colorAction(action): """Give an action string (e.g. created, edited) and get a nice IRC colouring""" - if action in [ "created", "opened", "tagged", "success", "passed", "fixed", - "published", "completed" ]: + if action in ["created", "opened", "tagged", "success", "passed", "fixed", + "published", "completed", "ready"]: return ircutils.bold(ircutils.mircColor(action, "green")) - if action in [ "deleted" ,"closed", "re-tagged", "deleted tag", - "failed", "errored", "failure", "still failing", - "broken" ]: + if action in ["deleted", "closed", "re-tagged", "deleted tag", + "failed", "errored", "failure", "still failing", + "broken", "error"]: return ircutils.bold(ircutils.mircColor(action, "red")) - if action in [ "assigned", "merged" ]: + if action in ["assigned", "merged"]: return ircutils.bold(ircutils.mircColor(action, "light blue")) - if action in [ "reopened", "pending" ]: + if action in ["reopened", "pending"]: return ircutils.bold(ircutils.mircColor(action, "blue")) - if action[0:5] in [ "force" ]: + if action[0:5] in ["force"]: return ircutils.bold(ircutils.mircColor(action, "brown")) return action + def getShortURL(longurl): """ Returns a short URL generated by git.io""" if longurl is None: @@ -129,14 +141,18 @@ def getShortURL(longurl): log.warning("URL shortening failed with: %s" % (e.message,)) url = longurl return ircutils.mircColor(url, "purple") + + getShortURL.github = re.compile('^([a-z]*\:\/\/)?([^\/]+.)?github.com') + def saveMessages(msgs): """ Saves the last messages so that the plugin can be easily tested """ if not world.testing: return globals.messageList = msgs + def isYes(string): """Returns True if the string represents a yes, False, if it represents no, and another string if it represents something else""" @@ -146,15 +162,16 @@ def isYes(string): return True if value in ['no', 'never', 'off', 'false', 'null']: return False - if value in ['changed', 'change', 'on_change', 'diff']: + if value in ['changed', 'change', 'onchange', 'on_change', 'diff']: return 'change' -def isStatusVisible(repo, status): + +def isStatusVisible(repo, status, option='showSuccessfulBuildMessages'): """Returns whether the build status message should be shown""" - config = isYes(configValue('showSuccessfulBuildMessages')) + config = isYes(configValue(option)) changed = False - if status != "passed": + if status != "passed" and status != "ready": changed = True elif type(config) is bool: changed = config @@ -165,16 +182,19 @@ def isStatusVisible(repo, status): globals.travisStatuses[repo] = status return changed + def randomString(length): """Returns a securely generated random string of a specific length""" return ''.join(random.SystemRandom().choice( string.ascii_uppercase + string.ascii_lowercase + string.digits ) for _ in range(length)) + def secureCompare(s1, s2): """Securely compare two strings""" return sum(i != j for i, j in zip(s1, s2)) is 0 + def getChannelSecret(channel): """Returns a secret for a channel, or None if that channel has no secret""" if globals.secretDB is None: @@ -185,6 +205,7 @@ def getChannelSecret(channel): except KeyError: return None + def showIssueName(repoId, issueId): """Returns whether we should show the issue name for a repo issue""" now = datetime.now() @@ -197,7 +218,7 @@ def showIssueName(repoId, issueId): globals.shownIssues[repoId] = {} # Clean up old issues - remove = [k for k in globals.shownIssues[repoId] if now - globals.shownIssues[repoId][k] > timedelta(seconds = 15)] + remove = [k for k in globals.shownIssues[repoId] if now - globals.shownIssues[repoId][k] > timedelta(seconds=15)] for k in remove: del globals.shownIssues[repoId][k] exists = issueId in globals.shownIssues[repoId] @@ -207,6 +228,7 @@ def showIssueName(repoId, issueId): return not exists + def hexToMirc(hash): colors = { 'white': (255, 255, 255), @@ -229,21 +251,24 @@ def hexToMirc(hash): rgb = _hex_to_rgb(hash) - return min(colors, key = lambda x:_colourDistance(colors[x], rgb)) + return min(colors, key=lambda x: _colourDistance(colors[x], rgb)) + def _hex_to_rgb(value): value = value.lstrip('#') lv = len(value) return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) + def _colourDistance(a, b): # Source: http://www.compuphase.com/cmetric.htm - rmean = (a[0] + b[0]) /2 + rmean = (a[0] + b[0]) / 2 red = a[0] - b[0] green = a[1] - b[1] blue = a[2] - b[2] - return math.sqrt((((512+rmean)*red*red)>>8) + 4*green*green + (((767-rmean)*blue*blue)>>8)) + return math.sqrt((((512 + rmean) * red * red) >> 8) + 4 * green * green + (((767 - rmean) * blue * blue) >> 8)) + # Possible colours: # white, black, (light/dark) blue, (light) green, red, brown, purple, # orange, yellow, teal, pink, light/dark gray/grey -- cgit v1.2.3