mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-26 21:19:22 +00:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
a7a4554a85 | |||
6bd808ce91 | |||
a5c143bc46 | |||
87c9cac756 | |||
6a047f8722 | |||
6523494e83 | |||
7c6ce8bb90 | |||
dafbfe4021 | |||
a4d5c94d9b | |||
7119e378a7 | |||
e1dc3032c1 | |||
5de03b8921 | |||
7631d43c48 | |||
d0b2ee5c85 | |||
8830a5a1df | |||
ee87626a93 | |||
9f15d38c1c | |||
4a96a977c0 | |||
9a95293bdf | |||
0b3a06d263 | |||
9a6249c4f5 | |||
50bd51e461 | |||
04f8013314 | |||
a0aaf0057a | |||
8e78b3e6be | |||
57a503818d | |||
25d2ff3e9b | |||
31902d3e57 | |||
16f3fa6bae | |||
1f706673cf |
@ -1,7 +1,7 @@
|
|||||||
# matterbridge
|
# matterbridge
|
||||||
Click on one of the badges below to join the chat
|
Click on one of the badges below to join the chat
|
||||||
|
|
||||||
[](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) 
|
[](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) [](https://inverse.chat) [](https://www.twitch.tv/matterbridge)
|
||||||
|
|
||||||
[](https://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
|
[](https://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
|
||||||
|
|
||||||
@ -48,13 +48,14 @@ Accounts to one of the supported bridges
|
|||||||
* [Rocket.chat](https://rocket.chat)
|
* [Rocket.chat](https://rocket.chat)
|
||||||
* [Matrix](https://matrix.org)
|
* [Matrix](https://matrix.org)
|
||||||
* [Steam](https://store.steampowered.com/)
|
* [Steam](https://store.steampowered.com/)
|
||||||
|
* [Twitch](https://twitch.tv)
|
||||||
|
|
||||||
# Screenshots
|
# Screenshots
|
||||||
See https://github.com/42wim/matterbridge/wiki
|
See https://github.com/42wim/matterbridge/wiki
|
||||||
|
|
||||||
# Installing
|
# Installing
|
||||||
## Binaries
|
## Binaries
|
||||||
* Latest stable release [v1.6.0](https://github.com/42wim/matterbridge/releases/latest)
|
* Latest stable release [v1.7.0](https://github.com/42wim/matterbridge/releases/latest)
|
||||||
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
@ -88,6 +88,7 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid
|
|||||||
bridgeConfig.Config = cfg.Api[name]
|
bridgeConfig.Config = cfg.Api[name]
|
||||||
b.Bridger = api.New(bridgeConfig)
|
b.Bridger = api.New(bridgeConfig)
|
||||||
}
|
}
|
||||||
|
b.Config = bridgeConfig.Config
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ type Protocol struct {
|
|||||||
BindAddress string // mattermost, slack // DEPRECATED
|
BindAddress string // mattermost, slack // DEPRECATED
|
||||||
Buffer int // api
|
Buffer int // api
|
||||||
Charset string // irc
|
Charset string // irc
|
||||||
|
Debug bool // general
|
||||||
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
||||||
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
||||||
IconURL string // mattermost, slack
|
IconURL string // mattermost, slack
|
||||||
|
@ -323,7 +323,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
|||||||
if event.Source.Name == b.Nick {
|
if event.Source.Name == b.Nick {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rmsg := config.Message{Username: event.Source.Name, Channel: event.Params[0], Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host}
|
rmsg := config.Message{Username: event.Source.Name, Channel: strings.ToLower(event.Params[0]), Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host}
|
||||||
flog.Debugf("handlePrivMsg() %s %s %#v", event.Source.Name, event.Trailing, event)
|
flog.Debugf("handlePrivMsg() %s %s %#v", event.Source.Name, event.Trailing, event)
|
||||||
msg := ""
|
msg := ""
|
||||||
if event.IsAction() {
|
if event.IsAction() {
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
matrix "github.com/matrix-org/gomatrix"
|
matrix "github.com/matterbridge/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bmatrix struct {
|
type Bmatrix struct {
|
||||||
@ -75,16 +75,26 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
|||||||
|
|
||||||
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||||
flog.Debugf("Receiving %#v", msg)
|
flog.Debugf("Receiving %#v", msg)
|
||||||
|
channel := b.getRoomID(msg.Channel)
|
||||||
// ignore delete messages
|
// ignore delete messages
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
return "", nil
|
if msg.ID == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
channel := b.getRoomID(msg.Channel)
|
|
||||||
flog.Debugf("Sending to channel %s", channel)
|
flog.Debugf("Sending to channel %s", channel)
|
||||||
if msg.Event == config.EVENT_USER_ACTION {
|
if msg.Event == config.EVENT_USER_ACTION {
|
||||||
b.mc.SendMessageEvent(channel, "m.room.message",
|
resp, err := b.mc.SendMessageEvent(channel, "m.room.message",
|
||||||
matrix.TextMessage{"m.emote", msg.Username + msg.Text})
|
matrix.TextMessage{"m.emote", msg.Username + msg.Text})
|
||||||
return "", nil
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
@ -124,8 +134,11 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.mc.SendText(channel, msg.Username+msg.Text)
|
resp, err := b.mc.SendText(channel, msg.Username+msg.Text)
|
||||||
return "", nil
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.EventID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) getRoomID(channel string) string {
|
func (b *Bmatrix) getRoomID(channel string) string {
|
||||||
@ -138,58 +151,11 @@ func (b *Bmatrix) getRoomID(channel string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bmatrix) handlematrix() error {
|
func (b *Bmatrix) handlematrix() error {
|
||||||
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
|
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
|
||||||
syncer.OnEventType("m.room.message", func(ev *matrix.Event) {
|
syncer.OnEventType("m.room.redaction", b.handleEvent)
|
||||||
flog.Debugf("Received: %#v", ev)
|
syncer.OnEventType("m.room.message", b.handleEvent)
|
||||||
if (ev.Content["msgtype"].(string) == "m.text" ||
|
|
||||||
ev.Content["msgtype"].(string) == "m.notice" ||
|
|
||||||
ev.Content["msgtype"].(string) == "m.emote" ||
|
|
||||||
ev.Content["msgtype"].(string) == "m.file" ||
|
|
||||||
ev.Content["msgtype"].(string) == "m.image" ||
|
|
||||||
ev.Content["msgtype"].(string) == "m.video") && ev.Sender != b.UserID {
|
|
||||||
b.RLock()
|
|
||||||
channel, ok := b.RoomMap[ev.RoomID]
|
|
||||||
b.RUnlock()
|
|
||||||
if !ok {
|
|
||||||
flog.Debugf("Unknown room %s", ev.RoomID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
username := ev.Sender[1:]
|
|
||||||
if b.Config.NoHomeServerSuffix {
|
|
||||||
re := regexp.MustCompile("(.*?):.*")
|
|
||||||
username = re.ReplaceAllString(username, `$1`)
|
|
||||||
}
|
|
||||||
rmsg := config.Message{Username: username, Text: ev.Content["body"].(string), Channel: channel, Account: b.Account, UserID: ev.Sender}
|
|
||||||
if ev.Content["msgtype"].(string) == "m.emote" {
|
|
||||||
rmsg.Event = config.EVENT_USER_ACTION
|
|
||||||
}
|
|
||||||
if ev.Content["msgtype"].(string) == "m.image" ||
|
|
||||||
ev.Content["msgtype"].(string) == "m.video" ||
|
|
||||||
ev.Content["msgtype"].(string) == "m.file" {
|
|
||||||
flog.Debugf("ev: %#v", ev)
|
|
||||||
rmsg.Extra = make(map[string][]interface{})
|
|
||||||
url := ev.Content["url"].(string)
|
|
||||||
url = strings.Replace(url, "mxc://", b.Config.Server+"/_matrix/media/v1/download/", -1)
|
|
||||||
info := ev.Content["info"].(map[string]interface{})
|
|
||||||
size := info["size"].(float64)
|
|
||||||
name := ev.Content["body"].(string)
|
|
||||||
flog.Debugf("trying to download %#v with size %#v", name, size)
|
|
||||||
if size <= float64(b.General.MediaDownloadSize) {
|
|
||||||
data, err := helper.DownloadFile(url)
|
|
||||||
if err != nil {
|
|
||||||
flog.Errorf("download %s failed %#v", url, err)
|
|
||||||
} else {
|
|
||||||
flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
|
|
||||||
rmsg.Extra["file"] = append(rmsg.Extra["file"], config.FileInfo{Name: name, Data: data})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rmsg.Text = ""
|
|
||||||
}
|
|
||||||
flog.Debugf("Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
|
||||||
b.Remote <- rmsg
|
|
||||||
}
|
|
||||||
})
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if err := b.mc.Sync(); err != nil {
|
if err := b.mc.Sync(); err != nil {
|
||||||
@ -199,3 +165,73 @@ func (b *Bmatrix) handlematrix() error {
|
|||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
||||||
|
flog.Debugf("Received: %#v", ev)
|
||||||
|
if ev.Sender != b.UserID {
|
||||||
|
b.RLock()
|
||||||
|
channel, ok := b.RoomMap[ev.RoomID]
|
||||||
|
b.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
flog.Debugf("Unknown room %s", ev.RoomID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username := ev.Sender[1:]
|
||||||
|
if b.Config.NoHomeServerSuffix {
|
||||||
|
re := regexp.MustCompile("(.*?):.*")
|
||||||
|
username = re.ReplaceAllString(username, `$1`)
|
||||||
|
}
|
||||||
|
var text string
|
||||||
|
text, _ = ev.Content["body"].(string)
|
||||||
|
rmsg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: ev.Sender}
|
||||||
|
rmsg.ID = ev.ID
|
||||||
|
if ev.Type == "m.room.redaction" {
|
||||||
|
rmsg.Event = config.EVENT_MSG_DELETE
|
||||||
|
rmsg.ID = ev.Redacts
|
||||||
|
rmsg.Text = config.EVENT_MSG_DELETE
|
||||||
|
b.Remote <- rmsg
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ev.Content["msgtype"].(string) == "m.emote" {
|
||||||
|
rmsg.Event = config.EVENT_USER_ACTION
|
||||||
|
}
|
||||||
|
if ev.Content["msgtype"] != nil && ev.Content["msgtype"].(string) == "m.image" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.video" ||
|
||||||
|
ev.Content["msgtype"].(string) == "m.file" {
|
||||||
|
flog.Debugf("ev: %#v", ev)
|
||||||
|
rmsg.Extra = make(map[string][]interface{})
|
||||||
|
url := ev.Content["url"].(string)
|
||||||
|
url = strings.Replace(url, "mxc://", b.Config.Server+"/_matrix/media/v1/download/", -1)
|
||||||
|
info := ev.Content["info"].(map[string]interface{})
|
||||||
|
size := info["size"].(float64)
|
||||||
|
name := ev.Content["body"].(string)
|
||||||
|
// check if we have an image uploaded without extension
|
||||||
|
if !strings.Contains(name, ".") {
|
||||||
|
if ev.Content["msgtype"].(string) == "m.image" {
|
||||||
|
if mtype, ok := ev.Content["mimetype"].(string); ok {
|
||||||
|
mext, _ := mime.ExtensionsByType(mtype)
|
||||||
|
if len(mext) > 0 {
|
||||||
|
name = name + mext[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// just a default .png extension if we don't have mime info
|
||||||
|
name = name + ".png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flog.Debugf("trying to download %#v with size %#v", name, size)
|
||||||
|
if size <= float64(b.General.MediaDownloadSize) {
|
||||||
|
data, err := helper.DownloadFile(url)
|
||||||
|
if err != nil {
|
||||||
|
flog.Errorf("download %s failed %#v", url, err)
|
||||||
|
} else {
|
||||||
|
flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
|
||||||
|
rmsg.Extra["file"] = append(rmsg.Extra["file"], config.FileInfo{Name: name, Data: data})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rmsg.Text = ""
|
||||||
|
}
|
||||||
|
flog.Debugf("Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
||||||
|
b.Remote <- rmsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,10 +32,7 @@ type MMMessage struct {
|
|||||||
type Bmattermost struct {
|
type Bmattermost struct {
|
||||||
MMhook
|
MMhook
|
||||||
MMapi
|
MMapi
|
||||||
Config *config.Protocol
|
TeamId string
|
||||||
Remote chan config.Message
|
|
||||||
TeamId string
|
|
||||||
Account string
|
|
||||||
*config.BridgeConfig
|
*config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/matterbridge/slack"
|
"github.com/nlopes/slack"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -107,7 +107,7 @@ func (b *Bslack) Disconnect() error {
|
|||||||
|
|
||||||
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
||||||
// we can only join channels using the API
|
// we can only join channels using the API
|
||||||
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
|
if b.sc != nil {
|
||||||
if strings.HasPrefix(b.Config.Token, "xoxb") {
|
if strings.HasPrefix(b.Config.Token, "xoxb") {
|
||||||
// TODO check if bot has already joined channel
|
// TODO check if bot has already joined channel
|
||||||
return nil
|
return nil
|
||||||
@ -309,9 +309,11 @@ func (b *Bslack) handleSlack() {
|
|||||||
|
|
||||||
func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||||
for msg := range b.rtm.IncomingEvents {
|
for msg := range b.rtm.IncomingEvents {
|
||||||
|
if msg.Type != "user_typing" && msg.Type != "latency_report" {
|
||||||
|
flog.Debugf("Receiving from slackclient %#v", msg.Data)
|
||||||
|
}
|
||||||
switch ev := msg.Data.(type) {
|
switch ev := msg.Data.(type) {
|
||||||
case *slack.MessageEvent:
|
case *slack.MessageEvent:
|
||||||
flog.Debugf("Receiving from slackclient %#v", ev)
|
|
||||||
if len(ev.Attachments) > 0 {
|
if len(ev.Attachments) > 0 {
|
||||||
// skip messages we made ourselves
|
// skip messages we made ourselves
|
||||||
if ev.Attachments[0].CallbackID == "matterbridge" {
|
if ev.Attachments[0].CallbackID == "matterbridge" {
|
||||||
@ -394,6 +396,8 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
|||||||
}
|
}
|
||||||
case *slack.InvalidAuthEvent:
|
case *slack.InvalidAuthEvent:
|
||||||
flog.Fatalf("Invalid Token %#v", ev)
|
flog.Fatalf("Invalid Token %#v", ev)
|
||||||
|
case *slack.ConnectionErrorEvent:
|
||||||
|
flog.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
|||||||
TLSConfig: tc,
|
TLSConfig: tc,
|
||||||
|
|
||||||
//StartTLS: false,
|
//StartTLS: false,
|
||||||
Debug: true,
|
Debug: b.General.Debug,
|
||||||
Session: true,
|
Session: true,
|
||||||
Status: "",
|
Status: "",
|
||||||
StatusMessage: "",
|
StatusMessage: "",
|
||||||
@ -166,7 +166,7 @@ func (b *Bxmpp) handleXmpp() error {
|
|||||||
if len(s) == 2 {
|
if len(s) == 2 {
|
||||||
nick = s[1]
|
nick = s[1]
|
||||||
}
|
}
|
||||||
if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" {
|
if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" && !strings.Contains(v.Text, "</subject>") {
|
||||||
rmsg := config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account, UserID: v.Remote}
|
rmsg := config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account, UserID: v.Remote}
|
||||||
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
||||||
if ok {
|
if ok {
|
||||||
|
30
changelog.md
30
changelog.md
@ -1,3 +1,33 @@
|
|||||||
|
# v1.7.0
|
||||||
|
## New features
|
||||||
|
* matrix: Add support for deleting messages from/to matrix (matrix). Closes #320
|
||||||
|
* xmpp: Ignore <subject> messages (xmpp). #272
|
||||||
|
* irc: Add twitch support (irc) to README / wiki
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
* general: Change RemoteNickFormat replacement order. Closes #336
|
||||||
|
* general: Make edits/delete work for bridges that gets reused. Closes #342
|
||||||
|
* general: Lowercase irc channels in config. Closes #348
|
||||||
|
* matrix: Fix possible panics (matrix). Closes #333
|
||||||
|
* matrix: Add an extension to images without one (matrix). #331
|
||||||
|
* api: Obey the Gateway value from the json (api). Closes #344
|
||||||
|
* xmpp: Print only debug messages when specified (xmpp). Closes #345
|
||||||
|
* xmpp: Allow xmpp to receive the extra messages (file uploads) when text is empty. #295
|
||||||
|
|
||||||
|
# v1.6.3
|
||||||
|
## Bugfix
|
||||||
|
* slack: Fix connection issues
|
||||||
|
* slack: Add more debug messages
|
||||||
|
* irc: Convert received IRC channel names to lowercase. Fixes #329 (#330)
|
||||||
|
|
||||||
|
# v1.6.2
|
||||||
|
## Bugfix
|
||||||
|
* mattermost: Crashes while connecting to Mattermost (regression). Closes #327
|
||||||
|
|
||||||
|
# v1.6.1
|
||||||
|
## Bugfix
|
||||||
|
* general: Display of nicks not longer working (regression). Closes #323
|
||||||
|
|
||||||
# v1.6.0
|
# v1.6.0
|
||||||
## New features
|
## New features
|
||||||
* sshchat: New protocol support added (https://github.com/shazow/ssh-chat)
|
* sshchat: New protocol support added (https://github.com/shazow/ssh-chat)
|
||||||
|
11
docker/arm/Dockerfile
Normal file
11
docker/arm/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM cmosh/alpine-arm:edge
|
||||||
|
ENTRYPOINT ["/bin/matterbridge"]
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/42wim/matterbridge
|
||||||
|
RUN apk update && apk add go git gcc musl-dev ca-certificates \
|
||||||
|
&& cd /go/src/github.com/42wim/matterbridge \
|
||||||
|
&& export GOPATH=/go \
|
||||||
|
&& go get \
|
||||||
|
&& go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge \
|
||||||
|
&& rm -rf /go \
|
||||||
|
&& apk del --purge git go gcc musl-dev
|
@ -29,8 +29,9 @@ type Gateway struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BrMsgID struct {
|
type BrMsgID struct {
|
||||||
br *bridge.Bridge
|
br *bridge.Bridge
|
||||||
ID string
|
ID string
|
||||||
|
ChannelID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg config.Gateway, r *Router) *Gateway {
|
func New(cfg config.Gateway, r *Router) *Gateway {
|
||||||
@ -93,6 +94,10 @@ func (gw *Gateway) mapChannelConfig(cfg []config.Bridge, direction string) {
|
|||||||
if isApi(br.Account) {
|
if isApi(br.Account) {
|
||||||
br.Channel = "api"
|
br.Channel = "api"
|
||||||
}
|
}
|
||||||
|
// make sure to lowercase irc channels in config #348
|
||||||
|
if strings.HasPrefix(br.Account, "irc.") {
|
||||||
|
br.Channel = strings.ToLower(br.Channel)
|
||||||
|
}
|
||||||
ID := br.Channel + br.Account
|
ID := br.Channel + br.Account
|
||||||
if _, ok := gw.Channels[ID]; !ok {
|
if _, ok := gw.Channels[ID]; !ok {
|
||||||
channel := &config.ChannelInfo{Name: br.Channel, Direction: direction, ID: ID, Options: br.Options, Account: br.Account,
|
channel := &config.ChannelInfo{Name: br.Channel, Direction: direction, ID: ID, Options: br.Options, Account: br.Account,
|
||||||
@ -118,6 +123,12 @@ func (gw *Gateway) mapChannels() error {
|
|||||||
|
|
||||||
func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []config.ChannelInfo {
|
func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []config.ChannelInfo {
|
||||||
var channels []config.ChannelInfo
|
var channels []config.ChannelInfo
|
||||||
|
|
||||||
|
// for messages received from the api check that the gateway is the specified one
|
||||||
|
if msg.Protocol == "api" && gw.Name != msg.Gateway {
|
||||||
|
return channels
|
||||||
|
}
|
||||||
|
|
||||||
// if source channel is in only, do nothing
|
// if source channel is in only, do nothing
|
||||||
for _, channel := range gw.Channels {
|
for _, channel := range gw.Channels {
|
||||||
// lookup the channel from the message
|
// lookup the channel from the message
|
||||||
@ -159,7 +170,8 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
|||||||
dest.Protocol != "slack" &&
|
dest.Protocol != "slack" &&
|
||||||
dest.Protocol != "mattermost" &&
|
dest.Protocol != "mattermost" &&
|
||||||
dest.Protocol != "telegram" &&
|
dest.Protocol != "telegram" &&
|
||||||
dest.Protocol != "matrix" {
|
dest.Protocol != "matrix" &&
|
||||||
|
dest.Protocol != "xmpp" {
|
||||||
if msg.Text == "" {
|
if msg.Text == "" {
|
||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
}
|
}
|
||||||
@ -190,7 +202,9 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
|||||||
if res, ok := gw.Messages.Get(origmsg.ID); ok {
|
if res, ok := gw.Messages.Get(origmsg.ID); ok {
|
||||||
IDs := res.([]*BrMsgID)
|
IDs := res.([]*BrMsgID)
|
||||||
for _, id := range IDs {
|
for _, id := range IDs {
|
||||||
if dest.Protocol == id.br.Protocol {
|
// check protocol, bridge name and channelname
|
||||||
|
// for people that reuse the same bridge multiple times. see #342
|
||||||
|
if dest.Protocol == id.br.Protocol && dest.Name == id.br.Name && channel.ID == id.ChannelID {
|
||||||
msg.ID = id.ID
|
msg.ID = id.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,7 +219,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
|||||||
}
|
}
|
||||||
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
|
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
|
||||||
if mID != "" {
|
if mID != "" {
|
||||||
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID})
|
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID, channel.ID})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return brMsgIDs
|
return brMsgIDs
|
||||||
@ -284,9 +298,9 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin
|
|||||||
}
|
}
|
||||||
nick = strings.Replace(nick, "{NOPINGNICK}", msg.Username[:i]+""+msg.Username[i:], -1)
|
nick = strings.Replace(nick, "{NOPINGNICK}", msg.Username[:i]+""+msg.Username[i:], -1)
|
||||||
}
|
}
|
||||||
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
|
||||||
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
||||||
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
||||||
|
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||||
return nick
|
return nick
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +332,11 @@ func (gw *Gateway) modifyMessage(msg *config.Message) {
|
|||||||
}
|
}
|
||||||
msg.Text = re.ReplaceAllString(msg.Text, replace)
|
msg.Text = re.ReplaceAllString(msg.Text, replace)
|
||||||
}
|
}
|
||||||
msg.Gateway = gw.Name
|
|
||||||
|
// messages from api have Gateway specified, don't overwrite
|
||||||
|
if msg.Protocol != "api" {
|
||||||
|
msg.Gateway = gw.Name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gw *Gateway) handleFiles(msg *config.Message) {
|
func (gw *Gateway) handleFiles(msg *config.Message) {
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "1.6.0"
|
version = "1.7.0"
|
||||||
githash string
|
githash string
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ func main() {
|
|||||||
log.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
|
log.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
|
||||||
}
|
}
|
||||||
cfg := config.NewConfig(*flagConfig)
|
cfg := config.NewConfig(*flagConfig)
|
||||||
|
cfg.General.Debug = *flagDebug
|
||||||
r, err := gateway.NewRouter(cfg)
|
r, err := gateway.NewRouter(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Starting gateway failed: %s", err)
|
log.Fatalf("Starting gateway failed: %s", err)
|
||||||
|
201
vendor/github.com/matterbridge/gomatrix/LICENSE
generated
vendored
Normal file
201
vendor/github.com/matterbridge/gomatrix/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
703
vendor/github.com/matterbridge/gomatrix/client.go
generated
vendored
Normal file
703
vendor/github.com/matterbridge/gomatrix/client.go
generated
vendored
Normal file
@ -0,0 +1,703 @@
|
|||||||
|
// Package gomatrix implements the Matrix Client-Server API.
|
||||||
|
//
|
||||||
|
// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html
|
||||||
|
package gomatrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client represents a Matrix client.
|
||||||
|
type Client struct {
|
||||||
|
HomeserverURL *url.URL // The base homeserver URL
|
||||||
|
Prefix string // The API prefix eg '/_matrix/client/r0'
|
||||||
|
UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
|
||||||
|
AccessToken string // The access_token for the client.
|
||||||
|
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
|
||||||
|
Syncer Syncer // The thing which can process /sync responses
|
||||||
|
Store Storer // The thing which can store rooms/tokens/ids
|
||||||
|
|
||||||
|
// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
|
||||||
|
// no user_id parameter will be sent.
|
||||||
|
// See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion
|
||||||
|
AppServiceUserID string
|
||||||
|
|
||||||
|
syncingMutex sync.Mutex // protects syncingID
|
||||||
|
syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time.
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
|
||||||
|
type HTTPError struct {
|
||||||
|
WrappedError error
|
||||||
|
Message string
|
||||||
|
Code int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e HTTPError) Error() string {
|
||||||
|
var wrappedErrMsg string
|
||||||
|
if e.WrappedError != nil {
|
||||||
|
wrappedErrMsg = e.WrappedError.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("msg=%s code=%d wrapped=%s", e.Message, e.Code, wrappedErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildURL builds a URL with the Client's homserver/prefix/access_token set already.
|
||||||
|
func (cli *Client) BuildURL(urlPath ...string) string {
|
||||||
|
ps := []string{cli.Prefix}
|
||||||
|
for _, p := range urlPath {
|
||||||
|
ps = append(ps, p)
|
||||||
|
}
|
||||||
|
return cli.BuildBaseURL(ps...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must
|
||||||
|
// supply the prefix in the path.
|
||||||
|
func (cli *Client) BuildBaseURL(urlPath ...string) string {
|
||||||
|
// copy the URL. Purposefully ignore error as the input is from a valid URL already
|
||||||
|
hsURL, _ := url.Parse(cli.HomeserverURL.String())
|
||||||
|
parts := []string{hsURL.Path}
|
||||||
|
parts = append(parts, urlPath...)
|
||||||
|
hsURL.Path = path.Join(parts...)
|
||||||
|
query := hsURL.Query()
|
||||||
|
if cli.AccessToken != "" {
|
||||||
|
query.Set("access_token", cli.AccessToken)
|
||||||
|
}
|
||||||
|
if cli.AppServiceUserID != "" {
|
||||||
|
query.Set("user_id", cli.AppServiceUserID)
|
||||||
|
}
|
||||||
|
hsURL.RawQuery = query.Encode()
|
||||||
|
return hsURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix/access_token set already.
|
||||||
|
func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string {
|
||||||
|
u, _ := url.Parse(cli.BuildURL(urlPath...))
|
||||||
|
q := u.Query()
|
||||||
|
for k, v := range urlQuery {
|
||||||
|
q.Set(k, v)
|
||||||
|
}
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCredentials sets the user ID and access token on this client instance.
|
||||||
|
func (cli *Client) SetCredentials(userID, accessToken string) {
|
||||||
|
cli.AccessToken = accessToken
|
||||||
|
cli.UserID = userID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCredentials removes the user ID and access token on this client instance.
|
||||||
|
func (cli *Client) ClearCredentials() {
|
||||||
|
cli.AccessToken = ""
|
||||||
|
cli.UserID = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the
|
||||||
|
// error will be nil.
|
||||||
|
//
|
||||||
|
// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine.
|
||||||
|
// Fatal sync errors can be caused by:
|
||||||
|
// - The failure to create a filter.
|
||||||
|
// - Client.Syncer.OnFailedSync returning an error in response to a failed sync.
|
||||||
|
// - Client.Syncer.ProcessResponse returning an error.
|
||||||
|
// If you wish to continue retrying in spite of these fatal errors, call Sync() again.
|
||||||
|
func (cli *Client) Sync() error {
|
||||||
|
// Mark the client as syncing.
|
||||||
|
// We will keep syncing until the syncing state changes. Either because
|
||||||
|
// Sync is called or StopSync is called.
|
||||||
|
syncingID := cli.incrementSyncingID()
|
||||||
|
nextBatch := cli.Store.LoadNextBatch(cli.UserID)
|
||||||
|
filterID := cli.Store.LoadFilterID(cli.UserID)
|
||||||
|
if filterID == "" {
|
||||||
|
filterJSON := cli.Syncer.GetFilterJSON(cli.UserID)
|
||||||
|
resFilter, err := cli.CreateFilter(filterJSON)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filterID = resFilter.FilterID
|
||||||
|
cli.Store.SaveFilterID(cli.UserID, filterID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
|
||||||
|
if err != nil {
|
||||||
|
duration, err2 := cli.Syncer.OnFailedSync(resSync, err)
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
time.Sleep(duration)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the syncing state hasn't changed
|
||||||
|
// Either because we've stopped syncing or another sync has been started.
|
||||||
|
// We discard the response from our sync.
|
||||||
|
if cli.getSyncingID() != syncingID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the token now *before* processing it. This means it's possible
|
||||||
|
// to not process some events, but it means that we won't get constantly stuck processing
|
||||||
|
// a malformed/buggy event which keeps making us panic.
|
||||||
|
cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch)
|
||||||
|
if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nextBatch = resSync.NextBatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) incrementSyncingID() uint32 {
|
||||||
|
cli.syncingMutex.Lock()
|
||||||
|
defer cli.syncingMutex.Unlock()
|
||||||
|
cli.syncingID++
|
||||||
|
return cli.syncingID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) getSyncingID() uint32 {
|
||||||
|
cli.syncingMutex.Lock()
|
||||||
|
defer cli.syncingMutex.Unlock()
|
||||||
|
return cli.syncingID
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopSync stops the ongoing sync started by Sync.
|
||||||
|
func (cli *Client) StopSync() {
|
||||||
|
// Advance the syncing state so that any running Syncs will terminate.
|
||||||
|
cli.incrementSyncingID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRequest makes a JSON HTTP request to the given URL.
|
||||||
|
// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
|
||||||
|
//
|
||||||
|
// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along
|
||||||
|
// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned
|
||||||
|
// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError.
|
||||||
|
func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
|
||||||
|
var req *http.Request
|
||||||
|
var err error
|
||||||
|
if reqBody != nil {
|
||||||
|
var jsonStr []byte
|
||||||
|
jsonStr, err = json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
|
||||||
|
} else {
|
||||||
|
req, err = http.NewRequest(method, httpURL, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
res, err := cli.Client.Do(req)
|
||||||
|
if res != nil {
|
||||||
|
defer res.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contents, err := ioutil.ReadAll(res.Body)
|
||||||
|
if res.StatusCode/100 != 2 { // not 2xx
|
||||||
|
var wrap error
|
||||||
|
var respErr RespError
|
||||||
|
if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
|
||||||
|
wrap = respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
|
||||||
|
// HTTP error instead (e.g proxy errors which return HTML).
|
||||||
|
msg := "Failed to " + method + " JSON to " + req.URL.Path
|
||||||
|
if wrap == nil {
|
||||||
|
msg = msg + ": " + string(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, HTTPError{
|
||||||
|
Code: res.StatusCode,
|
||||||
|
Message: msg,
|
||||||
|
WrappedError: wrap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody != nil {
|
||||||
|
if err = json.Unmarshal(contents, &resBody); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
|
||||||
|
func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) {
|
||||||
|
urlPath := cli.BuildURL("user", cli.UserID, "filter")
|
||||||
|
_, err = cli.MakeRequest("POST", urlPath, &filter, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
|
||||||
|
func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) {
|
||||||
|
query := map[string]string{
|
||||||
|
"timeout": strconv.Itoa(timeout),
|
||||||
|
}
|
||||||
|
if since != "" {
|
||||||
|
query["since"] = since
|
||||||
|
}
|
||||||
|
if filterID != "" {
|
||||||
|
query["filter"] = filterID
|
||||||
|
}
|
||||||
|
if setPresence != "" {
|
||||||
|
query["set_presence"] = setPresence
|
||||||
|
}
|
||||||
|
if fullState {
|
||||||
|
query["full_state"] = "true"
|
||||||
|
}
|
||||||
|
urlPath := cli.BuildURLWithQuery([]string{"sync"}, query)
|
||||||
|
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
|
||||||
|
var bodyBytes []byte
|
||||||
|
bodyBytes, err = cli.MakeRequest("POST", u, req, nil)
|
||||||
|
if err != nil {
|
||||||
|
httpErr, ok := err.(HTTPError)
|
||||||
|
if !ok { // network error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if httpErr.Code == 401 {
|
||||||
|
// body should be RespUserInteractive, if it isn't, fail with the error
|
||||||
|
err = json.Unmarshal(bodyBytes, &uiaResp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// body should be RespRegister
|
||||||
|
err = json.Unmarshal(bodyBytes, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||||
|
//
|
||||||
|
// Registers with kind=user. For kind=guest, see RegisterGuest.
|
||||||
|
func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
|
||||||
|
u := cli.BuildURL("register")
|
||||||
|
return cli.register(u, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||||
|
// with kind=guest.
|
||||||
|
//
|
||||||
|
// For kind=user, see Register.
|
||||||
|
func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
|
||||||
|
query := map[string]string{
|
||||||
|
"kind": "guest",
|
||||||
|
}
|
||||||
|
u := cli.BuildURLWithQuery([]string{"register"}, query)
|
||||||
|
return cli.register(u, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth
|
||||||
|
//
|
||||||
|
// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration
|
||||||
|
// this way. If the homeserver does not, an error is returned.
|
||||||
|
//
|
||||||
|
// This does not set credentials on the client instance. See SetCredentials() instead.
|
||||||
|
//
|
||||||
|
// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{
|
||||||
|
// Username: "alice",
|
||||||
|
// Password: "wonderland",
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// token := res.AccessToken
|
||||||
|
func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) {
|
||||||
|
res, uia, err := cli.Register(req)
|
||||||
|
if err != nil && uia == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if uia != nil && uia.HasSingleStageFlow("m.login.dummy") {
|
||||||
|
req.Auth = struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Session string `json:"session,omitempty"`
|
||||||
|
}{"m.login.dummy", uia.Session}
|
||||||
|
res, _, err = cli.Register(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?")
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||||
|
// This does not set credentials on this client instance. See SetCredentials() instead.
|
||||||
|
func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) {
|
||||||
|
urlPath := cli.BuildURL("login")
|
||||||
|
_, err = cli.MakeRequest("POST", urlPath, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
|
||||||
|
// This does not clear the credentials from the client instance. See ClearCredentials() instead.
|
||||||
|
func (cli *Client) Logout() (resp *RespLogout, err error) {
|
||||||
|
urlPath := cli.BuildURL("logout")
|
||||||
|
_, err = cli.MakeRequest("POST", urlPath, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
|
||||||
|
func (cli *Client) Versions() (resp *RespVersions, err error) {
|
||||||
|
urlPath := cli.BuildBaseURL("_matrix", "client", "versions")
|
||||||
|
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias
|
||||||
|
//
|
||||||
|
// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will
|
||||||
|
// be JSON encoded and used as the request body.
|
||||||
|
func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) {
|
||||||
|
var urlPath string
|
||||||
|
if serverName != "" {
|
||||||
|
urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{
|
||||||
|
"server_name": serverName,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
urlPath = cli.BuildURL("join", roomIDorAlias)
|
||||||
|
}
|
||||||
|
_, err = cli.MakeRequest("POST", urlPath, content, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
|
||||||
|
func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) {
|
||||||
|
urlPath := cli.BuildURL("profile", mxid, "displayname")
|
||||||
|
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
|
||||||
|
func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) {
|
||||||
|
urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
|
||||||
|
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname
|
||||||
|
func (cli *Client) SetDisplayName(displayName string) (err error) {
|
||||||
|
urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
|
||||||
|
s := struct {
|
||||||
|
DisplayName string `json:"displayname"`
|
||||||
|
}{displayName}
|
||||||
|
_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url
|
||||||
|
func (cli *Client) GetAvatarURL() (url string, err error) {
|
||||||
|
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
|
||||||
|
s := struct {
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
_, err = cli.MakeRequest("GET", urlPath, nil, &s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.AvatarURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url
|
||||||
|
func (cli *Client) SetAvatarURL(url string) (err error) {
|
||||||
|
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
|
||||||
|
s := struct {
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
}{url}
|
||||||
|
_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||||
|
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
|
||||||
|
func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) {
|
||||||
|
txnID := txnID()
|
||||||
|
urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID)
|
||||||
|
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
|
||||||
|
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
|
||||||
|
func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
|
||||||
|
urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
|
||||||
|
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendText sends an m.room.message event into the given room with a msgtype of m.text
|
||||||
|
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text
|
||||||
|
func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) {
|
||||||
|
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||||
|
TextMessage{"m.text", text})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendImage sends an m.room.message event into the given room with a msgtype of m.image
|
||||||
|
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
|
||||||
|
func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) {
|
||||||
|
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||||
|
ImageMessage{
|
||||||
|
MsgType: "m.image",
|
||||||
|
Body: body,
|
||||||
|
URL: url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendVideo sends an m.room.message event into the given room with a msgtype of m.video
|
||||||
|
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||||
|
func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) {
|
||||||
|
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||||
|
VideoMessage{
|
||||||
|
MsgType: "m.video",
|
||||||
|
Body: body,
|
||||||
|
URL: url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
|
||||||
|
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice
|
||||||
|
func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) {
|
||||||
|
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||||
|
TextMessage{"m.notice", text})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||||
|
func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) {
|
||||||
|
txnID := txnID()
|
||||||
|
urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID)
|
||||||
|
_, err = cli.MakeRequest("PUT", urlPath, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||||
|
// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||||
|
// Preset: "public_chat",
|
||||||
|
// })
|
||||||
|
// fmt.Println("Room:", resp.RoomID)
|
||||||
|
func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) {
|
||||||
|
urlPath := cli.BuildURL("createRoom")
|
||||||
|
_, err = cli.MakeRequest("POST", urlPath, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
|
||||||
|
func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "leave")
|
||||||
|
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
|
||||||
|
func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "forget")
|
||||||
|
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||||
|
func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "invite")
|
||||||
|
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint
|
||||||
|
func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "invite")
|
||||||
|
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||||
|
func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "kick")
|
||||||
|
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||||
|
func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "ban")
|
||||||
|
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||||
|
func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "unban")
|
||||||
|
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||||
|
func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) {
|
||||||
|
req := ReqTyping{Typing: typing, Timeout: timeout}
|
||||||
|
u := cli.BuildURL("rooms", roomID, "typing", cli.UserID)
|
||||||
|
_, err = cli.MakeRequest("PUT", u, req, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with
|
||||||
|
// the HTTP response body, or return an error.
|
||||||
|
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
|
||||||
|
func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
|
||||||
|
_, err = cli.MakeRequest("GET", u, nil, outContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadLink uploads an HTTP URL and then returns an MXC URI.
|
||||||
|
func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
|
||||||
|
res, err := cli.Client.Get(link)
|
||||||
|
if res != nil {
|
||||||
|
defer res.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI.
|
||||||
|
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
|
||||||
|
func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
|
||||||
|
req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
req.ContentLength = contentLength
|
||||||
|
res, err := cli.Client.Do(req)
|
||||||
|
if res != nil {
|
||||||
|
defer res.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
contents, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, HTTPError{
|
||||||
|
Message: "Upload request failed - Failed to read response body: " + err.Error(),
|
||||||
|
Code: res.StatusCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, HTTPError{
|
||||||
|
Message: "Upload request failed: " + string(contents),
|
||||||
|
Code: res.StatusCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var m RespMediaUpload
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
|
||||||
|
//
|
||||||
|
// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
|
||||||
|
// This API is primarily designed for application services which may want to efficiently look up joined members in a room.
|
||||||
|
func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) {
|
||||||
|
u := cli.BuildURL("rooms", roomID, "joined_members")
|
||||||
|
_, err = cli.MakeRequest("GET", u, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
|
||||||
|
//
|
||||||
|
// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
|
||||||
|
// This API is primarily designed for application services which may want to efficiently look up joined rooms.
|
||||||
|
func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
|
||||||
|
u := cli.BuildURL("joined_rooms")
|
||||||
|
_, err = cli.MakeRequest("GET", u, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages returns a list of message and state events for a room. It uses
|
||||||
|
// pagination query parameters to paginate history in the room.
|
||||||
|
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
|
||||||
|
func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) {
|
||||||
|
query := map[string]string{
|
||||||
|
"from": from,
|
||||||
|
"dir": string(dir),
|
||||||
|
}
|
||||||
|
if to != "" {
|
||||||
|
query["to"] = to
|
||||||
|
}
|
||||||
|
if limit != 0 {
|
||||||
|
query["limit"] = strconv.Itoa(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query)
|
||||||
|
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TurnServer returns turn server details and credentials for the client to use when initiating calls.
|
||||||
|
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver
|
||||||
|
func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
|
||||||
|
urlPath := cli.BuildURL("voip", "turnServer")
|
||||||
|
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func txnID() string {
|
||||||
|
return "go" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new Matrix Client ready for syncing
|
||||||
|
func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
|
||||||
|
hsURL, err := url.Parse(homeserverURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// By default, use an in-memory store which will never save filter ids / next batch tokens to disk.
|
||||||
|
// The client will work with this storer: it just won't remember across restarts.
|
||||||
|
// In practice, a database backend should be used.
|
||||||
|
store := NewInMemoryStore()
|
||||||
|
cli := Client{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
HomeserverURL: hsURL,
|
||||||
|
UserID: userID,
|
||||||
|
Prefix: "/_matrix/client/r0",
|
||||||
|
Syncer: NewDefaultSyncer(userID, store),
|
||||||
|
Store: store,
|
||||||
|
}
|
||||||
|
// By default, use the default HTTP client.
|
||||||
|
cli.Client = http.DefaultClient
|
||||||
|
|
||||||
|
return &cli, nil
|
||||||
|
}
|
102
vendor/github.com/matterbridge/gomatrix/events.go
generated
vendored
Normal file
102
vendor/github.com/matterbridge/gomatrix/events.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package gomatrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event represents a single Matrix event.
|
||||||
|
type Event struct {
|
||||||
|
StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
|
||||||
|
Sender string `json:"sender"` // The user ID of the sender of the event
|
||||||
|
Type string `json:"type"` // The event type
|
||||||
|
Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
|
||||||
|
ID string `json:"event_id"` // The unique ID of this event
|
||||||
|
RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
|
||||||
|
Content map[string]interface{} `json:"content"` // The JSON content of the event.
|
||||||
|
Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body returns the value of the "body" key in the event content if it is
|
||||||
|
// present and is a string.
|
||||||
|
func (event *Event) Body() (body string, ok bool) {
|
||||||
|
value, exists := event.Content["body"]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, ok = value.(string)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType returns the value of the "msgtype" key in the event content if
|
||||||
|
// it is present and is a string.
|
||||||
|
func (event *Event) MessageType() (msgtype string, ok bool) {
|
||||||
|
value, exists := event.Content["msgtype"]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msgtype, ok = value.(string)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextMessage is the contents of a Matrix formated message event.
|
||||||
|
type TextMessage struct {
|
||||||
|
MsgType string `json:"msgtype"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageInfo contains info about an image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
|
||||||
|
type ImageInfo struct {
|
||||||
|
Height uint `json:"h,omitempty"`
|
||||||
|
Width uint `json:"w,omitempty"`
|
||||||
|
Mimetype string `json:"mimetype,omitempty"`
|
||||||
|
Size uint `json:"size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoInfo contains info about a video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||||
|
type VideoInfo struct {
|
||||||
|
Mimetype string `json:"mimetype,omitempty"`
|
||||||
|
ThumbnailInfo ImageInfo `json:"thumbnail_info"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
Height uint `json:"h,omitempty"`
|
||||||
|
Width uint `json:"w,omitempty"`
|
||||||
|
Duration uint `json:"duration,omitempty"`
|
||||||
|
Size uint `json:"size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoMessage is an m.video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||||
|
type VideoMessage struct {
|
||||||
|
MsgType string `json:"msgtype"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Info VideoInfo `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageMessage is an m.image event
|
||||||
|
type ImageMessage struct {
|
||||||
|
MsgType string `json:"msgtype"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Info ImageInfo `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// An HTMLMessage is the contents of a Matrix HTML formated message event.
|
||||||
|
type HTMLMessage struct {
|
||||||
|
Body string `json:"body"`
|
||||||
|
MsgType string `json:"msgtype"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
FormattedBody string `json:"formatted_body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var htmlRegex = regexp.MustCompile("<[^<]+?>")
|
||||||
|
|
||||||
|
// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition
|
||||||
|
// to the provided HTML.
|
||||||
|
func GetHTMLMessage(msgtype, htmlText string) HTMLMessage {
|
||||||
|
return HTMLMessage{
|
||||||
|
Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")),
|
||||||
|
MsgType: msgtype,
|
||||||
|
Format: "org.matrix.custom.html",
|
||||||
|
FormattedBody: htmlText,
|
||||||
|
}
|
||||||
|
}
|
43
vendor/github.com/matterbridge/gomatrix/filter.go
generated
vendored
Normal file
43
vendor/github.com/matterbridge/gomatrix/filter.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2017 Jan Christian Grünhage
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomatrix
|
||||||
|
|
||||||
|
//Filter is used by clients to specify how the server should filter responses to e.g. sync requests
|
||||||
|
//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering
|
||||||
|
type Filter struct {
|
||||||
|
AccountData FilterPart `json:"account_data,omitempty"`
|
||||||
|
EventFields []string `json:"event_fields,omitempty"`
|
||||||
|
EventFormat string `json:"event_format,omitempty"`
|
||||||
|
Presence FilterPart `json:"presence,omitempty"`
|
||||||
|
Room struct {
|
||||||
|
AccountData FilterPart `json:"account_data,omitempty"`
|
||||||
|
Ephemeral FilterPart `json:"ephemeral,omitempty"`
|
||||||
|
IncludeLeave bool `json:"include_leave,omitempty"`
|
||||||
|
NotRooms []string `json:"not_rooms,omitempty"`
|
||||||
|
Rooms []string `json:"rooms,omitempty"`
|
||||||
|
State FilterPart `json:"state,omitempty"`
|
||||||
|
Timeline FilterPart `json:"timeline,omitempty"`
|
||||||
|
} `json:"room,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilterPart struct {
|
||||||
|
NotRooms []string `json:"not_rooms,omitempty"`
|
||||||
|
Rooms []string `json:"rooms,omitempty"`
|
||||||
|
Limit *int `json:"limit,omitempty"`
|
||||||
|
NotSenders []string `json:"not_senders,omitempty"`
|
||||||
|
NotTypes []string `json:"not_types,omitempty"`
|
||||||
|
Senders []string `json:"senders,omitempty"`
|
||||||
|
Types []string `json:"types,omitempty"`
|
||||||
|
}
|
78
vendor/github.com/matterbridge/gomatrix/requests.go
generated
vendored
Normal file
78
vendor/github.com/matterbridge/gomatrix/requests.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package gomatrix
|
||||||
|
|
||||||
|
// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||||
|
type ReqRegister struct {
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
BindEmail bool `json:"bind_email,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
DeviceID string `json:"device_id,omitempty"`
|
||||||
|
InitialDeviceDisplayName string `json:"initial_device_display_name"`
|
||||||
|
Auth interface{} `json:"auth,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||||
|
type ReqLogin struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Medium string `json:"medium,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
DeviceID string `json:"device_id,omitempty"`
|
||||||
|
InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||||
|
type ReqCreateRoom struct {
|
||||||
|
Visibility string `json:"visibility,omitempty"`
|
||||||
|
RoomAliasName string `json:"room_alias_name,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Topic string `json:"topic,omitempty"`
|
||||||
|
Invite []string `json:"invite,omitempty"`
|
||||||
|
Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
|
||||||
|
CreationContent map[string]interface{} `json:"creation_content,omitempty"`
|
||||||
|
InitialState []Event `json:"initial_state,omitempty"`
|
||||||
|
Preset string `json:"preset,omitempty"`
|
||||||
|
IsDirect bool `json:"is_direct,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||||
|
type ReqRedact struct {
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57
|
||||||
|
// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||||
|
type ReqInvite3PID struct {
|
||||||
|
IDServer string `json:"id_server"`
|
||||||
|
Medium string `json:"medium"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||||
|
type ReqInviteUser struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||||
|
type ReqKickUser struct {
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||||
|
type ReqBanUser struct {
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||||
|
type ReqUnbanUser struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||||
|
type ReqTyping struct {
|
||||||
|
Typing bool `json:"typing"`
|
||||||
|
Timeout int64 `json:"timeout"`
|
||||||
|
}
|
176
vendor/github.com/matterbridge/gomatrix/responses.go
generated
vendored
Normal file
176
vendor/github.com/matterbridge/gomatrix/responses.go
generated
vendored
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package gomatrix
|
||||||
|
|
||||||
|
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
|
||||||
|
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
|
||||||
|
type RespError struct {
|
||||||
|
ErrCode string `json:"errcode"`
|
||||||
|
Err string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the errcode and error message.
|
||||||
|
func (e RespError) Error() string {
|
||||||
|
return e.ErrCode + ": " + e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
|
||||||
|
type RespCreateFilter struct {
|
||||||
|
FilterID string `json:"filter_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
|
||||||
|
type RespVersions struct {
|
||||||
|
Versions []string `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join
|
||||||
|
type RespJoinRoom struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
|
||||||
|
type RespLeaveRoom struct{}
|
||||||
|
|
||||||
|
// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
|
||||||
|
type RespForgetRoom struct{}
|
||||||
|
|
||||||
|
// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||||
|
type RespInviteUser struct{}
|
||||||
|
|
||||||
|
// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||||
|
type RespKickUser struct{}
|
||||||
|
|
||||||
|
// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||||
|
type RespBanUser struct{}
|
||||||
|
|
||||||
|
// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||||
|
type RespUnbanUser struct{}
|
||||||
|
|
||||||
|
// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||||
|
type RespTyping struct{}
|
||||||
|
|
||||||
|
// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
|
||||||
|
type RespJoinedRooms struct {
|
||||||
|
JoinedRooms []string `json:"joined_rooms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
|
||||||
|
type RespJoinedMembers struct {
|
||||||
|
Joined map[string]struct {
|
||||||
|
DisplayName *string `json:"display_name"`
|
||||||
|
AvatarURL *string `json:"avatar_url"`
|
||||||
|
} `json:"joined"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
|
||||||
|
type RespMessages struct {
|
||||||
|
Start string `json:"start"`
|
||||||
|
Chunk []Event `json:"chunk"`
|
||||||
|
End string `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||||
|
type RespSendEvent struct {
|
||||||
|
EventID string `json:"event_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
|
||||||
|
type RespMediaUpload struct {
|
||||||
|
ContentURI string `json:"content_uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api
|
||||||
|
type RespUserInteractive struct {
|
||||||
|
Flows []struct {
|
||||||
|
Stages []string `json:"stages"`
|
||||||
|
} `json:"flows"`
|
||||||
|
Params map[string]interface{} `json:"params"`
|
||||||
|
Session string `json:"string"`
|
||||||
|
Completed []string `json:"completed"`
|
||||||
|
ErrCode string `json:"errcode"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName.
|
||||||
|
func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool {
|
||||||
|
for _, f := range r.Flows {
|
||||||
|
if len(f.Stages) == 1 && f.Stages[0] == stageName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
|
||||||
|
type RespUserDisplayName struct {
|
||||||
|
DisplayName string `json:"displayname"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||||
|
type RespRegister struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
HomeServer string `json:"home_server"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||||
|
type RespLogin struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
HomeServer string `json:"home_server"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
|
||||||
|
type RespLogout struct{}
|
||||||
|
|
||||||
|
// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||||
|
type RespCreateRoom struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
|
||||||
|
type RespSync struct {
|
||||||
|
NextBatch string `json:"next_batch"`
|
||||||
|
AccountData struct {
|
||||||
|
Events []Event `json:"events"`
|
||||||
|
} `json:"account_data"`
|
||||||
|
Presence struct {
|
||||||
|
Events []Event `json:"events"`
|
||||||
|
} `json:"presence"`
|
||||||
|
Rooms struct {
|
||||||
|
Leave map[string]struct {
|
||||||
|
State struct {
|
||||||
|
Events []Event `json:"events"`
|
||||||
|
} `json:"state"`
|
||||||
|
Timeline struct {
|
||||||
|
Events []Event `json:"events"`
|
||||||
|
Limited bool `json:"limited"`
|
||||||
|
PrevBatch string `json:"prev_batch"`
|
||||||
|
} `json:"timeline"`
|
||||||
|
} `json:"leave"`
|
||||||
|
Join map[string]struct {
|
||||||
|
State struct {
|
||||||
|
Events []Event `json:"events"`
|
||||||
|
} `json:"state"`
|
||||||
|
Timeline struct {
|
||||||
|
Events []Event `json:"events"`
|
||||||
|
Limited bool `json:"limited"`
|
||||||
|
PrevBatch string `json:"prev_batch"`
|
||||||
|
} `json:"timeline"`
|
||||||
|
} `json:"join"`
|
||||||
|
Invite map[string]struct {
|
||||||
|
State struct {
|
||||||
|
Events []Event
|
||||||
|
} `json:"invite_state"`
|
||||||
|
} `json:"invite"`
|
||||||
|
} `json:"rooms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RespTurnServer struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
URIs []string `json:"uris"`
|
||||||
|
}
|
50
vendor/github.com/matterbridge/gomatrix/room.go
generated
vendored
Normal file
50
vendor/github.com/matterbridge/gomatrix/room.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package gomatrix
|
||||||
|
|
||||||
|
// Room represents a single Matrix room.
|
||||||
|
type Room struct {
|
||||||
|
ID string
|
||||||
|
State map[string]map[string]*Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateState updates the room's current state with the given Event. This will clobber events based
|
||||||
|
// on the type/state_key combination.
|
||||||
|
func (room Room) UpdateState(event *Event) {
|
||||||
|
_, exists := room.State[event.Type]
|
||||||
|
if !exists {
|
||||||
|
room.State[event.Type] = make(map[string]*Event)
|
||||||
|
}
|
||||||
|
room.State[event.Type][*event.StateKey] = event
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
|
||||||
|
func (room Room) GetStateEvent(eventType string, stateKey string) *Event {
|
||||||
|
stateEventMap, _ := room.State[eventType]
|
||||||
|
event, _ := stateEventMap[stateKey]
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMembershipState returns the membership state of the given user ID in this room. If there is
|
||||||
|
// no entry for this member, 'leave' is returned for consistency with left users.
|
||||||
|
func (room Room) GetMembershipState(userID string) string {
|
||||||
|
state := "leave"
|
||||||
|
event := room.GetStateEvent("m.room.member", userID)
|
||||||
|
if event != nil {
|
||||||
|
membershipState, found := event.Content["membership"]
|
||||||
|
if found {
|
||||||
|
mState, isString := membershipState.(string)
|
||||||
|
if isString {
|
||||||
|
state = mState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoom creates a new Room with the given ID
|
||||||
|
func NewRoom(roomID string) *Room {
|
||||||
|
// Init the State map and return a pointer to the Room
|
||||||
|
return &Room{
|
||||||
|
ID: roomID,
|
||||||
|
State: make(map[string]map[string]*Event),
|
||||||
|
}
|
||||||
|
}
|
65
vendor/github.com/matterbridge/gomatrix/store.go
generated
vendored
Normal file
65
vendor/github.com/matterbridge/gomatrix/store.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package gomatrix
|
||||||
|
|
||||||
|
// Storer is an interface which must be satisfied to store client data.
|
||||||
|
//
|
||||||
|
// You can either write a struct which persists this data to disk, or you can use the
|
||||||
|
// provided "InMemoryStore" which just keeps data around in-memory which is lost on
|
||||||
|
// restarts.
|
||||||
|
type Storer interface {
|
||||||
|
SaveFilterID(userID, filterID string)
|
||||||
|
LoadFilterID(userID string) string
|
||||||
|
SaveNextBatch(userID, nextBatchToken string)
|
||||||
|
LoadNextBatch(userID string) string
|
||||||
|
SaveRoom(room *Room)
|
||||||
|
LoadRoom(roomID string) *Room
|
||||||
|
}
|
||||||
|
|
||||||
|
// InMemoryStore implements the Storer interface.
|
||||||
|
//
|
||||||
|
// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs
|
||||||
|
// or next batch tokens on any goroutine other than the syncing goroutine: the one
|
||||||
|
// which called Client.Sync().
|
||||||
|
type InMemoryStore struct {
|
||||||
|
Filters map[string]string
|
||||||
|
NextBatch map[string]string
|
||||||
|
Rooms map[string]*Room
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveFilterID to memory.
|
||||||
|
func (s *InMemoryStore) SaveFilterID(userID, filterID string) {
|
||||||
|
s.Filters[userID] = filterID
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFilterID from memory.
|
||||||
|
func (s *InMemoryStore) LoadFilterID(userID string) string {
|
||||||
|
return s.Filters[userID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveNextBatch to memory.
|
||||||
|
func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) {
|
||||||
|
s.NextBatch[userID] = nextBatchToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadNextBatch from memory.
|
||||||
|
func (s *InMemoryStore) LoadNextBatch(userID string) string {
|
||||||
|
return s.NextBatch[userID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveRoom to memory.
|
||||||
|
func (s *InMemoryStore) SaveRoom(room *Room) {
|
||||||
|
s.Rooms[room.ID] = room
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadRoom from memory.
|
||||||
|
func (s *InMemoryStore) LoadRoom(roomID string) *Room {
|
||||||
|
return s.Rooms[roomID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInMemoryStore constructs a new InMemoryStore.
|
||||||
|
func NewInMemoryStore() *InMemoryStore {
|
||||||
|
return &InMemoryStore{
|
||||||
|
Filters: make(map[string]string),
|
||||||
|
NextBatch: make(map[string]string),
|
||||||
|
Rooms: make(map[string]*Room),
|
||||||
|
}
|
||||||
|
}
|
164
vendor/github.com/matterbridge/gomatrix/sync.go
generated
vendored
Normal file
164
vendor/github.com/matterbridge/gomatrix/sync.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package gomatrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Syncer represents an interface that must be satisfied in order to do /sync requests on a client.
|
||||||
|
type Syncer interface {
|
||||||
|
// Process the /sync response. The since parameter is the since= value that was used to produce the response.
|
||||||
|
// This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped
|
||||||
|
// permanently.
|
||||||
|
ProcessResponse(resp *RespSync, since string) error
|
||||||
|
// OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently.
|
||||||
|
OnFailedSync(res *RespSync, err error) (time.Duration, error)
|
||||||
|
// GetFilterJSON for the given user ID. NOT the filter ID.
|
||||||
|
GetFilterJSON(userID string) json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively
|
||||||
|
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
|
||||||
|
// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information.
|
||||||
|
type DefaultSyncer struct {
|
||||||
|
UserID string
|
||||||
|
Store Storer
|
||||||
|
listeners map[string][]OnEventListener // event type to listeners array
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
|
||||||
|
type OnEventListener func(*Event)
|
||||||
|
|
||||||
|
// NewDefaultSyncer returns an instantiated DefaultSyncer
|
||||||
|
func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
|
||||||
|
return &DefaultSyncer{
|
||||||
|
UserID: userID,
|
||||||
|
Store: store,
|
||||||
|
listeners: make(map[string][]OnEventListener),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of
|
||||||
|
// unrepeating events. Returns a fatal error if a listener panics.
|
||||||
|
func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) {
|
||||||
|
if !s.shouldProcessResponse(res, since) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for roomID, roomData := range res.Rooms.Join {
|
||||||
|
room := s.getOrCreateRoom(roomID)
|
||||||
|
for _, event := range roomData.State.Events {
|
||||||
|
event.RoomID = roomID
|
||||||
|
room.UpdateState(&event)
|
||||||
|
s.notifyListeners(&event)
|
||||||
|
}
|
||||||
|
for _, event := range roomData.Timeline.Events {
|
||||||
|
event.RoomID = roomID
|
||||||
|
s.notifyListeners(&event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for roomID, roomData := range res.Rooms.Invite {
|
||||||
|
room := s.getOrCreateRoom(roomID)
|
||||||
|
for _, event := range roomData.State.Events {
|
||||||
|
event.RoomID = roomID
|
||||||
|
room.UpdateState(&event)
|
||||||
|
s.notifyListeners(&event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for roomID, roomData := range res.Rooms.Leave {
|
||||||
|
room := s.getOrCreateRoom(roomID)
|
||||||
|
for _, event := range roomData.Timeline.Events {
|
||||||
|
if event.StateKey != nil {
|
||||||
|
event.RoomID = roomID
|
||||||
|
room.UpdateState(&event)
|
||||||
|
s.notifyListeners(&event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnEventType allows callers to be notified when there are new events for the given event type.
|
||||||
|
// There are no duplicate checks.
|
||||||
|
func (s *DefaultSyncer) OnEventType(eventType string, callback OnEventListener) {
|
||||||
|
_, exists := s.listeners[eventType]
|
||||||
|
if !exists {
|
||||||
|
s.listeners[eventType] = []OnEventListener{}
|
||||||
|
}
|
||||||
|
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
|
||||||
|
// stuff that shouldn't be processed.
|
||||||
|
func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool {
|
||||||
|
if since == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// This is a horrible hack because /sync will return the most recent messages for a room
|
||||||
|
// as soon as you /join it. We do NOT want to process those events in that particular room
|
||||||
|
// because they may have already been processed (if you toggle the bot in/out of the room).
|
||||||
|
//
|
||||||
|
// Work around this by inspecting each room's timeline and seeing if an m.room.member event for us
|
||||||
|
// exists and is "join" and then discard processing that room entirely if so.
|
||||||
|
// TODO: We probably want to process messages from after the last join event in the timeline.
|
||||||
|
for roomID, roomData := range resp.Rooms.Join {
|
||||||
|
for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
|
||||||
|
e := roomData.Timeline.Events[i]
|
||||||
|
if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID {
|
||||||
|
m := e.Content["membership"]
|
||||||
|
mship, ok := m.(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mship == "join" {
|
||||||
|
_, ok := resp.Rooms.Join[roomID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(resp.Rooms.Join, roomID) // don't re-process messages
|
||||||
|
delete(resp.Rooms.Invite, roomID) // don't re-process invites
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
|
||||||
|
func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room {
|
||||||
|
room := s.Store.LoadRoom(roomID)
|
||||||
|
if room == nil { // create a new Room
|
||||||
|
room = NewRoom(roomID)
|
||||||
|
s.Store.SaveRoom(room)
|
||||||
|
}
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultSyncer) notifyListeners(event *Event) {
|
||||||
|
listeners, exists := s.listeners[event.Type]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, fn := range listeners {
|
||||||
|
fn(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
|
||||||
|
func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) {
|
||||||
|
return 10 * time.Second, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilterJSON returns a filter with a timeline limit of 50.
|
||||||
|
func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage {
|
||||||
|
return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
|
||||||
|
}
|
130
vendor/github.com/matterbridge/gomatrix/userids.go
generated
vendored
Normal file
130
vendor/github.com/matterbridge/gomatrix/userids.go
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package gomatrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const lowerhex = "0123456789abcdef"
|
||||||
|
|
||||||
|
// encode the given byte using quoted-printable encoding (e.g "=2f")
|
||||||
|
// and writes it to the buffer
|
||||||
|
// See https://golang.org/src/mime/quotedprintable/writer.go
|
||||||
|
func encode(buf *bytes.Buffer, b byte) {
|
||||||
|
buf.WriteByte('=')
|
||||||
|
buf.WriteByte(lowerhex[b>>4])
|
||||||
|
buf.WriteByte(lowerhex[b&0x0f])
|
||||||
|
}
|
||||||
|
|
||||||
|
// escape the given alpha character and writes it to the buffer
|
||||||
|
func escape(buf *bytes.Buffer, b byte) {
|
||||||
|
buf.WriteByte('_')
|
||||||
|
if b == '_' {
|
||||||
|
buf.WriteByte('_') // another _
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldEncode(b byte) bool {
|
||||||
|
return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldEscape(b byte) bool {
|
||||||
|
return (b >= 'A' && b <= 'Z') || b == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidByte(b byte) bool {
|
||||||
|
return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidEscapedChar(b byte) bool {
|
||||||
|
return b == '_' || (b >= 'a' && b <= 'z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form.
|
||||||
|
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
||||||
|
//
|
||||||
|
// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z
|
||||||
|
// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges
|
||||||
|
// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs)
|
||||||
|
// and converted to lower-case hex with a leading "=". For example:
|
||||||
|
// Alph@Bet_50up => _alph=40_bet=5f50up
|
||||||
|
func EncodeUserLocalpart(str string) string {
|
||||||
|
strBytes := []byte(str)
|
||||||
|
var outputBuffer bytes.Buffer
|
||||||
|
for _, b := range strBytes {
|
||||||
|
if shouldEncode(b) {
|
||||||
|
encode(&outputBuffer, b)
|
||||||
|
} else if shouldEscape(b) {
|
||||||
|
escape(&outputBuffer, b)
|
||||||
|
} else {
|
||||||
|
outputBuffer.WriteByte(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputBuffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeUserLocalpart decodes the given string back into the original input string.
|
||||||
|
// Returns an error if the given string is not a valid user ID localpart encoding.
|
||||||
|
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
||||||
|
//
|
||||||
|
// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For
|
||||||
|
// example:
|
||||||
|
// _alph=40_bet=5f50up => Alph@Bet_50up
|
||||||
|
// Returns an error if the input string contains characters outside the
|
||||||
|
// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has
|
||||||
|
// an invalid _ escaped byte (e.g. "_5").
|
||||||
|
func DecodeUserLocalpart(str string) (string, error) {
|
||||||
|
strBytes := []byte(str)
|
||||||
|
var outputBuffer bytes.Buffer
|
||||||
|
for i := 0; i < len(strBytes); i++ {
|
||||||
|
b := strBytes[i]
|
||||||
|
if !isValidByte(b) {
|
||||||
|
return "", fmt.Errorf("Byte pos %d: Invalid byte", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _
|
||||||
|
if i+1 >= len(strBytes) {
|
||||||
|
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i)
|
||||||
|
}
|
||||||
|
if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping
|
||||||
|
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i)
|
||||||
|
}
|
||||||
|
if strBytes[i+1] == '_' {
|
||||||
|
outputBuffer.WriteByte('_')
|
||||||
|
} else {
|
||||||
|
outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z
|
||||||
|
}
|
||||||
|
i++ // skip next byte since we just handled it
|
||||||
|
} else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8
|
||||||
|
if i+2 >= len(strBytes) {
|
||||||
|
return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i)
|
||||||
|
}
|
||||||
|
dst := make([]byte, 1)
|
||||||
|
_, err := hex.Decode(dst, strBytes[i+1:i+3])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
outputBuffer.WriteByte(dst[0])
|
||||||
|
i += 2 // skip next 2 bytes since we just handled it
|
||||||
|
} else { // pass through
|
||||||
|
outputBuffer.WriteByte(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputBuffer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractUserLocalpart extracts the localpart portion of a user ID.
|
||||||
|
// See http://matrix.org/docs/spec/intro.html#user-identifiers
|
||||||
|
func ExtractUserLocalpart(userID string) (string, error) {
|
||||||
|
if len(userID) == 0 || userID[0] != '@' {
|
||||||
|
return "", fmt.Errorf("%s is not a valid user id", userID)
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(
|
||||||
|
strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ]
|
||||||
|
"@", // remove "@" prefix
|
||||||
|
), nil
|
||||||
|
}
|
20
vendor/github.com/matterbridge/slack/websocket_utils.go
generated
vendored
20
vendor/github.com/matterbridge/slack/websocket_utils.go
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
package slack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
var portMapping = map[string]string{"ws": "80", "wss": "443"}
|
|
||||||
|
|
||||||
func websocketizeURLPort(orig string) (string, error) {
|
|
||||||
urlObj, err := url.ParseRequestURI(orig)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
_, _, err = net.SplitHostPort(urlObj.Host)
|
|
||||||
if err != nil {
|
|
||||||
return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
|
|
||||||
}
|
|
||||||
return orig, nil
|
|
||||||
}
|
|
@ -25,6 +25,7 @@ type AttachmentAction struct {
|
|||||||
SelectedOptions []AttachmentActionOption `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
|
SelectedOptions []AttachmentActionOption `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
|
||||||
OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
|
OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
|
||||||
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
|
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
|
||||||
|
URL string `json:"url,omitempty"` // Optional.
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachmentActionOption the individual option to appear in action menu.
|
// AttachmentActionOption the individual option to appear in action menu.
|
||||||
@ -48,6 +49,9 @@ type AttachmentActionCallback struct {
|
|||||||
Channel Channel `json:"channel"`
|
Channel Channel `json:"channel"`
|
||||||
User User `json:"user"`
|
User User `json:"user"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
|
||||||
OriginalMessage Message `json:"original_message"`
|
OriginalMessage Message `json:"original_message"`
|
||||||
|
|
||||||
ActionTs string `json:"action_ts"`
|
ActionTs string `json:"action_ts"`
|
@ -38,51 +38,51 @@ func channelRequest(ctx context.Context, path string, values url.Values, debug b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ArchiveChannel archives the given channel
|
// ArchiveChannel archives the given channel
|
||||||
func (api *Client) ArchiveChannel(channel string) error {
|
// see https://api.slack.com/methods/channels.archive
|
||||||
return api.ArchiveChannelContext(context.Background(), channel)
|
func (api *Client) ArchiveChannel(channelID string) error {
|
||||||
|
return api.ArchiveChannelContext(context.Background(), channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArchiveChannelContext archives the given channel with a custom context
|
// ArchiveChannelContext archives the given channel with a custom context
|
||||||
func (api *Client) ArchiveChannelContext(ctx context.Context, channel string) error {
|
// see https://api.slack.com/methods/channels.archive
|
||||||
|
func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
}
|
}
|
||||||
_, err := channelRequest(ctx, "channels.archive", values, api.debug)
|
_, err := channelRequest(ctx, "channels.archive", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnarchiveChannel unarchives the given channel
|
// UnarchiveChannel unarchives the given channel
|
||||||
func (api *Client) UnarchiveChannel(channel string) error {
|
// see https://api.slack.com/methods/channels.unarchive
|
||||||
return api.UnarchiveChannelContext(context.Background(), channel)
|
func (api *Client) UnarchiveChannel(channelID string) error {
|
||||||
|
return api.UnarchiveChannelContext(context.Background(), channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnarchiveChannelContext unarchives the given channel with a custom context
|
// UnarchiveChannelContext unarchives the given channel with a custom context
|
||||||
func (api *Client) UnarchiveChannelContext(ctx context.Context, channel string) error {
|
// see https://api.slack.com/methods/channels.unarchive
|
||||||
|
func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
}
|
}
|
||||||
_, err := channelRequest(ctx, "channels.unarchive", values, api.debug)
|
_, err := channelRequest(ctx, "channels.unarchive", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateChannel creates a channel with the given name and returns a *Channel
|
// CreateChannel creates a channel with the given name and returns a *Channel
|
||||||
func (api *Client) CreateChannel(channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.create
|
||||||
return api.CreateChannelContext(context.Background(), channel)
|
func (api *Client) CreateChannel(channelName string) (*Channel, error) {
|
||||||
|
return api.CreateChannelContext(context.Background(), channelName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context
|
// CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context
|
||||||
func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.create
|
||||||
|
func (api *Client) CreateChannelContext(ctx context.Context, channelName string) (*Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"name": {channel},
|
"name": {channelName},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.create", values, api.debug)
|
response, err := channelRequest(ctx, "channels.create", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -92,15 +92,17 @@ func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelHistory retrieves the channel history
|
// GetChannelHistory retrieves the channel history
|
||||||
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
|
// see https://api.slack.com/methods/channels.history
|
||||||
return api.GetChannelHistoryContext(context.Background(), channel, params)
|
func (api *Client) GetChannelHistory(channelID string, params HistoryParameters) (*History, error) {
|
||||||
|
return api.GetChannelHistoryContext(context.Background(), channelID, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelHistoryContext retrieves the channel history with a custom context
|
// GetChannelHistoryContext retrieves the channel history with a custom context
|
||||||
func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
|
// see https://api.slack.com/methods/channels.history
|
||||||
|
func (api *Client) GetChannelHistoryContext(ctx context.Context, channelID string, params HistoryParameters) (*History, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
}
|
}
|
||||||
if params.Latest != DEFAULT_HISTORY_LATEST {
|
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||||
values.Add("latest", params.Latest)
|
values.Add("latest", params.Latest)
|
||||||
@ -133,15 +135,17 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelInfo retrieves the given channel
|
// GetChannelInfo retrieves the given channel
|
||||||
func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.info
|
||||||
return api.GetChannelInfoContext(context.Background(), channel)
|
func (api *Client) GetChannelInfo(channelID string) (*Channel, error) {
|
||||||
|
return api.GetChannelInfoContext(context.Background(), channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelInfoContext retrieves the given channel with a custom context
|
// GetChannelInfoContext retrieves the given channel with a custom context
|
||||||
func (api *Client) GetChannelInfoContext(ctx context.Context, channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.info
|
||||||
|
func (api *Client) GetChannelInfoContext(ctx context.Context, channelID string) (*Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.info", values, api.debug)
|
response, err := channelRequest(ctx, "channels.info", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -151,15 +155,17 @@ func (api *Client) GetChannelInfoContext(ctx context.Context, channel string) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InviteUserToChannel invites a user to a given channel and returns a *Channel
|
// InviteUserToChannel invites a user to a given channel and returns a *Channel
|
||||||
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.invite
|
||||||
return api.InviteUserToChannelContext(context.Background(), channel, user)
|
func (api *Client) InviteUserToChannel(channelID, user string) (*Channel, error) {
|
||||||
|
return api.InviteUserToChannelContext(context.Background(), channelID, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InviteUserToChannelCustom invites a user to a given channel and returns a *Channel with a custom context
|
// InviteUserToChannelCustom invites a user to a given channel and returns a *Channel with a custom context
|
||||||
func (api *Client) InviteUserToChannelContext(ctx context.Context, channel, user string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.invite
|
||||||
|
func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, user string) (*Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"user": {user},
|
"user": {user},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.invite", values, api.debug)
|
response, err := channelRequest(ctx, "channels.invite", values, api.debug)
|
||||||
@ -170,15 +176,17 @@ func (api *Client) InviteUserToChannelContext(ctx context.Context, channel, user
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JoinChannel joins the currently authenticated user to a channel
|
// JoinChannel joins the currently authenticated user to a channel
|
||||||
func (api *Client) JoinChannel(channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.join
|
||||||
return api.JoinChannelContext(context.Background(), channel)
|
func (api *Client) JoinChannel(channelName string) (*Channel, error) {
|
||||||
|
return api.JoinChannelContext(context.Background(), channelName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinChannelContext joins the currently authenticated user to a channel with a custom context
|
// JoinChannelContext joins the currently authenticated user to a channel with a custom context
|
||||||
func (api *Client) JoinChannelContext(ctx context.Context, channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.join
|
||||||
|
func (api *Client) JoinChannelContext(ctx context.Context, channelName string) (*Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"name": {channel},
|
"name": {channelName},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.join", values, api.debug)
|
response, err := channelRequest(ctx, "channels.join", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -188,15 +196,17 @@ func (api *Client) JoinChannelContext(ctx context.Context, channel string) (*Cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LeaveChannel makes the authenticated user leave the given channel
|
// LeaveChannel makes the authenticated user leave the given channel
|
||||||
func (api *Client) LeaveChannel(channel string) (bool, error) {
|
// see https://api.slack.com/methods/channels.leave
|
||||||
return api.LeaveChannelContext(context.Background(), channel)
|
func (api *Client) LeaveChannel(channelID string) (bool, error) {
|
||||||
|
return api.LeaveChannelContext(context.Background(), channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeaveChannelContext makes the authenticated user leave the given channel with a custom context
|
// LeaveChannelContext makes the authenticated user leave the given channel with a custom context
|
||||||
func (api *Client) LeaveChannelContext(ctx context.Context, channel string) (bool, error) {
|
// see https://api.slack.com/methods/channels.leave
|
||||||
|
func (api *Client) LeaveChannelContext(ctx context.Context, channelID string) (bool, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.leave", values, api.debug)
|
response, err := channelRequest(ctx, "channels.leave", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -209,30 +219,31 @@ func (api *Client) LeaveChannelContext(ctx context.Context, channel string) (boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KickUserFromChannel kicks a user from a given channel
|
// KickUserFromChannel kicks a user from a given channel
|
||||||
func (api *Client) KickUserFromChannel(channel, user string) error {
|
// see https://api.slack.com/methods/channels.kick
|
||||||
return api.KickUserFromChannelContext(context.Background(), channel, user)
|
func (api *Client) KickUserFromChannel(channelID, user string) error {
|
||||||
|
return api.KickUserFromChannelContext(context.Background(), channelID, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KickUserFromChannelContext kicks a user from a given channel with a custom context
|
// KickUserFromChannelContext kicks a user from a given channel with a custom context
|
||||||
func (api *Client) KickUserFromChannelContext(ctx context.Context, channel, user string) error {
|
// see https://api.slack.com/methods/channels.kick
|
||||||
|
func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, user string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"user": {user},
|
"user": {user},
|
||||||
}
|
}
|
||||||
_, err := channelRequest(ctx, "channels.kick", values, api.debug)
|
_, err := channelRequest(ctx, "channels.kick", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannels retrieves all the channels
|
// GetChannels retrieves all the channels
|
||||||
|
// see https://api.slack.com/methods/channels.list
|
||||||
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
||||||
return api.GetChannelsContext(context.Background(), excludeArchived)
|
return api.GetChannelsContext(context.Background(), excludeArchived)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelsContext retrieves all the channels with a custom context
|
// GetChannelsContext retrieves all the channels with a custom context
|
||||||
|
// see https://api.slack.com/methods/channels.list
|
||||||
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) {
|
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
@ -252,35 +263,36 @@ func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool)
|
|||||||
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
|
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
|
||||||
// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
|
// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
|
||||||
// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
|
// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
|
||||||
func (api *Client) SetChannelReadMark(channel, ts string) error {
|
// see https://api.slack.com/methods/channels.mark
|
||||||
return api.SetChannelReadMarkContext(context.Background(), channel, ts)
|
func (api *Client) SetChannelReadMark(channelID, ts string) error {
|
||||||
|
return api.SetChannelReadMarkContext(context.Background(), channelID, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context
|
// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context
|
||||||
// For more details see SetChannelReadMark documentation
|
// For more details see SetChannelReadMark documentation
|
||||||
func (api *Client) SetChannelReadMarkContext(ctx context.Context, channel, ts string) error {
|
// see https://api.slack.com/methods/channels.mark
|
||||||
|
func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"ts": {ts},
|
"ts": {ts},
|
||||||
}
|
}
|
||||||
_, err := channelRequest(ctx, "channels.mark", values, api.debug)
|
_, err := channelRequest(ctx, "channels.mark", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameChannel renames a given channel
|
// RenameChannel renames a given channel
|
||||||
func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.rename
|
||||||
return api.RenameChannelContext(context.Background(), channel, name)
|
func (api *Client) RenameChannel(channelID, name string) (*Channel, error) {
|
||||||
|
return api.RenameChannelContext(context.Background(), channelID, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameChannelContext renames a given channel with a custom context
|
// RenameChannelContext renames a given channel with a custom context
|
||||||
func (api *Client) RenameChannelContext(ctx context.Context, channel, name string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.rename
|
||||||
|
func (api *Client) RenameChannelContext(ctx context.Context, channelID, name string) (*Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"name": {name},
|
"name": {name},
|
||||||
}
|
}
|
||||||
// XXX: the created entry in this call returns a string instead of a number
|
// XXX: the created entry in this call returns a string instead of a number
|
||||||
@ -293,15 +305,17 @@ func (api *Client) RenameChannelContext(ctx context.Context, channel, name strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set
|
// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set
|
||||||
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
|
// see https://api.slack.com/methods/channels.setPurpose
|
||||||
return api.SetChannelPurposeContext(context.Background(), channel, purpose)
|
func (api *Client) SetChannelPurpose(channelID, purpose string) (string, error) {
|
||||||
|
return api.SetChannelPurposeContext(context.Background(), channelID, purpose)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context
|
// SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context
|
||||||
func (api *Client) SetChannelPurposeContext(ctx context.Context, channel, purpose string) (string, error) {
|
// see https://api.slack.com/methods/channels.setPurpose
|
||||||
|
func (api *Client) SetChannelPurposeContext(ctx context.Context, channelID, purpose string) (string, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"purpose": {purpose},
|
"purpose": {purpose},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.setPurpose", values, api.debug)
|
response, err := channelRequest(ctx, "channels.setPurpose", values, api.debug)
|
||||||
@ -312,15 +326,17 @@ func (api *Client) SetChannelPurposeContext(ctx context.Context, channel, purpos
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetChannelTopic sets the channel topic and returns the topic that was successfully set
|
// SetChannelTopic sets the channel topic and returns the topic that was successfully set
|
||||||
func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
|
// see https://api.slack.com/methods/channels.setTopic
|
||||||
return api.SetChannelTopicContext(context.Background(), channel, topic)
|
func (api *Client) SetChannelTopic(channelID, topic string) (string, error) {
|
||||||
|
return api.SetChannelTopicContext(context.Background(), channelID, topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context
|
// SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context
|
||||||
func (api *Client) SetChannelTopicContext(ctx context.Context, channel, topic string) (string, error) {
|
// see https://api.slack.com/methods/channels.setTopic
|
||||||
|
func (api *Client) SetChannelTopicContext(ctx context.Context, channelID, topic string) (string, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"topic": {topic},
|
"topic": {topic},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.setTopic", values, api.debug)
|
response, err := channelRequest(ctx, "channels.setTopic", values, api.debug)
|
||||||
@ -331,15 +347,17 @@ func (api *Client) SetChannelTopicContext(ctx context.Context, channel, topic st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelReplies gets an entire thread (a message plus all the messages in reply to it).
|
// GetChannelReplies gets an entire thread (a message plus all the messages in reply to it).
|
||||||
func (api *Client) GetChannelReplies(channel, thread_ts string) ([]Message, error) {
|
// see https://api.slack.com/methods/channels.replies
|
||||||
return api.GetChannelRepliesContext(context.Background(), channel, thread_ts)
|
func (api *Client) GetChannelReplies(channelID, thread_ts string) ([]Message, error) {
|
||||||
|
return api.GetChannelRepliesContext(context.Background(), channelID, thread_ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context
|
// GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context
|
||||||
func (api *Client) GetChannelRepliesContext(ctx context.Context, channel, thread_ts string) ([]Message, error) {
|
// see https://api.slack.com/methods/channels.replies
|
||||||
|
func (api *Client) GetChannelRepliesContext(ctx context.Context, channelID, thread_ts string) ([]Message, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"thread_ts": {thread_ts},
|
"thread_ts": {thread_ts},
|
||||||
}
|
}
|
||||||
response, err := channelRequest(ctx, "channels.replies", values, api.debug)
|
response, err := channelRequest(ctx, "channels.replies", values, api.debug)
|
62
vendor/github.com/matterbridge/slack/chat.go → vendor/github.com/nlopes/slack/chat.go
generated
vendored
62
vendor/github.com/matterbridge/slack/chat.go → vendor/github.com/nlopes/slack/chat.go
generated
vendored
@ -11,6 +11,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
DEFAULT_MESSAGE_USERNAME = ""
|
DEFAULT_MESSAGE_USERNAME = ""
|
||||||
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
|
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
|
||||||
|
DEFAULT_MESSAGE_REPLY_BROADCAST = false
|
||||||
DEFAULT_MESSAGE_ASUSER = false
|
DEFAULT_MESSAGE_ASUSER = false
|
||||||
DEFAULT_MESSAGE_PARSE = ""
|
DEFAULT_MESSAGE_PARSE = ""
|
||||||
DEFAULT_MESSAGE_LINK_NAMES = 0
|
DEFAULT_MESSAGE_LINK_NAMES = 0
|
||||||
@ -36,6 +37,7 @@ type PostMessageParameters struct {
|
|||||||
AsUser bool `json:"as_user"`
|
AsUser bool `json:"as_user"`
|
||||||
Parse string `json:"parse"`
|
Parse string `json:"parse"`
|
||||||
ThreadTimestamp string `json:"thread_ts"`
|
ThreadTimestamp string `json:"thread_ts"`
|
||||||
|
ReplyBroadcast bool `json:"reply_broadcast"`
|
||||||
LinkNames int `json:"link_names"`
|
LinkNames int `json:"link_names"`
|
||||||
Attachments []Attachment `json:"attachments"`
|
Attachments []Attachment `json:"attachments"`
|
||||||
UnfurlLinks bool `json:"unfurl_links"`
|
UnfurlLinks bool `json:"unfurl_links"`
|
||||||
@ -44,12 +46,17 @@ type PostMessageParameters struct {
|
|||||||
IconEmoji string `json:"icon_emoji"`
|
IconEmoji string `json:"icon_emoji"`
|
||||||
Markdown bool `json:"mrkdwn,omitempty"`
|
Markdown bool `json:"mrkdwn,omitempty"`
|
||||||
EscapeText bool `json:"escape_text"`
|
EscapeText bool `json:"escape_text"`
|
||||||
|
|
||||||
|
// chat.postEphemeral support
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
User string `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
|
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
|
||||||
func NewPostMessageParameters() PostMessageParameters {
|
func NewPostMessageParameters() PostMessageParameters {
|
||||||
return PostMessageParameters{
|
return PostMessageParameters{
|
||||||
Username: DEFAULT_MESSAGE_USERNAME,
|
Username: DEFAULT_MESSAGE_USERNAME,
|
||||||
|
User: DEFAULT_MESSAGE_USERNAME,
|
||||||
AsUser: DEFAULT_MESSAGE_ASUSER,
|
AsUser: DEFAULT_MESSAGE_ASUSER,
|
||||||
Parse: DEFAULT_MESSAGE_PARSE,
|
Parse: DEFAULT_MESSAGE_PARSE,
|
||||||
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
|
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
|
||||||
@ -102,6 +109,37 @@ func (api *Client) PostMessageContext(ctx context.Context, channel, text string,
|
|||||||
return respChannel, respTimestamp, err
|
return respChannel, respTimestamp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostEphemeral sends an ephemeral message to a user in a channel.
|
||||||
|
// Message is escaped by default according to https://api.slack.com/docs/formatting
|
||||||
|
// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
|
||||||
|
func (api *Client) PostEphemeral(channel, userID string, options ...MsgOption) (string, error) {
|
||||||
|
options = append(options, MsgOptionPostEphemeral())
|
||||||
|
return api.PostEphemeralContext(
|
||||||
|
context.Background(),
|
||||||
|
channel,
|
||||||
|
userID,
|
||||||
|
options...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context
|
||||||
|
// For more details, see PostEphemeral documentation
|
||||||
|
func (api *Client) PostEphemeralContext(ctx context.Context, channel, userID string, options ...MsgOption) (string, error) {
|
||||||
|
path, values, err := ApplyMsgOptions(api.config.token, channel, options...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
values.Add("user", userID)
|
||||||
|
|
||||||
|
response, err := chatRequest(ctx, path, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Timestamp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateMessage updates a message in a channel
|
// UpdateMessage updates a message in a channel
|
||||||
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
|
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
|
||||||
return api.UpdateMessageContext(context.Background(), channel, timestamp, text)
|
return api.UpdateMessageContext(context.Background(), channel, timestamp, text)
|
||||||
@ -171,9 +209,10 @@ func chatRequest(ctx context.Context, path string, values url.Values, debug bool
|
|||||||
type sendMode string
|
type sendMode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chatUpdate sendMode = "chat.update"
|
chatUpdate sendMode = "chat.update"
|
||||||
chatPostMessage sendMode = "chat.postMessage"
|
chatPostMessage sendMode = "chat.postMessage"
|
||||||
chatDelete sendMode = "chat.delete"
|
chatDelete sendMode = "chat.delete"
|
||||||
|
chatPostEphemeral sendMode = "chat.postEphemeral"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sendConfig struct {
|
type sendConfig struct {
|
||||||
@ -193,6 +232,15 @@ func MsgOptionPost() MsgOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MsgOptionPostEphemeral posts an ephemeral message
|
||||||
|
func MsgOptionPostEphemeral() MsgOption {
|
||||||
|
return func(config *sendConfig) error {
|
||||||
|
config.mode = chatPostEphemeral
|
||||||
|
config.values.Del("ts")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MsgOptionUpdate updates a message based on the timestamp.
|
// MsgOptionUpdate updates a message based on the timestamp.
|
||||||
func MsgOptionUpdate(timestamp string) MsgOption {
|
func MsgOptionUpdate(timestamp string) MsgOption {
|
||||||
return func(config *sendConfig) error {
|
return func(config *sendConfig) error {
|
||||||
@ -279,6 +327,11 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
|||||||
config.values.Set("username", string(params.Username))
|
config.values.Set("username", string(params.Username))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chat.postEphemeral support
|
||||||
|
if params.User != DEFAULT_MESSAGE_USERNAME {
|
||||||
|
config.values.Set("user", params.User)
|
||||||
|
}
|
||||||
|
|
||||||
// never generates an error.
|
// never generates an error.
|
||||||
MsgOptionAsUser(params.AsUser)(config)
|
MsgOptionAsUser(params.AsUser)(config)
|
||||||
|
|
||||||
@ -314,6 +367,9 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
|||||||
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
|
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
|
||||||
config.values.Set("thread_ts", params.ThreadTimestamp)
|
config.values.Set("thread_ts", params.ThreadTimestamp)
|
||||||
}
|
}
|
||||||
|
if params.ReplyBroadcast != DEFAULT_MESSAGE_REPLY_BROADCAST {
|
||||||
|
config.values.Set("reply_broadcast", "true")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
0
vendor/github.com/matterbridge/slack/dnd.go → vendor/github.com/nlopes/slack/dnd.go
generated
vendored
0
vendor/github.com/matterbridge/slack/dnd.go → vendor/github.com/nlopes/slack/dnd.go
generated
vendored
21
vendor/github.com/nlopes/slack/examples/channels/channels.go
generated
vendored
Normal file
21
vendor/github.com/nlopes/slack/examples/channels/channels.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
channels, err := api.GetChannels(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, channel := range channels {
|
||||||
|
fmt.Println(channel.Name)
|
||||||
|
// channel is of type conversation & groupConversation
|
||||||
|
// see all available methods in `conversation.go`
|
||||||
|
}
|
||||||
|
}
|
30
vendor/github.com/nlopes/slack/examples/files/files.go
generated
vendored
Normal file
30
vendor/github.com/nlopes/slack/examples/files/files.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
params := slack.FileUploadParameters{
|
||||||
|
Title: "Batman Example",
|
||||||
|
//Filetype: "txt",
|
||||||
|
File: "example.txt",
|
||||||
|
//Content: "Nan Nan Nan Nan Nan Nan Nan Nan Batman",
|
||||||
|
}
|
||||||
|
file, err := api.UploadFile(params)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Name: %s, URL: %s\n", file.Name, file.URL)
|
||||||
|
|
||||||
|
err = api.DeleteFile(file.ID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("File %s deleted successfully.\n", file.Name)
|
||||||
|
}
|
22
vendor/github.com/nlopes/slack/examples/groups/groups.go
generated
vendored
Normal file
22
vendor/github.com/nlopes/slack/examples/groups/groups.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
// If you set debugging, it will log all requests to the console
|
||||||
|
// Useful when encountering issues
|
||||||
|
// api.SetDebug(true)
|
||||||
|
groups, err := api.GetGroups(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, group := range groups {
|
||||||
|
fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name)
|
||||||
|
}
|
||||||
|
}
|
21
vendor/github.com/nlopes/slack/examples/ims/ims.go
generated
vendored
Normal file
21
vendor/github.com/nlopes/slack/examples/ims/ims.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
|
||||||
|
userID := "USER_ID"
|
||||||
|
|
||||||
|
_, _, channelID, err := api.OpenIMChannel(userID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
api.PostMessage(channelID, "Hello World!", slack.PostMessageParameters{})
|
||||||
|
}
|
32
vendor/github.com/nlopes/slack/examples/messages/messages.go
generated
vendored
Normal file
32
vendor/github.com/nlopes/slack/examples/messages/messages.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
params := slack.PostMessageParameters{}
|
||||||
|
attachment := slack.Attachment{
|
||||||
|
Pretext: "some pretext",
|
||||||
|
Text: "some text",
|
||||||
|
// Uncomment the following part to send a field too
|
||||||
|
/*
|
||||||
|
Fields: []slack.AttachmentField{
|
||||||
|
slack.AttachmentField{
|
||||||
|
Title: "a",
|
||||||
|
Value: "no",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
params.Attachments = []slack.Attachment{attachment}
|
||||||
|
channelID, timestamp, err := api.PostMessage("CHANNEL_ID", "Some text", params)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Message successfully sent to channel %s at %s", channelID, timestamp)
|
||||||
|
}
|
123
vendor/github.com/nlopes/slack/examples/pins/pins.go
generated
vendored
Normal file
123
vendor/github.com/nlopes/slack/examples/pins/pins.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
WARNING: This example is destructive in the sense that it create a channel called testpinning
|
||||||
|
*/
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
apiToken string
|
||||||
|
debug bool
|
||||||
|
)
|
||||||
|
|
||||||
|
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Show JSON output")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
api := slack.New(apiToken)
|
||||||
|
if debug {
|
||||||
|
api.SetDebug(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
postAsUserName string
|
||||||
|
postAsUserID string
|
||||||
|
postToChannelID string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the user to post as.
|
||||||
|
authTest, err := api.AuthTest()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting channels: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelName := "testpinning"
|
||||||
|
|
||||||
|
// Post as the authenticated user.
|
||||||
|
postAsUserName = authTest.User
|
||||||
|
postAsUserID = authTest.UserID
|
||||||
|
|
||||||
|
// Create a temporary channel
|
||||||
|
channel, err := api.CreateChannel(channelName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// If the channel exists, that means we just need to unarchive it
|
||||||
|
if err.Error() == "name_taken" {
|
||||||
|
err = nil
|
||||||
|
channels, err := api.GetChannels(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Could not retrieve channels")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, archivedChannel := range channels {
|
||||||
|
if archivedChannel.Name == channelName {
|
||||||
|
if archivedChannel.IsArchived {
|
||||||
|
err = api.UnarchiveChannel(archivedChannel.ID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Could not unarchive %s: %s\n", archivedChannel.ID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel = &archivedChannel
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error setting test channel for pinning: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postToChannelID = channel.ID
|
||||||
|
|
||||||
|
fmt.Printf("Posting as %s (%s) in channel %s\n", postAsUserName, postAsUserID, postToChannelID)
|
||||||
|
|
||||||
|
// Post a message.
|
||||||
|
postParams := slack.PostMessageParameters{}
|
||||||
|
channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error posting message: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab a reference to the message.
|
||||||
|
msgRef := slack.NewRefToMessage(channelID, timestamp)
|
||||||
|
|
||||||
|
// Add message pin to channel
|
||||||
|
if err := api.AddPin(channelID, msgRef); err != nil {
|
||||||
|
fmt.Printf("Error adding pin: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all of the users pins.
|
||||||
|
listPins, _, err := api.ListPins(channelID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error listing pins: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("All pins by %s...\n", authTest.User)
|
||||||
|
for _, item := range listPins {
|
||||||
|
fmt.Printf(" > Item type: %s\n", item.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the pin.
|
||||||
|
err = api.RemovePin(channelID, msgRef)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error remove pin: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = api.ArchiveChannel(channelID); err != nil {
|
||||||
|
fmt.Printf("Error archiving channel: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
126
vendor/github.com/nlopes/slack/examples/reactions/reactions.go
generated
vendored
Normal file
126
vendor/github.com/nlopes/slack/examples/reactions/reactions.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
apiToken string
|
||||||
|
debug bool
|
||||||
|
)
|
||||||
|
|
||||||
|
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Show JSON output")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
api := slack.New(apiToken)
|
||||||
|
if debug {
|
||||||
|
api.SetDebug(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
postAsUserName string
|
||||||
|
postAsUserID string
|
||||||
|
postToUserName string
|
||||||
|
postToUserID string
|
||||||
|
postToChannelID string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the user to post as.
|
||||||
|
authTest, err := api.AuthTest()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting channels: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post as the authenticated user.
|
||||||
|
postAsUserName = authTest.User
|
||||||
|
postAsUserID = authTest.UserID
|
||||||
|
|
||||||
|
// Posting to DM with self causes a conversation with slackbot.
|
||||||
|
postToUserName = authTest.User
|
||||||
|
postToUserID = authTest.UserID
|
||||||
|
|
||||||
|
// Find the channel.
|
||||||
|
_, _, chanID, err := api.OpenIMChannel(postToUserID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error opening IM: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
postToChannelID = chanID
|
||||||
|
|
||||||
|
fmt.Printf("Posting as %s (%s) in DM with %s (%s), channel %s\n", postAsUserName, postAsUserID, postToUserName, postToUserID, postToChannelID)
|
||||||
|
|
||||||
|
// Post a message.
|
||||||
|
postParams := slack.PostMessageParameters{}
|
||||||
|
channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error posting message: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab a reference to the message.
|
||||||
|
msgRef := slack.NewRefToMessage(channelID, timestamp)
|
||||||
|
|
||||||
|
// React with :+1:
|
||||||
|
if err := api.AddReaction("+1", msgRef); err != nil {
|
||||||
|
fmt.Printf("Error adding reaction: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// React with :-1:
|
||||||
|
if err := api.AddReaction("cry", msgRef); err != nil {
|
||||||
|
fmt.Printf("Error adding reaction: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all reactions on the message.
|
||||||
|
msgReactions, err := api.GetReactions(msgRef, slack.NewGetReactionsParameters())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting reactions: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("%d reactions to message...\n", len(msgReactions))
|
||||||
|
for _, r := range msgReactions {
|
||||||
|
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all of the users reactions.
|
||||||
|
listReactions, _, err := api.ListReactions(slack.NewListReactionsParameters())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error listing reactions: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("All reactions by %s...\n", authTest.User)
|
||||||
|
for _, item := range listReactions {
|
||||||
|
fmt.Printf("%d on a %s...\n", len(item.Reactions), item.Type)
|
||||||
|
for _, r := range item.Reactions {
|
||||||
|
fmt.Printf(" %s (along with %d others)\n", r.Name, r.Count-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the :cry: reaction.
|
||||||
|
err = api.RemoveReaction("cry", msgRef)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error remove reaction: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all reactions on the message.
|
||||||
|
msgReactions, err = api.GetReactions(msgRef, slack.NewGetReactionsParameters())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting reactions: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("%d reactions to message after removing cry...\n", len(msgReactions))
|
||||||
|
for _, r := range msgReactions {
|
||||||
|
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
|
||||||
|
}
|
||||||
|
}
|
46
vendor/github.com/nlopes/slack/examples/stars/stars.go
generated
vendored
Normal file
46
vendor/github.com/nlopes/slack/examples/stars/stars.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
apiToken string
|
||||||
|
debug bool
|
||||||
|
)
|
||||||
|
|
||||||
|
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Show JSON output")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
api := slack.New(apiToken)
|
||||||
|
if debug {
|
||||||
|
api.SetDebug(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all stars for the usr.
|
||||||
|
params := slack.NewStarsParameters()
|
||||||
|
starredItems, _, err := api.GetStarred(params)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting stars: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, s := range starredItems {
|
||||||
|
var desc string
|
||||||
|
switch s.Type {
|
||||||
|
case slack.TYPE_MESSAGE:
|
||||||
|
desc = s.Message.Text
|
||||||
|
case slack.TYPE_FILE:
|
||||||
|
desc = s.File.Name
|
||||||
|
case slack.TYPE_FILE_COMMENT:
|
||||||
|
desc = s.File.Name + " - " + s.Comment.Comment
|
||||||
|
case slack.TYPE_CHANNEL, slack.TYPE_IM, slack.TYPE_GROUP:
|
||||||
|
desc = s.Channel
|
||||||
|
}
|
||||||
|
fmt.Printf("Starred %s: %s\n", s.Type, desc)
|
||||||
|
}
|
||||||
|
}
|
25
vendor/github.com/nlopes/slack/examples/team/team.go
generated
vendored
Normal file
25
vendor/github.com/nlopes/slack/examples/team/team.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
//Example for single user
|
||||||
|
billingActive, err := api.GetBillableInfo("U023BECGF")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("ID: U023BECGF, BillingActive: %v\n\n\n", billingActive["U023BECGF"])
|
||||||
|
|
||||||
|
//Example for team
|
||||||
|
billingActiveForTeam, _ := api.GetBillableInfoForTeam()
|
||||||
|
for id, value := range billingActiveForTeam {
|
||||||
|
fmt.Printf("ID: %v, BillingActive: %v\n", id, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
vendor/github.com/nlopes/slack/examples/users/users.go
generated
vendored
Normal file
17
vendor/github.com/nlopes/slack/examples/users/users.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
user, err := api.GetUserInfo("U023BECGF")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email)
|
||||||
|
}
|
54
vendor/github.com/nlopes/slack/examples/websocket/websocket.go
generated
vendored
Normal file
54
vendor/github.com/nlopes/slack/examples/websocket/websocket.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR TOKEN HERE")
|
||||||
|
logger := log.New(os.Stdout, "slack-bot: ", log.Lshortfile|log.LstdFlags)
|
||||||
|
slack.SetLogger(logger)
|
||||||
|
api.SetDebug(true)
|
||||||
|
|
||||||
|
rtm := api.NewRTM()
|
||||||
|
go rtm.ManageConnection()
|
||||||
|
|
||||||
|
for msg := range rtm.IncomingEvents {
|
||||||
|
fmt.Print("Event Received: ")
|
||||||
|
switch ev := msg.Data.(type) {
|
||||||
|
case *slack.HelloEvent:
|
||||||
|
// Ignore hello
|
||||||
|
|
||||||
|
case *slack.ConnectedEvent:
|
||||||
|
fmt.Println("Infos:", ev.Info)
|
||||||
|
fmt.Println("Connection counter:", ev.ConnectionCount)
|
||||||
|
// Replace C2147483705 with your Channel ID
|
||||||
|
rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "C2147483705"))
|
||||||
|
|
||||||
|
case *slack.MessageEvent:
|
||||||
|
fmt.Printf("Message: %v\n", ev)
|
||||||
|
|
||||||
|
case *slack.PresenceChangeEvent:
|
||||||
|
fmt.Printf("Presence Change: %v\n", ev)
|
||||||
|
|
||||||
|
case *slack.LatencyReport:
|
||||||
|
fmt.Printf("Current latency: %v\n", ev.Value)
|
||||||
|
|
||||||
|
case *slack.RTMError:
|
||||||
|
fmt.Printf("Error: %s\n", ev.Error())
|
||||||
|
|
||||||
|
case *slack.InvalidAuthEvent:
|
||||||
|
fmt.Printf("Invalid credentials")
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
// Ignore other events..
|
||||||
|
// fmt.Printf("Unexpected: %v\n", msg.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -267,10 +267,7 @@ func (api *Client) DeleteFileContext(ctx context.Context, fileID string) error {
|
|||||||
"file": {fileID},
|
"file": {fileID},
|
||||||
}
|
}
|
||||||
_, err := fileRequest(ctx, "files.delete", values, api.debug)
|
_, err := fileRequest(ctx, "files.delete", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -208,10 +208,7 @@ func (api *Client) LeaveGroupContext(ctx context.Context, group string) error {
|
|||||||
"channel": {group},
|
"channel": {group},
|
||||||
}
|
}
|
||||||
_, err := groupRequest(ctx, "groups.leave", values, api.debug)
|
_, err := groupRequest(ctx, "groups.leave", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// KickUserFromGroup kicks a user from a group
|
// KickUserFromGroup kicks a user from a group
|
||||||
@ -227,10 +224,7 @@ func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user str
|
|||||||
"user": {user},
|
"user": {user},
|
||||||
}
|
}
|
||||||
_, err := groupRequest(ctx, "groups.kick", values, api.debug)
|
_, err := groupRequest(ctx, "groups.kick", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroups retrieves all groups
|
// GetGroups retrieves all groups
|
||||||
@ -289,10 +283,7 @@ func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string
|
|||||||
"ts": {ts},
|
"ts": {ts},
|
||||||
}
|
}
|
||||||
_, err := groupRequest(ctx, "groups.mark", values, api.debug)
|
_, err := groupRequest(ctx, "groups.mark", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenGroup opens a private group
|
// OpenGroup opens a private group
|
0
vendor/github.com/matterbridge/slack/im.go → vendor/github.com/nlopes/slack/im.go
generated
vendored
0
vendor/github.com/matterbridge/slack/im.go → vendor/github.com/nlopes/slack/im.go
generated
vendored
@ -3,6 +3,7 @@ package slack
|
|||||||
// OutgoingMessage is used for the realtime API, and seems incomplete.
|
// OutgoingMessage is used for the realtime API, and seems incomplete.
|
||||||
type OutgoingMessage struct {
|
type OutgoingMessage struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
// channel ID
|
||||||
Channel string `json:"channel,omitempty"`
|
Channel string `json:"channel,omitempty"`
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
@ -121,12 +122,12 @@ type Pong struct {
|
|||||||
// NewOutgoingMessage prepares an OutgoingMessage that the user can
|
// NewOutgoingMessage prepares an OutgoingMessage that the user can
|
||||||
// use to send a message. Use this function to properly set the
|
// use to send a message. Use this function to properly set the
|
||||||
// messageID.
|
// messageID.
|
||||||
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
|
func (rtm *RTM) NewOutgoingMessage(text string, channelID string) *OutgoingMessage {
|
||||||
id := rtm.idGen.Next()
|
id := rtm.idGen.Next()
|
||||||
return &OutgoingMessage{
|
return &OutgoingMessage{
|
||||||
ID: id,
|
ID: id,
|
||||||
Type: "message",
|
Type: "message",
|
||||||
Channel: channel,
|
Channel: channelID,
|
||||||
Text: text,
|
Text: text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,11 +135,11 @@ func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage
|
|||||||
// NewTypingMessage prepares an OutgoingMessage that the user can
|
// NewTypingMessage prepares an OutgoingMessage that the user can
|
||||||
// use to send as a typing indicator. Use this function to properly set the
|
// use to send as a typing indicator. Use this function to properly set the
|
||||||
// messageID.
|
// messageID.
|
||||||
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
|
func (rtm *RTM) NewTypingMessage(channelID string) *OutgoingMessage {
|
||||||
id := rtm.idGen.Next()
|
id := rtm.idGen.Next()
|
||||||
return &OutgoingMessage{
|
return &OutgoingMessage{
|
||||||
ID: id,
|
ID: id,
|
||||||
Type: "typing",
|
Type: "typing",
|
||||||
Channel: channel,
|
Channel: channelID,
|
||||||
}
|
}
|
||||||
}
|
}
|
36
vendor/github.com/matterbridge/slack/misc.go → vendor/github.com/nlopes/slack/misc.go
generated
vendored
36
vendor/github.com/matterbridge/slack/misc.go → vendor/github.com/nlopes/slack/misc.go
generated
vendored
@ -13,6 +13,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -42,6 +43,14 @@ func (s WebError) Error() string {
|
|||||||
return string(s)
|
return string(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RateLimitedError struct {
|
||||||
|
RetryAfter time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RateLimitedError) Error() string {
|
||||||
|
return fmt.Sprintf("Slack rate limit exceeded, retry after %s", e.RetryAfter)
|
||||||
|
}
|
||||||
|
|
||||||
func fileUploadReq(ctx context.Context, path, fieldname, filename string, values url.Values, r io.Reader) (*http.Request, error) {
|
func fileUploadReq(ctx context.Context, path, fieldname, filename string, values url.Values, r io.Reader) (*http.Request, error) {
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
wr := multipart.NewWriter(body)
|
wr := multipart.NewWriter(body)
|
||||||
@ -79,12 +88,7 @@ func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error
|
|||||||
logger.Printf("parseResponseBody: %s\n", string(response))
|
logger.Printf("parseResponseBody: %s\n", string(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(response, &intf)
|
return json.Unmarshal(response, &intf)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func postLocalWithMultipartResponse(ctx context.Context, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error {
|
func postLocalWithMultipartResponse(ctx context.Context, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error {
|
||||||
@ -112,8 +116,16 @@ func postWithMultipartResponse(ctx context.Context, path, name, fieldname string
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusTooManyRequests {
|
||||||
|
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return &RateLimitedError{time.Duration(retry) * time.Second}
|
||||||
|
}
|
||||||
|
|
||||||
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != http.StatusOK {
|
||||||
logResponse(resp, debug)
|
logResponse(resp, debug)
|
||||||
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
||||||
}
|
}
|
||||||
@ -136,8 +148,16 @@ func postForm(ctx context.Context, endpoint string, values url.Values, intf inte
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusTooManyRequests {
|
||||||
|
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return &RateLimitedError{time.Duration(retry) * time.Second}
|
||||||
|
}
|
||||||
|
|
||||||
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != http.StatusOK {
|
||||||
logResponse(resp, debug)
|
logResponse(resp, debug)
|
||||||
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
||||||
}
|
}
|
23
vendor/github.com/matterbridge/slack/rtm.go → vendor/github.com/nlopes/slack/rtm.go
generated
vendored
23
vendor/github.com/matterbridge/slack/rtm.go → vendor/github.com/nlopes/slack/rtm.go
generated
vendored
@ -27,17 +27,8 @@ func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketUR
|
|||||||
if !response.Ok {
|
if !response.Ok {
|
||||||
return nil, "", response.Error
|
return nil, "", response.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// websocket.Dial does not accept url without the port (yet)
|
|
||||||
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
|
|
||||||
// but slack returns the address with no port, so we have to fix it
|
|
||||||
api.Debugln("Using URL:", response.Info.URL)
|
api.Debugln("Using URL:", response.Info.URL)
|
||||||
websocketURL, err = websocketizeURLPort(response.Info.URL)
|
return &response.Info, response.Info.URL, nil
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("parsing response URL: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response.Info, websocketURL, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block.
|
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block.
|
||||||
@ -59,17 +50,8 @@ func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocket
|
|||||||
if !response.Ok {
|
if !response.Ok {
|
||||||
return nil, "", response.Error
|
return nil, "", response.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// websocket.Dial does not accept url without the port (yet)
|
|
||||||
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
|
|
||||||
// but slack returns the address with no port, so we have to fix it
|
|
||||||
api.Debugln("Using URL:", response.Info.URL)
|
api.Debugln("Using URL:", response.Info.URL)
|
||||||
websocketURL, err = websocketizeURLPort(response.Info.URL)
|
return &response.Info, response.Info.URL, nil
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("parsing response URL: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response.Info, websocketURL, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTM returns a RTM, which provides a fully managed connection to
|
// NewRTM returns a RTM, which provides a fully managed connection to
|
||||||
@ -90,6 +72,7 @@ func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM {
|
|||||||
isConnected: false,
|
isConnected: false,
|
||||||
wasIntentional: true,
|
wasIntentional: true,
|
||||||
killChannel: make(chan bool),
|
killChannel: make(chan bool),
|
||||||
|
disconnected: make(chan struct{}),
|
||||||
forcePing: make(chan bool),
|
forcePing: make(chan bool),
|
||||||
rawEvents: make(chan json.RawMessage),
|
rawEvents: make(chan json.RawMessage),
|
||||||
idGen: NewSafeID(1),
|
idGen: NewSafeID(1),
|
28
vendor/github.com/matterbridge/slack/slack.go → vendor/github.com/nlopes/slack/slack.go
generated
vendored
28
vendor/github.com/matterbridge/slack/slack.go → vendor/github.com/nlopes/slack/slack.go
generated
vendored
@ -3,12 +3,13 @@ package slack
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *log.Logger // A logger that can be set by consumers
|
var logger stdLogger // A logger that can be set by consumers
|
||||||
/*
|
/*
|
||||||
Added as a var so that we can change this for testing purposes
|
Added as a var so that we can change this for testing purposes
|
||||||
*/
|
*/
|
||||||
@ -41,12 +42,31 @@ type Client struct {
|
|||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stdLogger is a logger interface compatible with both stdlib and some
|
||||||
|
// 3rd party loggers such as logrus.
|
||||||
|
type stdLogger interface {
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
Fatalln(...interface{})
|
||||||
|
|
||||||
|
Panic(...interface{})
|
||||||
|
Panicf(string, ...interface{})
|
||||||
|
Panicln(...interface{})
|
||||||
|
|
||||||
|
Output(int, string) error
|
||||||
|
}
|
||||||
|
|
||||||
// SetLogger let's library users supply a logger, so that api debugging
|
// SetLogger let's library users supply a logger, so that api debugging
|
||||||
// can be logged along with the application's debugging info.
|
// can be logged along with the application's debugging info.
|
||||||
func SetLogger(l *log.Logger) {
|
func SetLogger(l stdLogger) {
|
||||||
logger = l
|
logger = l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New creates new Client.
|
||||||
func New(token string) *Client {
|
func New(token string) *Client {
|
||||||
s := &Client{}
|
s := &Client{}
|
||||||
s.config.token = token
|
s.config.token = token
|
||||||
@ -83,12 +103,12 @@ func (api *Client) SetDebug(debug bool) {
|
|||||||
|
|
||||||
func (api *Client) Debugf(format string, v ...interface{}) {
|
func (api *Client) Debugf(format string, v ...interface{}) {
|
||||||
if api.debug {
|
if api.debug {
|
||||||
logger.Printf(format, v...)
|
logger.Output(2, fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *Client) Debugln(v ...interface{}) {
|
func (api *Client) Debugln(v ...interface{}) {
|
||||||
if api.debug {
|
if api.debug {
|
||||||
logger.Println(v...)
|
logger.Output(2, fmt.Sprintln(v...))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -200,10 +200,7 @@ func (api *Client) SetUserAsActiveContext(ctx context.Context) error {
|
|||||||
"token": {api.config.token},
|
"token": {api.config.token},
|
||||||
}
|
}
|
||||||
_, err := userRequest(ctx, "users.setActive", values, api.debug)
|
_, err := userRequest(ctx, "users.setActive", values, api.debug)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserPresence changes the currently authenticated user presence
|
// SetUserPresence changes the currently authenticated user presence
|
||||||
@ -247,8 +244,8 @@ func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetUserPhoto changes the currently authenticated user's profile image
|
// SetUserPhoto changes the currently authenticated user's profile image
|
||||||
func (api *Client) SetUserPhoto(ctx context.Context, image string, params UserSetPhotoParams) error {
|
func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
|
||||||
return api.SetUserPhoto(context.Background(), image, params)
|
return api.SetUserPhotoContext(context.Background(), image, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
|
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
|
@ -27,6 +27,7 @@ type RTM struct {
|
|||||||
IncomingEvents chan RTMEvent
|
IncomingEvents chan RTMEvent
|
||||||
outgoingMessages chan OutgoingMessage
|
outgoingMessages chan OutgoingMessage
|
||||||
killChannel chan bool
|
killChannel chan bool
|
||||||
|
disconnected chan struct{} // disconnected is closed when Disconnect is invoked, regardless of connection state. Allows for ManagedConnection to not leak.
|
||||||
forcePing chan bool
|
forcePing chan bool
|
||||||
rawEvents chan json.RawMessage
|
rawEvents chan json.RawMessage
|
||||||
wasIntentional bool
|
wasIntentional bool
|
||||||
@ -59,9 +60,14 @@ type RTMOptions struct {
|
|||||||
|
|
||||||
// Disconnect and wait, blocking until a successful disconnection.
|
// Disconnect and wait, blocking until a successful disconnection.
|
||||||
func (rtm *RTM) Disconnect() error {
|
func (rtm *RTM) Disconnect() error {
|
||||||
|
// this channel is always closed on disconnect. lets the ManagedConnection() function
|
||||||
|
// properly clean up.
|
||||||
|
close(rtm.disconnected)
|
||||||
|
|
||||||
if !rtm.isConnected {
|
if !rtm.isConnected {
|
||||||
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
|
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
|
||||||
}
|
}
|
||||||
|
|
||||||
rtm.killChannel <- true
|
rtm.killChannel <- true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@ -99,6 +99,15 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
|
|||||||
Attempt: boff.attempts,
|
Attempt: boff.attempts,
|
||||||
ErrorObj: err,
|
ErrorObj: err,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
// check if Disconnect() has been invoked.
|
||||||
|
select {
|
||||||
|
case _ = <-rtm.disconnected:
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: true}}
|
||||||
|
return nil, nil, fmt.Errorf("disconnect received while trying to connect")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
// get time we should wait before attempting to connect again
|
// get time we should wait before attempting to connect again
|
||||||
dur := boff.Duration()
|
dur := boff.Duration()
|
||||||
rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err)
|
rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err)
|
||||||
@ -124,7 +133,8 @@ func (rtm *RTM) startRTMAndDial(useRTMStart bool) (*Info, *websocket.Conn, error
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := websocketProxyDial(url, "http://api.slack.com")
|
// Only use HTTPS for connections to prevent MITM attacks on the connection.
|
||||||
|
conn, err := websocketProxyDial(url, "https://api.slack.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -317,10 +327,13 @@ func (rtm *RTM) handleAck(event json.RawMessage) {
|
|||||||
rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
|
rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ack.Ok {
|
if ack.Ok {
|
||||||
rtm.IncomingEvents <- RTMEvent{"ack", ack}
|
rtm.IncomingEvents <- RTMEvent{"ack", ack}
|
||||||
} else {
|
} else if ack.RTMResponse.Error != nil {
|
||||||
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
|
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
|
||||||
|
} else {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,8 +32,7 @@ func websocketHTTPConnect(proxy, urlString string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cc := httputil.NewProxyClientConn(p, nil)
|
cc := httputil.NewProxyClientConn(p, nil)
|
||||||
cc.Do(&req)
|
if _, err := cc.Do(&req); err != nil {
|
||||||
if err != nil && err != httputil.ErrPersistEOF {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
18
vendor/manifest
vendored
18
vendor/manifest
vendored
@ -295,11 +295,11 @@
|
|||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matterbridge/slack",
|
"importpath": "github.com/matterbridge/gomatrix",
|
||||||
"repository": "https://github.com/matterbridge/slack",
|
"repository": "https://github.com/matterbridge/gomatrix",
|
||||||
"vcs": "git",
|
"vcs": "git",
|
||||||
"revision": "1c6e6305bf9c07fc603c9cf28f09ab0517a03120",
|
"revision": "78ac6a1a0f5fb9eb1684d85a3ed581a742fe4c79",
|
||||||
"branch": "matterbridge",
|
"branch": "work",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -441,6 +441,14 @@
|
|||||||
"path": "/i18n",
|
"path": "/i18n",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "github.com/nlopes/slack",
|
||||||
|
"repository": "https://github.com/nlopes/slack",
|
||||||
|
"vcs": "git",
|
||||||
|
"revision": "107290b5bbaf3e634833346bb4ff389b1c782bc7",
|
||||||
|
"branch": "master",
|
||||||
|
"notests": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/paulrosania/go-charset",
|
"importpath": "github.com/paulrosania/go-charset",
|
||||||
"repository": "https://github.com/paulrosania/go-charset",
|
"repository": "https://github.com/paulrosania/go-charset",
|
||||||
@ -712,7 +720,7 @@
|
|||||||
"importpath": "golang.org/x/net/websocket",
|
"importpath": "golang.org/x/net/websocket",
|
||||||
"repository": "https://go.googlesource.com/net",
|
"repository": "https://go.googlesource.com/net",
|
||||||
"vcs": "git",
|
"vcs": "git",
|
||||||
"revision": "6c23252515492caf9b228a9d5cabcdbde29f7f82",
|
"revision": "434ec0c7fe3742c984919a691b2018a6e9694425",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"path": "/websocket",
|
"path": "/websocket",
|
||||||
"notests": true
|
"notests": true
|
||||||
|
Reference in New Issue
Block a user