Skip to content

Commit

Permalink
Matrix capability integration
Browse files Browse the repository at this point in the history
To support Matrix as an outlet for ghi we:
-edited ghi/configuration.py to read, check and use the (optional) Matrix-settings from the .yml
-created ghi/ghimatrix.py to facilitate creating credential-files, logging onto a Matrix server and sending messages to one or more rooms on it by using the matrix-nio module
-changed some naming and matrix-specific things in most of the files
-changed the README.md en .ghy.yml.example to include the new Matrix-support
-created ghi/util.py to facilitate in a workaround for a Matrix-issue (matrix-org/matrix-appservice-irc#1562)
-added relevant exception-handlers

No changes to the config-file are necessary when using IRC.

It works quite well with server.py, haven't tested it with AWS though, but should work as well.

Closes gkrizek#27

Co-authored-by: W. J. van der Laan <laanwj@protonmail.com>
  • Loading branch information
Kvaciral and laanwj committed May 6, 2022
1 parent 85fa507 commit e20e96a
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 76 deletions.
112 changes: 78 additions & 34 deletions README.md

Large diffs are not rendered by default.

170 changes: 165 additions & 5 deletions ghi/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import os
import yaml

SUPPORTED_OUTLETS = ["irc", "mastodon"]
SUPPORTED_OUTLETS = ["irc", "mastodon", "matrix"]
MATRIX_DEVICE_ID = "Ghi-Matrix-Bot"

class Pool(object):


def __init__(self, name, outlets, repos, shorten, ircHost, ircPort, ircSsl, ircNick, ircPassword, ircChannels,\
mastInstance, mastUser, mastPassword, mastSecPath, mastAppName, mastMergeFilter):
mastInstance, mastUser, mastPassword, mastSecPath, mastAppName, mastMergeFilter,\
matrixUser, matrixPassword, matrixServer, matrixRooms, matrixSecPath, matrixDevId):
self.name = name
self.outlets = outlets
self.repos = repos
Expand All @@ -26,10 +28,16 @@ def __init__(self, name, outlets, repos, shorten, ircHost, ircPort, ircSsl, ircN
self.mastSecPath = mastSecPath
self.mastAppName = mastAppName
self.mastMergeFilter = mastMergeFilter
self.matrixUser = matrixUser
self.matrixPassword = matrixPassword
self.matrixServer = matrixServer
self.matrixRooms = matrixRooms
self.matrixSecPath = matrixSecPath
self.matrixDevId = matrixDevId


def containsRepo(self, repo):
for configRepo in self.repos:
for configRepo in self.repos:
if repo == configRepo["name"]:
return True
return False
Expand All @@ -44,7 +52,8 @@ class GlobalConfig(object):


def __init__(self, ircHost, ircPort, ircSsl, ircNick, ircPassword, mastInstance, mastUser,\
mastPassword, mastSecPath, mastAppName, mastMergeFilter, shorten, verify, outlets):
mastPassword, mastSecPath, mastAppName, mastMergeFilter, shorten, verify, outlets,\
matrixUser, matrixPassword, matrixServer, matrixSecPath, matrixDevId):
self.ircHost = ircHost
self.ircPort = ircPort
self.ircSsl = ircSsl
Expand All @@ -59,6 +68,11 @@ def __init__(self, ircHost, ircPort, ircSsl, ircNick, ircPassword, mastInstance,
self.shorten = shorten
self.verify = verify
self.outlets = outlets
self.matrixUser = matrixUser
self.matrixPassword = matrixPassword
self.matrixServer = matrixServer
self.matrixSecPath = matrixSecPath
self.matrixDevId = matrixDevId


def getConfiguration():
Expand Down Expand Up @@ -242,6 +256,48 @@ def getConfiguration():
globalMastAppName = None
globalMastMergeFilter = None

if "matrix" in globalConfig:
if "user" in globalConfig["matrix"]:
globalMatrixUser = globalConfig["matrix"]["user"]
if type(globalMatrixUser) is not str:
raise TypeError("'user' is not a string")
else:
globalMatrixUser = None

if "password" in globalConfig["matrix"]:
globalMatrixPassword = globalConfig["matrix"]["password"]
if type(globalMatrixPassword) is not str:
raise TypeError("'password' is not a string")
else:
globalMatrixPassword = None

if "homeserver" in globalConfig["matrix"]:
globalMatrixServer = globalConfig["matrix"]["homeserver"]
if type(globalMatrixServer) is not str:
raise TypeError("'homeserver' is not a string")
else:
globalMatrixServer = None

if "secretspath" in globalConfig["matrix"]:
globalMatrixSecPath = globalConfig["matrix"]["secretspath"]
if type(globalMatrixSecPath) is not str:
raise TypeError("'secretspath' is not a string")
else:
globalMatrixSecPath = None

if "device_id" in globalConfig["matrix"]:
globalMatrixDevId = globalConfig["matrix"]["device_id"]
if type(globalMatrixDevId) is not str:
raise TypeError("'device_id' is not a string")
else:
globalMatrixDevId = MATRIX_DEVICE_ID
else:
globalMatrixUser = None
globalMatrixPassword = None
globalMatrixServer = None
globalMatrixSecPath = None
globalMatrixDevId = None

if "github" in globalConfig:
if "shorten_url" in globalConfig["github"]:
globalShorten = globalConfig["github"]["shorten_url"]
Expand Down Expand Up @@ -292,6 +348,11 @@ def getConfiguration():
mastSecPath = globalMastSecPath,
mastAppName = globalMastAppName,
mastMergeFilter = globalMastMergeFilter,
matrixUser = globalMatrixUser,
matrixPassword = globalMatrixPassword,
matrixServer = globalMatrixServer,
matrixSecPath = globalMatrixSecPath,
matrixDevId = globalMatrixDevId,
shorten = globalShorten,
verify = globalVerify,
outlets = globalGeneratedOutlets
Expand Down Expand Up @@ -575,6 +636,91 @@ def getConfiguration():
else:
mastMergeFilter = True

if "matrix" in generatedOutlets and "matrix" in pool:
if "user" in pool["matrix"]:
matrixUser = pool["matrix"]["user"]
elif globalSettings.matrixUser:
matrixUser = globalSettings.matrixUser
else:
raise KeyError("user")
if type(matrixUser) is not str:
raise TypeError("'user' is not a string")

if "password" in pool["matrix"]:
matrixPassword = pool["matrix"]["password"]
elif globalSettings.matrixPassword:
matrixPassword = globalSettings.matrixPassword
else:
raise KeyError("password")
if type(matrixPassword) is not str:
raise TypeError("'password' is not a string")

if "homeserver" in pool["matrix"]:
matrixServer = pool["matrix"]["homeserver"]
elif globalSettings.matrixServer:
matrixServer = globalSettings.matrixServer
else:
raise KeyError("homeserver")
if type(matrixServer) is not str:
raise TypeError("'homeserver' is not a string")

if "secretspath" in pool["matrix"]:
matrixSecPath = pool["matrix"]["secretspath"]
elif globalSettings.matrixSecPath:
matrixSecPath = globalSettings.matrixSecPath
else:
raise KeyError("secretspath")
if type(matrixSecPath) is not str:
raise TypeError("'secretspath' is not a string")

if "device_id" in pool["matrix"]:
matrixDevId = pool["matrix"]["device_id"]
elif globalSettings.matrixDevId:
matrixDevId = globalSettings.matrixDevId
else:
matrixDevId = MATRIX_DEVICE_ID
if type(matrixDevId) is not str:
raise TypeError("'device_id' is not a string")

if "rooms" in pool["matrix"]:
matrixRooms = pool["matrix"]["rooms"]
else:
raise KeyError("rooms")
if type(matrixRooms) is not list:
raise TypeError("'rooms' is not a list")
if len(matrixRooms) < 1:
raise TypeError("'rooms' must contain at least 1 item")

generatedMatrixRooms = list()
for room in matrixRooms:
generatedMatrixRooms.append(room)#"+room)

elif "matrix" in generatedOutlets and "matrix" in globalConfig:
if globalSettings.matrixUser:
matrixUser = globalSettings.matrixUser
else:
raise KeyError("user")

if globalSettings.matrixPassword:
matrixPassword = globalSettings.matrixPassword
else:
raise KeyError("password")

if globalSettings.matrixServer:
matrixServer = globalSettings.matrixServer
else:
raise KeyError("homeserver")

if globalSettings.matrixSecPath:
matrixSecPath = globalSettings.matrixSecPath
else:
raise KeyError("secretspath")

if globalSettings.matrixDevId:
matrixDevId = globalSettings.matrixDevId
else:
matrixDevId = MATRIX_DEVICE_ID

except (KeyError, TypeError) as e:
errorMessage = "Missing or invalid parameter in configuration file: %s" % e
logging.error(errorMessage)
Expand Down Expand Up @@ -602,6 +748,14 @@ def getConfiguration():
mastAppName = None
mastMergeFilter = None

if "matrix" not in generatedOutlets:
matrixUser = None
matrixPassword = None
matrixServer = None
generatedMatrixRooms = None
matrixSecPath = None
matrixDevId = None

pools.append(
Pool(
name=name,
Expand All @@ -619,7 +773,13 @@ def getConfiguration():
mastPassword=mastPassword,
mastSecPath=mastSecPath,
mastAppName=mastAppName,
mastMergeFilter=mastMergeFilter
mastMergeFilter=mastMergeFilter,
matrixUser=matrixUser,
matrixPassword=matrixPassword,
matrixServer=matrixServer,
matrixRooms=generatedMatrixRooms,
matrixSecPath=matrixSecPath,
matrixDevId=matrixDevId
)
)

Expand Down
20 changes: 18 additions & 2 deletions ghi/events/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../")
import github
from irc import Colors
from util import matrix_html


def PullRequest(payload, shorten):
Expand Down Expand Up @@ -53,10 +54,25 @@ def PullRequest(payload, shorten):
url = url
)

