Skip to content
Snippets Groups Projects
Commit 75b34e7e authored by Tulir Asokan's avatar Tulir Asokan :cat2:
Browse files

Improve project structure

parent 7afa40d9
No related branches found
No related tags found
No related merge requests found
tokens.json
config.json
// maulabbot - A Gitlab bot for Matrix
// Copyright (C) 2017 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"encoding/json"
"io/ioutil"
)
// Config is the config for Maunium GitLab bot.
type Config struct {
Webhook struct {
Listen string `json:"listen"`
Path string `json:"path"`
Secret string `json:"secret"`
} `json:"webhook"`
Matrix struct {
Homeserver string `json:"homeserver"`
Username string `json:"username"`
Password string `json:"password"`
}
GitLab struct {
Domain string `json:"domain"`
} `json:"gitlab"`
}
var config Config
func loadConfig(path string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
return json.Unmarshal(data, &config)
}
{
"webhook": {
"listen": "localhost:20087",
"path": "/webhooks",
"secret": "foobar"
},
"gitlab": {
"domain": "https://gitlab.org"
},
"matrix": {
"homeserver": "https://matrix.org",
"username": "",
"password": ""
}
}
// maulabbot - A Gitlab bot for Matrix
// Copyright (C) 2017 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
gitlab "github.com/xanzy/go-gitlab"
)
var gitlabTokens = make(map[string]string)
func saveGitlabTokens() {
data, _ := json.MarshalIndent(gitlabTokens, "", " ")
ioutil.WriteFile("tokens.json", data, 0600)
}
func loadGitlabTokens() {
data, err := ioutil.ReadFile("tokens.json")
if err != nil {
return
}
err = json.Unmarshal(data, &gitlabTokens)
if err != nil {
panic(err)
}
}
func loginGitlab(userID, token string) string {
git := gitlab.NewClient(nil, token)
err := git.SetBaseURL(fmt.Sprintf("%s/api/v4", config.GitLab.Domain))
if err != nil {
return err.Error()
}
user, resp, err := git.Users.CurrentUser()
if resp.StatusCode == 401 {
return fmt.Sprintf("Invalid access token!")
} else if err != nil {
return fmt.Sprintf("GitLab login failed: %s", err)
}
gitlabTokens[userID] = token
saveGitlabTokens()
return fmt.Sprintf("Successfully logged into GitLab at %s as %s\n", git.BaseURL().Hostname(), user.Name)
}
func logoutGitlab(userID string) {
delete(gitlabTokens, userID)
saveGitlabTokens()
}
func getGitlabClient(userID string) *gitlab.Client {
token, ok := gitlabTokens[userID]
if !ok {
return nil
}
git := gitlab.NewClient(nil, token)
err := git.SetBaseURL(fmt.Sprintf("%s/api/v4", config.GitLab.Domain))
if err != nil {
return nil
}
return git
}
......@@ -17,75 +17,18 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/xanzy/go-gitlab"
flag "maunium.net/go/mauflag"
"maunium.net/go/mautrix"
)
var gitlabTokens = make(map[string]string)
var gitlabDomain = flag.MakeFull("d", "gitlab-domain", "GitLab domain", "https://gitlab.com").String()
func saveGitlabTokens() {
data, _ := json.MarshalIndent(gitlabTokens, "", " ")
ioutil.WriteFile("tokens.json", data, 0600)
}
func loadGitlabTokens() {
data, err := ioutil.ReadFile("tokens.json")
if err != nil {
return
}
err = json.Unmarshal(data, &gitlabTokens)
if err != nil {
panic(err)
}
}
func loginGitlab(userID, token string) string {
git := gitlab.NewClient(nil, token)
err := git.SetBaseURL(fmt.Sprintf("%s/api/v4", *gitlabDomain))
if err != nil {
return err.Error()
}
user, resp, err := git.Users.CurrentUser()
if resp.StatusCode == 401 {
return fmt.Sprintf("Invalid access token!")
} else if err != nil {
return fmt.Sprintf("GitLab login failed: %s", err)
}
gitlabTokens[userID] = token
saveGitlabTokens()
return fmt.Sprintf("Successfully logged into GitLab at %s as %s\n", git.BaseURL().Hostname(), user.Name)
}
func getGitlabClient(userID string) *gitlab.Client {
token, ok := gitlabTokens[userID]
if !ok {
return nil
}
git := gitlab.NewClient(nil, token)
err := git.SetBaseURL(fmt.Sprintf("%s/api/v4", *gitlabDomain))
if err != nil {
return nil
}
return git
}
func handlePreloginGitlabCommand(room *mautrix.Room, sender, command string, args ...string) {
switch command {
case "ping":
room.Send("Pong.")
case "server":
room.Send(fmt.Sprintf("I'm using the GitLab server at %s", *gitlabDomain))
room.Sendf("I'm using the GitLab server at %s", config.GitLab.Domain)
case "login":
if len(args) == 0 {
room.SendHTML("Usage: <code>!gitlab login &lt;access token&gt;</code>")
......@@ -122,25 +65,24 @@ func handleGitlabCommand(room *mautrix.Room, sender, command string, args ...str
case "ping":
room.Send("Pong.")
case "server":
room.Send(fmt.Sprintf("I'm using the GitLab server at %s", *gitlabDomain))
room.Sendf("I'm using the GitLab server at %s", config.GitLab.Domain)
case "login":
room.Send("You're already logged in.")
case "logout":
delete(gitlabTokens, sender)
saveGitlabTokens()
logoutGitlab(sender)
room.Send("Access token removed successfully.")
case "whoami":
user, _, err := git.Users.CurrentUser()
if err != nil {
room.Send(fmt.Sprintf("Unexpected error: %s", err))
room.Sendf("Unexpected error: %s", err)
return
}
room.SendHTML(fmt.Sprintf(
room.SendfHTML(
"You're logged into %[1]s as <a href='%[2]s/%[3]s'>%[4]s</a>",
git.BaseURL().Hostname(),
*gitlabDomain,
config.GitLab.Domain,
user.Username,
user.Name))
user.Name)
case "commit":
if len(args) < 2 {
room.SendHTML("Usage: <code>!gitlab commit &lt;repo&gt; &lt;hash&gt;</code>")
......@@ -149,16 +91,16 @@ func handleGitlabCommand(room *mautrix.Room, sender, command string, args ...str
commit, _, err := git.Commits.GetCommit(args[0], args[1])
if err != nil {
room.Send(fmt.Sprintf("An error occurred: %s", err))
room.Sendf("An error occurred: %s", err)
return
}
room.SendHTML(fmt.Sprintf(
room.SendfHTML(
"<a href='%s'>Commit %s</a> by %s at %s:<br/><blockquote>%s</blockquote>",
fmt.Sprintf("%s/%s/commit/%s", *gitlabDomain, args[0], commit.ID),
fmt.Sprintf("%s/%s/commit/%s", config.GitLab.Domain, args[0], commit.ID),
commit.ShortID,
commit.AuthorName,
commit.CommittedDate.Format("Jan _2, 2006 15:04:05"),
strings.Replace(commit.Message, "\n", "<br/>", -1)))
strings.Replace(commit.Message, "\n", "<br/>", -1))
case "help":
room.SendHTML(`<pre>
Commands are prefixed with !gitlab
......
......@@ -24,13 +24,8 @@ import (
"gopkg.in/go-playground/webhooks.v3"
"gopkg.in/go-playground/webhooks.v3/gitlab"
flag "maunium.net/go/mauflag"
)
var gitlabListenAddr = flag.MakeFull("l", "webhook-listen", "GitLab listen address", ":8080").String()
var gitlabListenPath = flag.MakeFull("g", "webhook-path", "GitLab listen path", "/webhooks").String()
var gitlabSecret = flag.MakeFull("e", "webhook-secret", "GitLab secret", "").String()
func addRoomToHeaders(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
room := r.URL.Query().Get("room")
......@@ -45,20 +40,20 @@ func addRoomToHeaders(handler http.Handler) http.Handler {
}
func startWebhook() func() {
hook := gitlab.New(&gitlab.Config{Secret: *gitlabSecret})
hook := gitlab.New(&gitlab.Config{Secret: config.Webhook.Secret})
hook.RegisterEvents(handlePushEvent, gitlab.PushEvents)
hook.RegisterEvents(handleIssueEvent, gitlab.IssuesEvents)
hook.RegisterEvents(handleMergeRequestEvent, gitlab.MergeRequestEvents)
hook.RegisterEvents(handleCommentEvent, gitlab.CommentEvents)
server := &http.Server{Addr: *gitlabListenAddr}
server := &http.Server{Addr: config.Webhook.Listen}
go func() {
mux := http.NewServeMux()
mux.Handle(*gitlabListenPath, addRoomToHeaders(webhooks.Handler(hook)))
mux.Handle(config.Webhook.Path, addRoomToHeaders(webhooks.Handler(hook)))
server.Handler = mux
fmt.Println("Listening to GitLab webhooks at", *gitlabListenAddr+*gitlabListenPath)
fmt.Println("Listening to GitLab webhooks at", config.Webhook.Listen+config.Webhook.Path)
err := server.ListenAndServe()
if err != nil {
fmt.Println(err)
......@@ -76,13 +71,13 @@ func handlePushEvent(payload interface{}, header webhooks.Header) {
branch := strings.TrimPrefix(data.Ref, "refs/heads/")
if data.TotalCommitsCount == 0 {
room.SendHTML(fmt.Sprintf(
room.SendfHTML(
"[%[3]s/%[4]s] %[5]s force pushed to or deleted branch <a href='%[1]s/tree/%[2]s'>%[2]s</a>",
data.Project.WebURL,
branch,
data.Project.Namespace,
data.Project.Name,
data.UserName))
data.UserName)
return
}
......@@ -90,7 +85,7 @@ func handlePushEvent(payload interface{}, header webhooks.Header) {
if data.TotalCommitsCount != 1 {
pluralizer = "s"
}
room.SendHTML(fmt.Sprintf(
room.SendfHTML(
"[<a href='%[1]s/tree/%[2]s'>%[3]s/%[4]s#%[2]s</a>] %[5]d new commit%[7]s by %[6]s",
data.Project.WebURL,
branch,
......@@ -98,7 +93,7 @@ func handlePushEvent(payload interface{}, header webhooks.Header) {
data.Project.Name,
data.TotalCommitsCount,
data.UserName,
pluralizer))
pluralizer)
// IRC compatibility: Allow up to 4 commits to be displayed through the IRC bridge without
// having the bridge turn the message into a link.
if data.TotalCommitsCount > 4 {
......@@ -121,7 +116,7 @@ func handlePushEvent(payload interface{}, header webhooks.Header) {
if strings.Contains(message, "\n") {
message = fmt.Sprintf("%s (...)", strings.Split(message, "\n")[0])
}
room.SendHTML(fmt.Sprintf("<ul><li>%s (%s)</li></ul>", message, commit.ID[:8]))
room.SendfHTML("<ul><li>%s (%s)</li></ul>", message, commit.ID[:8])
}
}
}
......@@ -137,7 +132,7 @@ func handleIssueEvent(payload interface{}, header webhooks.Header) {
} else if !strings.HasSuffix(action, "e") {
action += "e"
}
room.SendHTML(fmt.Sprintf(
room.SendfHTML(
"[%[1]s/%[2]s] %[3]s %[4]sd issue <a href='%[5]s'>%[6]s (#%[7]d)</a>",
data.Project.Namespace,
data.Project.Name,
......@@ -145,7 +140,7 @@ func handleIssueEvent(payload interface{}, header webhooks.Header) {
action,
data.ObjectAttributes.URL,
data.ObjectAttributes.Title,
data.ObjectAttributes.IID))
data.ObjectAttributes.IID)
}
func handleMergeRequestEvent(payload interface{}, header webhooks.Header) {
......@@ -159,7 +154,7 @@ func handleMergeRequestEvent(payload interface{}, header webhooks.Header) {
} else if !strings.HasSuffix(action, "e") {
action += "e"
}
room.SendHTML(fmt.Sprintf(
room.SendfHTML(
"[%[1]s/%[2]s] %[3]s %[4]sd merge request <a href='%[5]s'>%[6]s (#%[7]d)</a>",
data.ObjectAttributes.Target.Namespace,
data.ObjectAttributes.Target.Name,
......@@ -167,7 +162,7 @@ func handleMergeRequestEvent(payload interface{}, header webhooks.Header) {
action,
data.ObjectAttributes.URL,
data.ObjectAttributes.Title,
data.ObjectAttributes.IID))
data.ObjectAttributes.IID)
}
func handleCommentEvent(payload interface{}, header webhooks.Header) {
......@@ -188,7 +183,7 @@ func handleCommentEvent(payload interface{}, header webhooks.Header) {
id = data.MergeRequest.IID
}
room.SendHTML(fmt.Sprintf(
room.SendfHTML(
"[%[1]s/%[2]s] %[3]s <a href='%[5]s'>commented</a> on %[4]s %[6]s (#%[7]d)",
data.Project.Namespace,
data.Project.Name,
......@@ -196,5 +191,5 @@ func handleCommentEvent(payload interface{}, header webhooks.Header) {
notebookType,
data.ObjectAttributes.URL,
title,
id))
id)
}
......@@ -20,23 +20,19 @@ import (
"fmt"
"strings"
flag "maunium.net/go/mauflag"
"maunium.net/go/mautrix"
)
var homeserver = flag.MakeFull("s", "homeserver", "Matrix homeserver", "https://matrix.org").String()
var username = flag.MakeFull("u", "username", "Matrix username", "").String()
var password = flag.MakeFull("p", "password", "Matrix password", "").String()
var mxbot *mautrix.MatrixBot
func startMatrix() func() {
mxbot = mautrix.Create(*homeserver)
mxbot = mautrix.Create(config.Matrix.Homeserver)
err := mxbot.PasswordLogin(*username, *password)
err := mxbot.PasswordLogin(config.Matrix.Username, config.Matrix.Password)
if err != nil {
panic(err)
}
fmt.Println("Connected to Matrix homeserver at", *homeserver, "as", *username)
fmt.Println("Connected to Matrix homeserver at", config.Matrix.Homeserver, "as", config.Matrix.Username)
stop := make(chan bool, 1)
......
......@@ -26,9 +26,10 @@ import (
)
var wantHelp, _ = flag.MakeHelpFlag()
var configPath = flag.MakeFull("c", "config", "The path to the config file.", "config.json").String()
func main() {
flag.SetHelpTitles("maulabbot - A GitLab bot for Matrix", "maulabbot [-h] [-s hs] [-u user] [-p passwd] [-l listen addr] [-g listen path] [-e secret]")
flag.SetHelpTitles("maulabbot - A GitLab bot for Matrix", "maulabbot [-h] [-c /path/to/config]")
err := flag.Parse()
if err != nil {
fmt.Println(err)
......@@ -38,13 +39,24 @@ func main() {
flag.PrintHelp()
return
}
err = loadConfig(*configPath)
if err != nil {
fmt.Println("Error loading config:", err)
return
}
// Initialize everything
stopMatrix := startMatrix()
stopWebhook := startWebhook()
loadGitlabTokens()
// Wait for interrupts
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
// Stop everything
stopMatrix()
stopWebhook()
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment