4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-06-26 17:49:23 +00:00

Compare commits

...

19 Commits

Author SHA1 Message Date
Wim
86865c6da5 Release v1.10.0 2018-05-07 22:07:17 +02:00
Wim
45296100df Add initial zulip support 2018-05-07 21:35:48 +02:00
Wim
1605fbc012 Add vendor matterbridge/gozulipbot 2018-05-07 21:06:25 +02:00
Wim
c6c92e273d Use only alphanumeric for file uploads to mediaserver. Closes #416 2018-05-06 20:32:09 +02:00
Wim
467b373c43 Fix crash on invalid filenames 2018-05-06 20:14:16 +02:00
Wim
72ce7f06e9 Handle file comment better 2018-05-06 16:57:59 +02:00
Wim
346a7284f7 Handle file uploads to mediaserver (steam) 2018-05-06 16:32:24 +02:00
Wim
ee4ac67081 Fix possible nil when using channels (telegram). #410 2018-05-05 23:15:50 +02:00
Wim
5a93d14d75 Update issue templates 2018-05-05 18:04:03 +02:00
Wim
96a47a60ad Add support for reloading all settings automatically after changing config except connection and gateway configuration. Closes #373 2018-05-01 22:23:37 +02:00
Wim
b24a47ad7f Handle channel posts correctly (telegram) 2018-04-29 22:31:11 +02:00
Wim
cd1fd1bb7c Fix panic (telegram). Closes #410 2018-04-29 15:46:40 +02:00
Wim
d44df7b6e6 Fix alignment 2018-04-25 22:21:16 +02:00
Wim
9d1ac0c84b Add image to repo. Make more clear that mattermost is not required to run matterbridge 2018-04-25 22:20:06 +02:00
76af9cba5a Properly set Slack user who initiated slash command (#394)
* Properly set Slack user who initiated slash command
2018-04-25 21:27:34 +02:00
Wim
b69fc30902 Fix regression in ReplaceMessages and ReplaceNicks. Closes #407 2018-04-21 23:26:39 +02:00
Wim
c3174f4de9 Update GetFileLinks to API_V4 2018-04-21 20:49:44 +02:00
Wim
99ce68e9ba Use username if bot name is Slack API Tester (slack) 2018-04-20 01:01:45 +02:00
Wim
0cf73673a9 Bump version 2018-04-20 00:39:57 +02:00
23 changed files with 1227 additions and 19 deletions

26
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@ -0,0 +1,26 @@
---
name: Bug report
about: Create a report to help us improve. (Check the FAQ on the wiki first)
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots/debug logs**
If applicable, add screenshots to help explain your problem.
Use logs from running `matterbridge -debug` if possible.
**Environment (please complete the following information):**
- OS: [e.g. linux]
- Matterbridge version: output of `matterbridge -version`
- If self compiled: output of `git rev-parse HEAD`
**Additional context**
Please add your configuration file (be sure to exclude or anonymize private data (tokens/passwords))

View File

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -5,12 +5,15 @@ Click on one of the badges below to join the chat
[![Download stable](https://img.shields.io/github/release/42wim/matterbridge.svg?label=download%20stable)](https://github.com/42wim/matterbridge/releases/latest) [![Download dev](https://img.shields.io/bintray/v/42wim/nightly/Matterbridge.svg?label=download%20dev&colorB=007ec6)](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
![matterbridge.gif](https://s15.postimg.org/qpjhp6y3f/matterbridge.gif)
![matterbridge.gif](https://github.com/42wim/matterbridge/blob/master/img/matterbridge.gif)
Simple bridge between Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam and ssh-chat
Has a REST API.
Simple bridge between IRC, XMPP, Gitter, Mattermost, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam, ssh-chat and Zulip
Has a REST API.
Minecraft server chat support via [MatterLink](https://github.com/elytra/MatterLink)
**Mattermost isn't required to run matterbridge. It bridges between any supported protocol.**
(The name matterbridge is a remnant when it was only bridging mattermost)
# Table of Contents
* [Features](https://github.com/42wim/matterbridge/wiki/Features)
* [Requirements](#requirements)
@ -59,13 +62,14 @@ Accounts to one of the supported bridges
* [Steam](https://store.steampowered.com/)
* [Twitch](https://twitch.tv)
* [Ssh-chat](https://github.com/shazow/ssh-chat)
* [Zulip](https://zulipchat.com)
# Screenshots
See https://github.com/42wim/matterbridge/wiki
# Installing
## Binaries
* Latest stable release [v1.9.1](https://github.com/42wim/matterbridge/releases/latest)
* Latest stable release [v1.10.0](https://github.com/42wim/matterbridge/releases/latest)
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
## Building
@ -186,6 +190,7 @@ Matterbridge wouldn't exist without these libraries:
* echo - https://github.com/labstack/echo
* gitter - https://github.com/sromku/go-gitter
* gops - https://github.com/google/gops
* gozulipbot - https://github.com/ifo/gozulipbot
* irc - https://github.com/lrstanley/girc
* mattermost - https://github.com/mattermost/platform
* matrix - https://github.com/matrix-org/gomatrix

View File

@ -2,8 +2,10 @@ package config
import (
"bytes"
"github.com/fsnotify/fsnotify"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
prefixed "github.com/x-cray/logrus-prefixed-formatter"
"os"
"strings"
"sync"
@ -105,6 +107,7 @@ type Protocol struct {
StripNick bool // all protocols
Team string // mattermost
Token string // gitter, slack, discord, api
Topic string // zulip
URL string // mattermost, slack // DEPRECATED
UseAPI bool // mattermost, slack
UseSASL bool // IRC
@ -157,6 +160,7 @@ type ConfigValues struct {
Telegram map[string]Protocol
Rocketchat map[string]Protocol
Sshchat map[string]Protocol
Zulip map[string]Protocol
General Protocol
Gateway []Gateway
SameChannelGateway []SameChannelGateway
@ -169,9 +173,13 @@ type Config struct {
}
func NewConfig(cfgfile string) *Config {
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false})
flog := log.WithFields(log.Fields{"prefix": "config"})
var cfg ConfigValues
viper.SetConfigType("toml")
viper.SetConfigFile(cfgfile)
viper.SetEnvPrefix("matterbridge")
viper.AddConfigPath(".")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
f, err := os.Open(cfgfile)
@ -191,6 +199,11 @@ func NewConfig(cfgfile string) *Config {
if cfg.General.MediaDownloadSize == 0 {
cfg.General.MediaDownloadSize = 1000000
}
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
flog.Println("Config file changed:", e.Name)
})
mycfg.ConfigValues = &cfg
return mycfg
}
@ -243,11 +256,18 @@ func (c *Config) GetStringSlice(key string) []string {
func (c *Config) GetStringSlice2D(key string) [][]string {
c.RLock()
defer c.RUnlock()
if res, ok := c.v.Get(key).([][]string); ok {
return res
result := [][]string{}
if res, ok := c.v.Get(key).([]interface{}); ok {
for _, entry := range res {
result2 := []string{}
for _, entry2 := range entry.([]interface{}) {
result2 = append(result2, entry2.(string))
}
result = append(result, result2)
}
return result
}
// log.Debugf("getting StringSlice2D %s = %#v", key, c.v.Get(key))
return [][]string{}
return result
}
func GetIconURL(msg *Message, iconURL string) string {

View File

@ -168,6 +168,9 @@ func (b *Bgitter) handleUploadFile(msg *config.Message, roomID string) (string,
}
if fi.URL != "" {
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
}
}
_, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
if err != nil {

View File

@ -201,6 +201,9 @@ func (b *Birc) Send(msg config.Message) (string, error) {
}
if fi.URL != "" {
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
}
}
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
}

View File

@ -485,7 +485,7 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er
rmsg := config.Message{Text: ev.Text, Channel: channel.Name, Account: b.Account, ID: "slack " + ev.Timestamp, Extra: make(map[string][]interface{})}
// find the user id and name
if ev.BotID == "" && ev.SubType != messageDeleted && ev.SubType != "file_comment" {
if ev.User != "" && ev.SubType != messageDeleted && ev.SubType != "file_comment" {
user, err := b.rtm.GetUserInfo(ev.User)
if err != nil {
return nil, err
@ -509,7 +509,7 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er
}
// when using webhookURL we can't check if it's our webhook or not for now
if ev.BotID != "" && b.GetString("WebhookURL") == "" {
if rmsg.Username == "" && ev.BotID != "" && b.GetString("WebhookURL") == "" {
bot, err := b.rtm.GetBotInfo(ev.BotID)
if err != nil {
return nil, err
@ -521,6 +521,19 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er
}
rmsg.UserID = bot.ID
}
// fixes issues with matterircd users
if bot.Name == "Slack API Tester" {
user, err := b.rtm.GetUserInfo(ev.User)
if err != nil {
return nil, err
}
rmsg.UserID = user.ID
rmsg.Username = user.Name
if user.Profile.DisplayName != "" {
rmsg.Username = user.Profile.DisplayName
}
}
}
// file comments are set by the system (because there is no username given)

View File

@ -68,6 +68,9 @@ func (b *Bsshchat) Send(msg config.Message) (string, error) {
}
if fi.URL != "" {
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
}
}
b.w.Write([]byte(msg.Username + msg.Text))
}

View File

@ -2,8 +2,10 @@ package bsteam
import (
"fmt"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/Philipp15b/go-steam"
"github.com/Philipp15b/go-steam/protocol/steamlang"
"github.com/Philipp15b/go-steam/steamid"
@ -66,6 +68,30 @@ func (b *Bsteam) Send(msg config.Message) (string, error) {
if err != nil {
return "", err
}
// Handle files
if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, rmsg.Username+rmsg.Text)
}
if len(msg.Extra["file"]) > 0 {
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
if fi.Comment != "" {
msg.Text += fi.Comment + ": "
}
if fi.URL != "" {
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
}
}
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
}
return "", nil
}
}
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
return "", nil
}

View File

@ -121,7 +121,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
for update := range updates {
b.Log.Debugf("== Receiving event: %#v", update.Message)
if update.Message == nil && update.ChannelPost == nil {
if update.Message == nil && update.ChannelPost == nil && update.EditedMessage == nil && update.EditedChannelPost == nil {
b.Log.Error("Getting nil messages, this shouldn't happen.")
continue
}
@ -133,6 +133,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// handle channels
if update.ChannelPost != nil {
message = update.ChannelPost
rmsg.Text = message.Text
}
// edited channel message
@ -144,6 +145,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// handle groups
if update.Message != nil {
message = update.Message
rmsg.Text = message.Text
}
// edited group message
@ -154,11 +156,11 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// set the ID's from the channel or group message
rmsg.ID = strconv.Itoa(message.MessageID)
rmsg.UserID = strconv.Itoa(message.From.ID)
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
// handle username
if message.From != nil {
rmsg.UserID = strconv.Itoa(message.From.ID)
if b.GetBool("UseFirstName") {
rmsg.Username = message.From.FirstName
}
@ -168,7 +170,6 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
rmsg.Username = message.From.FirstName
}
}
rmsg.Text += message.Text
// only download avatars if we have a place to upload them (configured mediaserver)
if b.General.MediaServerUpload != "" {
b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
@ -228,7 +229,10 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
if rmsg.Text != "" || len(rmsg.Extra) > 0 {
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)
// channels don't have (always?) user information. see #410
if message.From != nil {
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)

View File

@ -186,7 +186,10 @@ func (b *Bxmpp) handleUploadFile(msg *config.Message) (string, error) {
msg.Text += fi.Comment + ": "
}
if fi.URL != "" {
msg.Text += fi.URL
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
}
}
_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text})
if err != nil {

170
bridge/zulip/zulip.go Normal file
View File

@ -0,0 +1,170 @@
package bzulip
import (
"encoding/json"
"io/ioutil"
"strconv"
"time"
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
gzb "github.com/matterbridge/gozulipbot"
)
type Bzulip struct {
q *gzb.Queue
bot *gzb.Bot
streams map[int]string
*bridge.Config
}
func New(cfg *bridge.Config) bridge.Bridger {
return &Bzulip{Config: cfg, streams: make(map[int]string)}
}
func (b *Bzulip) Connect() error {
bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login")}
bot.Init()
q, err := bot.RegisterAll()
b.q = q
b.bot = &bot
if err != nil {
b.Log.Errorf("Connect() %#v", err)
return err
}
// init stream
b.getChannel(0)
b.Log.Info("Connection succeeded")
go b.handleQueue()
return nil
}
func (b *Bzulip) Disconnect() error {
return nil
}
func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error {
return nil
}
func (b *Bzulip) Send(msg config.Message) (string, error) {
b.Log.Debugf("=> Receiving %#v", msg)
// Delete message
if msg.Event == config.EVENT_MSG_DELETE {
if msg.ID == "" {
return "", nil
}
_, err := b.bot.UpdateMessage(msg.ID, "")
return "", err
}
// Upload a file if it exists
if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
b.sendMessage(rmsg)
}
if len(msg.Extra["file"]) > 0 {
return b.handleUploadFile(&msg)
}
}
// edit the message if we have a msg ID
if msg.ID != "" {
_, err := b.bot.UpdateMessage(msg.ID, msg.Username+msg.Text)
return "", err
}
// Post normal message
return b.sendMessage(msg)
}
func (b *Bzulip) getChannel(id int) string {
if name, ok := b.streams[id]; ok {
return name
}
streams, err := b.bot.GetRawStreams()
if err != nil {
b.Log.Errorf("getChannel: %#v", err)
return ""
}
for _, stream := range streams.Streams {
b.streams[stream.StreamID] = stream.Name
}
if name, ok := b.streams[id]; ok {
return name
}
return ""
}
func (b *Bzulip) handleQueue() error {
for {
messages, _ := b.q.GetEvents()
for _, m := range messages {
b.Log.Debugf("== Receiving %#v", m)
// ignore our own messages
if m.SenderEmail == b.GetString("login") {
continue
}
rmsg := config.Message{Username: m.SenderFullName, Text: m.Content, Channel: b.getChannel(m.StreamID), Account: b.Account, UserID: strconv.Itoa(m.SenderID), Avatar: m.AvatarURL}
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
b.q.LastEventID = m.ID
}
time.Sleep(time.Second * 3)
}
}
func (b *Bzulip) sendMessage(msg config.Message) (string, error) {
topic := "matterbridge"
if b.GetString("topic") != "" {
topic = b.GetString("topic")
}
m := gzb.Message{
Stream: msg.Channel,
Topic: topic,
Content: msg.Username + msg.Text,
}
resp, err := b.bot.Message(m)
if err != nil {
return "", err
}
if resp != nil {
defer resp.Body.Close()
res, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var jr struct {
ID int `json:"id"`
}
err = json.Unmarshal(res, &jr)
if err != nil {
return "", err
}
return strconv.Itoa(jr.ID), nil
}
return "", nil
}
func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) {
for _, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
if fi.Comment != "" {
msg.Text += fi.Comment + ": "
}
if fi.URL != "" {
msg.Text = fi.URL
if fi.Comment != "" {
msg.Text = fi.Comment + ": " + fi.URL
}
}
_, err := b.sendMessage(*msg)
if err != nil {
return "", err
}
}
return "", nil
}

View File

@ -1,3 +1,22 @@
# v1.10.0
## New features
* general: Add support for reloading all settings automatically after changing config except connection and gateway configuration. Closes #373
* zulip: New protocol support added (https://zulipchat.com)
## Enhancements
* general: Handle file comment better
* steam: Handle file uploads to mediaserver (steam)
* slack: Properly set Slack user who initiated slash command (#394)
## Bugfix
* general: Use only alphanumeric for file uploads to mediaserver. Closes #416
* general: Fix crash on invalid filenames
* general: Fix regression in ReplaceMessages and ReplaceNicks. Closes #407
* telegram: Fix possible nil when using channels (telegram). #410
* telegram: Fix panic (telegram). Closes #410
* telegram: Handle channel posts correctly
* mattermost: Update GetFileLinks to API_V4
# v1.9.1
## New features
* telegram: Add QuoteDisable option (telegram). Closes #399. See QuoteDisable in matterbridge.toml.sample

View File

@ -17,12 +17,14 @@ import (
"github.com/42wim/matterbridge/bridge/steam"
"github.com/42wim/matterbridge/bridge/telegram"
"github.com/42wim/matterbridge/bridge/xmpp"
"github.com/42wim/matterbridge/bridge/zulip"
log "github.com/sirupsen/logrus"
// "github.com/davecgh/go-spew/spew"
"crypto/sha1"
"github.com/hashicorp/golang-lru"
"github.com/peterhellberg/emojilib"
"net/http"
"path/filepath"
"regexp"
"strings"
"time"
@ -61,6 +63,7 @@ var bridgeMap = map[string]bridge.Factory{
"steam": bsteam.New,
"telegram": btelegram.New,
"xmpp": bxmpp.New,
"zulip": bzulip.New,
}
func init() {
@ -409,6 +412,7 @@ func (gw *Gateway) modifyMessage(msg *config.Message) {
}
func (gw *Gateway) handleFiles(msg *config.Message) {
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
// if we don't have a attachfield or we don't have a mediaserver configured return
if msg.Extra == nil || gw.Config.General.MediaServerUpload == "" {
return
@ -421,15 +425,23 @@ func (gw *Gateway) handleFiles(msg *config.Message) {
}
for i, f := range msg.Extra["file"] {
fi := f.(config.FileInfo)
ext := filepath.Ext(fi.Name)
fi.Name = fi.Name[0 : len(fi.Name)-len(ext)]
fi.Name = reg.ReplaceAllString(fi.Name, "_")
fi.Name = fi.Name + ext
sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))
reader := bytes.NewReader(*fi.Data)
url := gw.Config.General.MediaServerUpload + "/" + sha1sum + "/" + fi.Name
durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
extra := msg.Extra["file"][i].(config.FileInfo)
extra.URL = durl
req, _ := http.NewRequest("PUT", url, reader)
req, err := http.NewRequest("PUT", url, reader)
if err != nil {
flog.Errorf("mediaserver upload failed: %#v", err)
continue
}
req.Header.Set("Content-Type", "binary/octet-stream")
_, err := client.Do(req)
_, err = client.Do(req)
if err != nil {
flog.Errorf("mediaserver upload failed: %#v", err)
continue

BIN
img/matterbridge.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -13,7 +13,7 @@ import (
)
var (
version = "1.9.1"
version = "1.10.0"
githash string
)

View File

@ -64,6 +64,9 @@ NickServPassword="secret"
#OPTIONAL only used for quakenet auth
NickServUsername="username"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Flood control
#Delay in milliseconds between each message send to the IRC server
#OPTIONAL (default 1300)
@ -184,6 +187,9 @@ Nick="xmppbot"
#OPTIONAL (default false)
SkipTLSVerify=true
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Nicks you want to ignore.
#Messages from those users will not be sent to other bridges.
#OPTIONAL
@ -265,6 +271,9 @@ Muc="conf.hipchat.com"
#REQUIRED
Nick="yourlogin"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Nicks you want to ignore.
#Messages from those users will not be sent to other bridges.
#OPTIONAL
@ -382,6 +391,9 @@ IconURL="http://youricon.png"
#OPTIONAL (default false)
SkipTLSVerify=true
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#how to format the list of IRC nicks when displayed in mattermost.
#Possible options are "table" and "plain"
#OPTIONAL (default plain)
@ -482,6 +494,9 @@ ShowTopicChange=false
#REQUIRED
Token="Yourtokenhere"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Nicks you want to ignore.
#Messages from those users will not be sent to other bridges.
#OPTIONAL
@ -577,6 +592,9 @@ WebhookBindAddress="0.0.0.0:9999"
#OPTIONAL
IconURL="https://robohash.org/{NICK}.png?size=48x48"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#how to format the list of IRC nicks when displayed in slack
#Possible options are "table" and "plain"
#OPTIONAL (default plain)
@ -680,6 +698,9 @@ Token="Yourtokenhere"
#REQUIRED
Server="yourservername"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Shows title, description and URL of embedded messages (sent by other bots)
#OPTIONAL (default false)
ShowEmbeds=false
@ -770,6 +791,9 @@ ShowTopicChange=false
#REQUIRED
Token="Yourtokenhere"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#OPTIONAL (default empty)
#Only supported format is "HTML", messages will be sent in html parsemode.
#See https://core.telegram.org/bots/api#html-style
@ -892,6 +916,9 @@ NoTLS=false
#OPTIONAL (default false)
SkipTLSVerify=true
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Whether to prefix messages from other bridges to rocketchat with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
@ -979,6 +1006,9 @@ Password="yourpass"
#OPTIONAL (default false)
NoHomeServerSuffix=false
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Whether to prefix messages from other bridges to matrix with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#matrix server. If you set PrefixMessagesWithNick to true, each message
@ -1060,6 +1090,9 @@ Password="yourpass"
#OPTIONAL
Authcode="ABCE12"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Whether to prefix messages from other bridges to matrix with the sender's nick.
#Useful if username overrides for incoming webhooks isn't enabled on the
#matrix server. If you set PrefixMessagesWithNick to true, each message
@ -1122,6 +1155,90 @@ StripNick=false
#OPTIONAL (default false)
ShowTopicChange=false
###################################################################
#zulip section
###################################################################
[zulip]
#You can configure multiple servers "[zulip.name]" or "[zulip.name2]"
#In this example we use [zulip.streamchat]
#REQUIRED
[zulip.streamchat]
#Token to connect with zulip API (called bot API key in Settings - Your bots)
#REQUIRED
Token="Yourtokenhere"
#Username of the bot, normally called yourbot-bot@yourserver.zulipchat.com
#See username in Settings - Your bots
#REQUIRED
Login="yourbot-bot@yourserver.zulipchat.com"
#Servername of your zulip instance
#REQUIRED
Server="https://yourserver.zulipchat.com"
#Topic of the messages matterbridge will use
#OPTIONAL (default "matterbridge")
Topic="matterbridge"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#Nicks you want to ignore.
#Messages from those users will not be sent to other bridges.
#OPTIONAL
IgnoreNicks="spammer1 spammer2"
#Messages you want to ignore.
#Messages matching these regexp will be ignored and not sent to other bridges
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
IgnoreMessages="^~~ badword"
#messages you want to replace.
#it replaces outgoing messages from the bridge.
#so you need to place it by the sending bridge definition.
#regular expressions supported
#some examples:
#this replaces cat => dog and sleep => awake
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
#this replaces every number with number. 123 => numbernumbernumber
#replacemessages=[ ["[0-9]","number"] ]
#optional (default empty)
ReplaceMessages=[ ["cat","dog"] ]
#nicks you want to replace.
#see replacemessages for syntaxa
#optional (default empty)
ReplaceNicks=[ ["user--","user"] ]
#extra label that can be used in the RemoteNickFormat
#optional (default empty)
Label=""
#RemoteNickFormat defines how remote users appear on this bridge
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
#The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
#OPTIONAL (default empty)
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
#Enable to show users joins/parts from other bridges
#Currently works for messages from the following bridges: irc, mattermost, slack
#OPTIONAL (default false)
ShowJoinPart=false
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
#It will strip other characters from the nick
#OPTIONAL (default false)
StripNick=false
#Enable to show topic changes from other bridges
#Only works hiding/show topic changes from slack bridge for now
#OPTIONAL (default false)
ShowTopicChange=false
###################################################################
#API
###################################################################
@ -1162,6 +1279,10 @@ RemoteNickFormat="{NICK}"
###################################################################
# Settings here are defaults that each protocol can override
[general]
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
#RemoteNickFormat defines how remote users appear on this bridge
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
@ -1246,6 +1367,7 @@ enable=true
# - encrypted rooms are not supported in matrix
#steam - chatid (a large number).
# The number in the URL when you click "enter chat room" in the browser
#zulip - stream (without the #)
#
#REQUIRED
channel="#testing"

View File

@ -574,7 +574,7 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
res, resp := m.Client.GetFileLink(f)
if resp.Error != nil {
// public links is probably disabled, create the link ourselves
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get")
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V4+"/files/"+f)
continue
}
output = append(output, res)

256
vendor/github.com/matterbridge/gozulipbot/bot.go generated vendored Normal file
View File

@ -0,0 +1,256 @@
package gozulipbot
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
)
type Bot struct {
APIKey string
APIURL string
Email string
Queues []*Queue
Streams []string
Client Doer
Backoff time.Duration
Retries int64
}
type Doer interface {
Do(*http.Request) (*http.Response, error)
}
// Init adds an http client to an existing bot struct.
func (b *Bot) Init() *Bot {
b.Client = &http.Client{}
return b
}
// GetStreamList gets the raw http response when requesting all public streams.
func (b *Bot) GetStreamList() (*http.Response, error) {
req, err := b.constructRequest("GET", "streams", "")
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
type StreamJSON struct {
Msg string `json:"msg"`
Streams []struct {
StreamID int `json:"stream_id"`
InviteOnly bool `json:"invite_only"`
Description string `json:"description"`
Name string `json:"name"`
} `json:"streams"`
Result string `json:"result"`
}
// GetStreams returns a list of all public streams
func (b *Bot) GetStreams() ([]string, error) {
resp, err := b.GetStreamList()
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var sj StreamJSON
err = json.Unmarshal(body, &sj)
if err != nil {
return nil, err
}
var streams []string
for _, s := range sj.Streams {
streams = append(streams, s.Name)
}
return streams, nil
}
// GetStreams returns a list of all public streams
func (b *Bot) GetRawStreams() (StreamJSON, error) {
var sj StreamJSON
resp, err := b.GetStreamList()
if err != nil {
return sj, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return sj, err
}
err = json.Unmarshal(body, &sj)
if err != nil {
return sj, err
}
return sj, nil
}
// Subscribe will set the bot to receive messages from the given streams.
// If no streams are given, it will subscribe the bot to the streams in the bot struct.
func (b *Bot) Subscribe(streams []string) (*http.Response, error) {
if streams == nil {
streams = b.Streams
}
var toSubStreams []map[string]string
for _, name := range streams {
toSubStreams = append(toSubStreams, map[string]string{"name": name})
}
bodyBts, err := json.Marshal(toSubStreams)
if err != nil {
return nil, err
}
body := "subscriptions=" + string(bodyBts)
req, err := b.constructRequest("POST", "users/me/subscriptions", body)
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
// Unsubscribe will remove the bot from the given streams.
// If no streams are given, nothing will happen and the function will error.
func (b *Bot) Unsubscribe(streams []string) (*http.Response, error) {
if len(streams) == 0 {
return nil, fmt.Errorf("No streams were provided")
}
body := `delete=["` + strings.Join(streams, `","`) + `"]`
req, err := b.constructRequest("PATCH", "users/me/subscriptions", body)
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
func (b *Bot) ListSubscriptions() (*http.Response, error) {
req, err := b.constructRequest("GET", "users/me/subscriptions", "")
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
type EventType string
const (
Messages EventType = "messages"
Subscriptions EventType = "subscriptions"
RealmUser EventType = "realm_user"
Pointer EventType = "pointer"
)
type Narrow string
const (
NarrowPrivate Narrow = `[["is", "private"]]`
NarrowAt Narrow = `[["is", "mentioned"]]`
)
// RegisterEvents adds a queue to the bot. It includes the EventTypes and
// Narrow given. If neither is given, it will default to all Messages.
func (b *Bot) RegisterEvents(ets []EventType, n Narrow) (*Queue, error) {
resp, err := b.RawRegisterEvents(ets, n)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
q := &Queue{Bot: b}
err = json.Unmarshal(body, q)
if err != nil {
return nil, err
}
if q.LastEventID < q.MaxMessageID {
q.LastEventID = q.MaxMessageID
}
b.Queues = append(b.Queues, q)
return q, nil
}
func (b *Bot) RegisterAll() (*Queue, error) {
return b.RegisterEvents(nil, "")
}
func (b *Bot) RegisterAt() (*Queue, error) {
return b.RegisterEvents(nil, NarrowAt)
}
func (b *Bot) RegisterPrivate() (*Queue, error) {
return b.RegisterEvents(nil, NarrowPrivate)
}
func (b *Bot) RegisterSubscriptions() (*Queue, error) {
events := []EventType{Subscriptions}
return b.RegisterEvents(events, "")
}
// RawRegisterEvents tells Zulip to include message events in the bots events queue.
// Passing nil as the slice of EventType will default to receiving Messages
func (b *Bot) RawRegisterEvents(ets []EventType, n Narrow) (*http.Response, error) {
// default to Messages if no EventTypes given
query := `event_types=["message"]`
if len(ets) != 0 {
query = `event_types=["`
for i, s := range ets {
query += fmt.Sprintf("%s", s)
if i != len(ets)-1 {
query += `", "`
}
}
query += `"]`
}
if n != "" {
query += fmt.Sprintf("&narrow=%s", n)
}
query += fmt.Sprintf("&all_public_streams=true")
req, err := b.constructRequest("POST", "register", query)
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
// constructRequest makes a zulip request and ensures the proper headers are set.
func (b *Bot) constructRequest(method, endpoint, body string) (*http.Request, error) {
url := b.APIURL + endpoint
req, err := http.NewRequest(method, url, strings.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(b.Email, b.APIKey)
return req, nil
}

32
vendor/github.com/matterbridge/gozulipbot/flag.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
package gozulipbot
import (
"flag"
"fmt"
"time"
)
func (b *Bot) GetConfigFromFlags() error {
var (
apiKey = flag.String("apikey", "", "bot api key")
apiURL = flag.String("apiurl", "", "url of zulip server")
email = flag.String("email", "", "bot email address")
backoff = flag.Duration("backoff", 1*time.Second, "backoff base duration")
)
flag.Parse()
if *apiKey == "" {
return fmt.Errorf("--apikey is required")
}
if *apiURL == "" {
return fmt.Errorf("--apiurl is required")
}
if *email == "" {
return fmt.Errorf("--email is required")
}
b.APIKey = *apiKey
b.APIURL = *apiURL
b.Email = *email
b.Backoff = *backoff
return nil
}

263
vendor/github.com/matterbridge/gozulipbot/message.go generated vendored Normal file
View File

@ -0,0 +1,263 @@
package gozulipbot
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
// A Message is all of the necessary metadata to post on Zulip.
// It can be either a public message, where Topic is set, or a private message,
// where there is at least one element in Emails.
//
// If the length of Emails is not 0, functions will always assume it is a private message.
type Message struct {
Stream string
Topic string
Emails []string
Content string
}
type EventMessage struct {
AvatarURL string `json:"avatar_url"`
Client string `json:"client"`
Content string `json:"content"`
ContentType string `json:"content_type"`
DisplayRecipient DisplayRecipient `json:"display_recipient"`
GravatarHash string `json:"gravatar_hash"`
ID int `json:"id"`
RecipientID int `json:"recipient_id"`
SenderDomain string `json:"sender_domain"`
SenderEmail string `json:"sender_email"`
SenderFullName string `json:"sender_full_name"`
SenderID int `json:"sender_id"`
SenderShortName string `json:"sender_short_name"`
Subject string `json:"subject"`
SubjectLinks []interface{} `json:"subject_links"`
StreamID int `json:"stream_id"`
Timestamp int `json:"timestamp"`
Type string `json:"type"`
Queue *Queue `json:"-"`
}
type DisplayRecipient struct {
Users []User `json:"users,omitempty"`
Topic string `json:"topic,omitempty"`
}
type User struct {
Domain string `json:"domain"`
Email string `json:"email"`
FullName string `json:"full_name"`
ID int `json:"id"`
IsMirrorDummy bool `json:"is_mirror_dummy"`
ShortName string `json:"short_name"`
}
func (d *DisplayRecipient) UnmarshalJSON(b []byte) (err error) {
topic, users := "", make([]User, 1)
if err = json.Unmarshal(b, &topic); err == nil {
d.Topic = topic
return
}
if err = json.Unmarshal(b, &users); err == nil {
d.Users = users
return
}
return
}
// Message posts a message to Zulip. If any emails have been set on the message,
// the message will be re-routed to the PrivateMessage function.
func (b *Bot) Message(m Message) (*http.Response, error) {
if m.Content == "" {
return nil, fmt.Errorf("content cannot be empty")
}
// if any emails are set, this is a private message
if len(m.Emails) != 0 {
return b.PrivateMessage(m)
}
// otherwise it's a stream message
if m.Stream == "" {
return nil, fmt.Errorf("stream cannot be empty")
}
if m.Topic == "" {
return nil, fmt.Errorf("topic cannot be empty")
}
req, err := b.constructMessageRequest(m)
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
// PrivateMessage sends a message to the users in the message email slice.
func (b *Bot) PrivateMessage(m Message) (*http.Response, error) {
if len(m.Emails) == 0 {
return nil, fmt.Errorf("there must be at least one recipient")
}
req, err := b.constructMessageRequest(m)
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
// Respond sends a given message as a response to whatever context from which
// an EventMessage was received.
func (b *Bot) Respond(e EventMessage, response string) (*http.Response, error) {
if response == "" {
return nil, fmt.Errorf("Message response cannot be blank")
}
m := Message{
Stream: e.DisplayRecipient.Topic,
Topic: e.Subject,
Content: response,
}
if m.Topic != "" {
return b.Message(m)
}
// private message
if m.Stream == "" {
emails, err := b.privateResponseList(e)
if err != nil {
return nil, err
}
m.Emails = emails
return b.Message(m)
}
return nil, fmt.Errorf("EventMessage is not understood: %v\n", e)
}
// privateResponseList gets the list of other users in a private multiple
// message conversation.
func (b *Bot) privateResponseList(e EventMessage) ([]string, error) {
var out []string
for _, u := range e.DisplayRecipient.Users {
if u.Email != b.Email {
out = append(out, u.Email)
}
}
if len(out) == 0 {
return nil, fmt.Errorf("EventMessage had no Users within the DisplayRecipient")
}
return out, nil
}
// constructMessageRequest is a helper for simplifying sending a message.
func (b *Bot) constructMessageRequest(m Message) (*http.Request, error) {
to := m.Stream
mtype := "stream"
le := len(m.Emails)
if le != 0 {
mtype = "private"
}
if le == 1 {
to = m.Emails[0]
}
if le > 1 {
to = ""
for i, e := range m.Emails {
to += e
if i != le-1 {
to += ","
}
}
}
values := url.Values{}
values.Set("type", mtype)
values.Set("to", to)
values.Set("content", m.Content)
if mtype == "stream" {
values.Set("subject", m.Topic)
}
return b.constructRequest("POST", "messages", values.Encode())
}
func (b *Bot) UpdateMessage(id string, content string) (*http.Response, error) {
//mid, _ := strconv.Atoi(id)
values := url.Values{}
values.Set("content", content)
req, err := b.constructRequest("PATCH", "messages/"+id, values.Encode())
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
// React adds an emoji reaction to an EventMessage.
func (b *Bot) React(e EventMessage, emoji string) (*http.Response, error) {
url := fmt.Sprintf("messages/%d/emoji_reactions/%s", e.ID, emoji)
req, err := b.constructRequest("PUT", url, "")
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
// Unreact removes an emoji reaction from an EventMessage.
func (b *Bot) Unreact(e EventMessage, emoji string) (*http.Response, error) {
url := fmt.Sprintf("messages/%d/emoji_reactions/%s", e.ID, emoji)
req, err := b.constructRequest("DELETE", url, "")
if err != nil {
return nil, err
}
return b.Client.Do(req)
}
type Emoji struct {
Author string `json:"author"`
DisplayURL string `json:"display_url"`
SourceURL string `json:"source_url"`
}
type EmojiResponse struct {
Emoji map[string]*Emoji `json:"emoji"`
Msg string `json:"msg"`
Result string `json:"result"`
}
// RealmEmoji gets the custom emoji information for the Zulip instance.
func (b *Bot) RealmEmoji() (map[string]*Emoji, error) {
req, err := b.constructRequest("GET", "realm/emoji", "")
if err != nil {
return nil, err
}
resp, err := b.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var emjResp EmojiResponse
err = json.Unmarshal(body, &emjResp)
if err != nil {
return nil, err
}
return emjResp.Emoji, nil
}
// RealmEmojiSet makes a set of the names of the custom emoji in the Zulip instance.
func (b *Bot) RealmEmojiSet() (map[string]struct{}, error) {
emj, err := b.RealmEmoji()
if err != nil {
return nil, nil
}
out := map[string]struct{}{}
for k, _ := range emj {
out[k] = struct{}{}
}
return out, nil
}

203
vendor/github.com/matterbridge/gozulipbot/queue.go generated vendored Normal file
View File

@ -0,0 +1,203 @@
package gozulipbot
import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"net/url"
"strconv"
"sync/atomic"
"time"
)
var (
HeartbeatError = fmt.Errorf("EventMessage is a heartbeat")
UnauthorizedError = fmt.Errorf("Request is unauthorized")
BackoffError = fmt.Errorf("Too many requests")
UnknownError = fmt.Errorf("Error was unknown")
)
type Queue struct {
ID string `json:"queue_id"`
LastEventID int `json:"last_event_id"`
MaxMessageID int `json:"max_message_id"`
Bot *Bot `json:"-"`
}
func (q *Queue) EventsChan() (chan EventMessage, func()) {
end := false
endFunc := func() {
end = true
}
out := make(chan EventMessage)
go func() {
defer close(out)
for {
backoffTime := time.Now().Add(q.Bot.Backoff * time.Duration(math.Pow10(int(atomic.LoadInt64(&q.Bot.Retries)))))
minTime := time.Now().Add(q.Bot.Backoff)
if end {
return
}
ems, err := q.GetEvents()
switch {
case err == HeartbeatError:
time.Sleep(time.Until(minTime))
continue
case err == BackoffError:
time.Sleep(time.Until(backoffTime))
atomic.AddInt64(&q.Bot.Retries, 1)
case err == UnauthorizedError:
// TODO? have error channel when ending the continuously running process?
return
default:
atomic.StoreInt64(&q.Bot.Retries, 0)
}
if err != nil {
// TODO: handle unknown error
// For now, handle this like an UnauthorizedError and end the func.
return
}
for _, em := range ems {
out <- em
}
// Always make sure we wait the minimum time before asking again.
time.Sleep(time.Until(minTime))
}
}()
return out, endFunc
}
// EventsCallback will repeatedly call provided callback function with
// the output of continual queue.GetEvents calls.
// It returns a function which can be called to end the calls.
//
// It will end early if it receives an UnauthorizedError, or an unknown error.
// Note, it will never return a HeartbeatError.
func (q *Queue) EventsCallback(fn func(EventMessage, error)) func() {
end := false
endFunc := func() {
end = true
}
go func() {
for {
backoffTime := time.Now().Add(q.Bot.Backoff * time.Duration(math.Pow10(int(atomic.LoadInt64(&q.Bot.Retries)))))
minTime := time.Now().Add(q.Bot.Backoff)
if end {
return
}
ems, err := q.GetEvents()
switch {
case err == HeartbeatError:
time.Sleep(time.Until(minTime))
continue
case err == BackoffError:
time.Sleep(time.Until(backoffTime))
atomic.AddInt64(&q.Bot.Retries, 1)
case err == UnauthorizedError:
// TODO? have error channel when ending the continuously running process?
return
default:
atomic.StoreInt64(&q.Bot.Retries, 0)
}
if err != nil {
// TODO: handle unknown error
// For now, handle this like an UnauthorizedError and end the func.
return
}
for _, em := range ems {
fn(em, err)
}
// Always make sure we wait the minimum time before asking again.
time.Sleep(time.Until(minTime))
}
}()
return endFunc
}
// GetEvents is a blocking call that waits for and parses a list of EventMessages.
// There will usually only be one EventMessage returned.
// When a heartbeat is returned, GetEvents will return a HeartbeatError.
// When an http status code above 400 is returned, one of a BackoffError,
// UnauthorizedError, or UnknownError will be returned.
func (q *Queue) GetEvents() ([]EventMessage, error) {
resp, err := q.RawGetEvents()
if err != nil {
return nil, err
}
defer resp.Body.Close()
switch {
case resp.StatusCode == 429:
return nil, BackoffError
case resp.StatusCode == 403:
return nil, UnauthorizedError
case resp.StatusCode >= 400:
return nil, UnknownError
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
msgs, err := q.ParseEventMessages(body)
if err != nil {
return nil, err
}
return msgs, nil
}
// RawGetEvents is a blocking call that receives a response containing a list
// of events (a.k.a. received messages) since the last message id in the queue.
func (q *Queue) RawGetEvents() (*http.Response, error) {
values := url.Values{}
values.Set("queue_id", q.ID)
values.Set("last_event_id", strconv.Itoa(q.LastEventID))
url := "events?" + values.Encode()
req, err := q.Bot.constructRequest("GET", url, "")
if err != nil {
return nil, err
}
return q.Bot.Client.Do(req)
}
func (q *Queue) ParseEventMessages(rawEventResponse []byte) ([]EventMessage, error) {
rawResponse := map[string]json.RawMessage{}
err := json.Unmarshal(rawEventResponse, &rawResponse)
if err != nil {
return nil, err
}
events := []map[string]json.RawMessage{}
err = json.Unmarshal(rawResponse["events"], &events)
if err != nil {
return nil, err
}
messages := []EventMessage{}
for _, event := range events {
// if the event is a heartbeat, return a special error
if string(event["type"]) == `"heartbeat"` {
return nil, HeartbeatError
}
var msg EventMessage
err = json.Unmarshal(event["message"], &msg)
// TODO? should this check be here
if err != nil {
return nil, err
}
msg.Queue = q
messages = append(messages, msg)
}
return messages, nil
}

8
vendor/manifest vendored
View File

@ -405,6 +405,14 @@
"branch": "work",
"notests": true
},
{
"importpath": "github.com/matterbridge/gozulipbot",
"repository": "https://github.com/matterbridge/gozulipbot",
"vcs": "git",
"revision": "b6bb12d33544893aa68904652704cf1a86ea3d18",
"branch": "work",
"notests": true
},
{
"importpath": "github.com/mattermost/mattermost-server/einterfaces",
"repository": "https://github.com/mattermost/mattermost-server",