matrixMessage = (
'[<font color="fuchsia">{repo}</font>] <font color="gray">{user}</font> <b>{action}</b> pull request <b>#{number}</b>: {title} '
'(<font color="purple">{baseBranch}</font>...<font color="purple">{headBranch}</font>) <font color="navy"><u>{url}</u></font>'
).format(
repo = matrix_html(payload["pull_request"]["base"]["repo"]["name"]),
user = matrix_html(payload["sender"]["login"]),
action = matrix_html(action),
number = payload["number"],
title = matrix_html(payload["pull_request"]["title"]),
baseBranch = matrix_html(payload["pull_request"]["base"]["ref"]),
headBranch = matrix_html(payload["pull_request"]["head"]["ref"]),
url = url
)

return {
"statusCode": 200,
"ircMessages": [ircMessage],
"mastMessages": [mastMessage]
"mastMessages": [mastMessage],
"matrixMessages": [matrixMessage]
}

else:
Expand All @@ -68,4 +84,4 @@ def PullRequest(payload, shorten):
"success": True,
"message": message
})
}
}
65 changes: 53 additions & 12 deletions ghi/events/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "../")
import github
from irc import Colors
from util import matrix_html


def Push(payload, poolRepos, shorten):
Expand Down Expand Up @@ -54,22 +55,35 @@ def Push(payload, poolRepos, shorten):
compareUrl = url
)

matrixMessage = (
'[<font color="fuchsia">{repo}</font>] <font color="gray">{user}</font> {action} '
'tag <font color="purple">{tag}</font>: <font color="navy"><u>{compareUrl}</u></font>'
).format(
repo = matrix_html(payload["repository"]["name"]),
user = matrix_html(payload["pusher"]["name"]),
action = matrix_html(action),
tag = matrix_html(ref.split("/", maxsplit=2)[2]),
compareUrl = url
)

return {
"statusCode": 200,
"ircMessages": [ircMessage],
"mastMessages": [mastMessage]
"mastMessages": [mastMessage],
"matrixMessages": [matrixMessage]
}

else:
# Commits were pushed
ircMessages = []
mastMessages = []
commits = payload["commits"]
repo = payload["repository"]["name"]
fullName = payload["repository"]["full_name"]
user = payload["pusher"]["name"]
length = len(commits)
branch = ref.split("/", maxsplit=2)[2]
ircMessages = []
mastMessages = []
matrixMessages = []
commits = payload["commits"]
repo = payload["repository"]["name"]
fullName = payload["repository"]["full_name"]
user = payload["pusher"]["name"]
length = len(commits)
branch = ref.split("/", maxsplit=2)[2]

# Check if the pool has allowed branches set.
# If they do, make sure that this branch is included
Expand Down Expand Up @@ -143,13 +157,28 @@ def Push(payload, poolRepos, shorten):
)
)

matrixMessages.append(
'[<font color="fuchsia">{repo}</font>] <font color="gray">{user}</font> {action} '
'<b>{length}</b> commit{plural} to <font color="purple">{branch}</font>: '
'<font color="navy"><u>{compareUrl}</u></font>'.format(
repo = matrix_html(repo),
user = matrix_html(user),
action = matrix_html(action),
length = length,
branch = matrix_html(branch),
compareUrl = url,
plural = matrix_html(plural)
)
)

# First 3 individual commits
num = 0
for commit in commits:
if num > 2:
break
# If commit message is longer than 75 characters, truncate.
commitMessage = commit["message"]
# We're only interested in the first line of the commit message, the title.
# If it is longer than 75 characters, truncate.
commitMessage = commit["message"].split('\n', 1)[0]
author = commit["author"]["name"]
if len(commitMessage) > 75:
commitMessage = commitMessage[0:74] + "..."
Expand Down Expand Up @@ -179,10 +208,22 @@ def Push(payload, poolRepos, shorten):
)
)

matrixMessages.append(
'<font color="fuchsia">{repo}</font>/<font color="purple">{branch}</font> '
'<font color="gray">{shortCommit}</font> <font color="lightgrey">{user}</font>: {message}'.format(
repo = repo,
branch = branch,
shortCommit = commit["id"][0:7],
user = author,
message = commitMessage
)
)

num += 1

return {
"statusCode": 200,
"ircMessages": ircMessages,
"mastMessages": mastMessages
"mastMessages": mastMessages,
"matrixMessages": matrixMessages
}

0 comments on commit e20e96a

Please sign in to comment.