mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-26 17:49:23 +00:00
Compare commits
78 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 | |||
fac5f69ad2 | |||
97c944bb63 | |||
d0c4fe78ee | |||
265457b451 | |||
4a4a29c9f6 | |||
0a91b9e1c9 | |||
f56163295c | |||
d1c87c068b | |||
fa20761110 | |||
e4a0e0a0e9 | |||
d30ae19e2a | |||
5c919e6bff | |||
434393d1c3 | |||
af9aa5d7cb | |||
05eb75442a | |||
3496ed0c7e | |||
1b89604c7a | |||
67a9d133e9 | |||
ed9118b346 | |||
59e55cfbd5 | |||
788d3b32ac | |||
1d414cf2fd | |||
cc3c168162 | |||
1ee6837f0e | |||
27dcea7c5b | |||
dcda7f7b8c | |||
e0cbb69a4f | |||
7ec95f786d | |||
1efe40add5 | |||
cbd73ee313 | |||
34227a7a39 | |||
71cb9b2d1d | |||
cd4c9b194f | |||
98762a0235 | |||
2fd1fd9573 | |||
aff3964078 | |||
2778580397 | |||
962062fe44 | |||
0578b21270 | |||
36a800c3f5 | |||
6d21f84187 | |||
f1e9833310 | |||
46f5acc4f9 | |||
95d4dcaeb3 | |||
64c542e614 | |||
13d081ea80 | |||
c0f9d86287 | |||
bcdecdaa73 |
@ -1,7 +1,7 @@
|
||||
# matterbridge
|
||||
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)
|
||||
|
||||
@ -48,13 +48,14 @@ Accounts to one of the supported bridges
|
||||
* [Rocket.chat](https://rocket.chat)
|
||||
* [Matrix](https://matrix.org)
|
||||
* [Steam](https://store.steampowered.com/)
|
||||
* [Twitch](https://twitch.tv)
|
||||
|
||||
# Screenshots
|
||||
See https://github.com/42wim/matterbridge/wiki
|
||||
|
||||
# Installing
|
||||
## Binaries
|
||||
* Latest stable release [v1.4.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/)
|
||||
|
||||
## Building
|
||||
@ -175,7 +176,7 @@ Matterbridge wouldn't exist without these libraries:
|
||||
* echo - https://github.com/labstack/echo
|
||||
* gitter - https://github.com/sromku/go-gitter
|
||||
* gops - https://github.com/google/gops
|
||||
* irc - https://github.com/thoj/go-ircevent
|
||||
* irc - https://github.com/lrstanley/girc
|
||||
* mattermost - https://github.com/mattermost/platform
|
||||
* matrix - https://github.com/matrix-org/gomatrix
|
||||
* slack - https://github.com/nlopes/slack
|
||||
|
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/labstack/echo"
|
||||
@ -8,14 +9,13 @@ import (
|
||||
"github.com/zfjagann/golang-ring"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Api struct {
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
Messages ring.Ring
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
type ApiMessage struct {
|
||||
@ -33,23 +33,21 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Api {
|
||||
b := &Api{}
|
||||
func New(cfg *config.BridgeConfig) *Api {
|
||||
b := &Api{BridgeConfig: cfg}
|
||||
e := echo.New()
|
||||
b.Messages = ring.Ring{}
|
||||
b.Messages.SetCapacity(cfg.Buffer)
|
||||
b.Config = &cfg
|
||||
b.Account = account
|
||||
b.Remote = c
|
||||
b.Messages.SetCapacity(b.Config.Buffer)
|
||||
if b.Config.Token != "" {
|
||||
e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
|
||||
return key == b.Config.Token, nil
|
||||
}))
|
||||
}
|
||||
e.GET("/api/messages", b.handleMessages)
|
||||
e.GET("/api/stream", b.handleStream)
|
||||
e.POST("/api/message", b.handlePostMessage)
|
||||
go func() {
|
||||
flog.Fatal(e.Start(cfg.BindAddress))
|
||||
flog.Fatal(e.Start(b.Config.BindAddress))
|
||||
}()
|
||||
return b
|
||||
}
|
||||
@ -103,3 +101,24 @@ func (b *Api) handleMessages(c echo.Context) error {
|
||||
b.Messages = ring.Ring{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Api) handleStream(c echo.Context) error {
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
c.Response().WriteHeader(http.StatusOK)
|
||||
closeNotifier := c.Response().CloseNotify()
|
||||
for {
|
||||
select {
|
||||
case <-closeNotifier:
|
||||
return nil
|
||||
default:
|
||||
msg := b.Messages.Dequeue()
|
||||
if msg != nil {
|
||||
if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Response().Flush()
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/42wim/matterbridge/bridge/mattermost"
|
||||
"github.com/42wim/matterbridge/bridge/rocketchat"
|
||||
"github.com/42wim/matterbridge/bridge/slack"
|
||||
"github.com/42wim/matterbridge/bridge/sshchat"
|
||||
"github.com/42wim/matterbridge/bridge/steam"
|
||||
"github.com/42wim/matterbridge/bridge/telegram"
|
||||
"github.com/42wim/matterbridge/bridge/xmpp"
|
||||
@ -45,44 +46,49 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid
|
||||
b.Protocol = protocol
|
||||
b.Account = bridge.Account
|
||||
b.Joined = make(map[string]bool)
|
||||
bridgeConfig := &config.BridgeConfig{General: &cfg.General, Account: bridge.Account, Remote: c}
|
||||
|
||||
// override config from environment
|
||||
config.OverrideCfgFromEnv(cfg, protocol, name)
|
||||
switch protocol {
|
||||
case "mattermost":
|
||||
b.Config = cfg.Mattermost[name]
|
||||
b.Bridger = bmattermost.New(cfg.Mattermost[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Mattermost[name]
|
||||
b.Bridger = bmattermost.New(bridgeConfig)
|
||||
case "irc":
|
||||
b.Config = cfg.IRC[name]
|
||||
b.Bridger = birc.New(cfg.IRC[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.IRC[name]
|
||||
b.Bridger = birc.New(bridgeConfig)
|
||||
case "gitter":
|
||||
b.Config = cfg.Gitter[name]
|
||||
b.Bridger = bgitter.New(cfg.Gitter[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Gitter[name]
|
||||
b.Bridger = bgitter.New(bridgeConfig)
|
||||
case "slack":
|
||||
b.Config = cfg.Slack[name]
|
||||
b.Bridger = bslack.New(cfg.Slack[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Slack[name]
|
||||
b.Bridger = bslack.New(bridgeConfig)
|
||||
case "xmpp":
|
||||
b.Config = cfg.Xmpp[name]
|
||||
b.Bridger = bxmpp.New(cfg.Xmpp[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Xmpp[name]
|
||||
b.Bridger = bxmpp.New(bridgeConfig)
|
||||
case "discord":
|
||||
b.Config = cfg.Discord[name]
|
||||
b.Bridger = bdiscord.New(cfg.Discord[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Discord[name]
|
||||
b.Bridger = bdiscord.New(bridgeConfig)
|
||||
case "telegram":
|
||||
b.Config = cfg.Telegram[name]
|
||||
b.Bridger = btelegram.New(cfg.Telegram[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Telegram[name]
|
||||
b.Bridger = btelegram.New(bridgeConfig)
|
||||
case "rocketchat":
|
||||
b.Config = cfg.Rocketchat[name]
|
||||
b.Bridger = brocketchat.New(cfg.Rocketchat[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Rocketchat[name]
|
||||
b.Bridger = brocketchat.New(bridgeConfig)
|
||||
case "matrix":
|
||||
b.Config = cfg.Matrix[name]
|
||||
b.Bridger = bmatrix.New(cfg.Matrix[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Matrix[name]
|
||||
b.Bridger = bmatrix.New(bridgeConfig)
|
||||
case "steam":
|
||||
b.Config = cfg.Steam[name]
|
||||
b.Bridger = bsteam.New(cfg.Steam[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Steam[name]
|
||||
b.Bridger = bsteam.New(bridgeConfig)
|
||||
case "sshchat":
|
||||
bridgeConfig.Config = cfg.Sshchat[name]
|
||||
b.Bridger = bsshchat.New(bridgeConfig)
|
||||
case "api":
|
||||
b.Config = cfg.Api[name]
|
||||
b.Bridger = api.New(cfg.Api[name], bridge.Account, c)
|
||||
bridgeConfig.Config = cfg.Api[name]
|
||||
b.Bridger = api.New(bridgeConfig)
|
||||
}
|
||||
b.Config = bridgeConfig.Config
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,10 @@ type Message struct {
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
Name string
|
||||
Data *[]byte
|
||||
Name string
|
||||
Data *[]byte
|
||||
Comment string
|
||||
URL string
|
||||
}
|
||||
|
||||
type ChannelInfo struct {
|
||||
@ -51,6 +53,7 @@ type Protocol struct {
|
||||
BindAddress string // mattermost, slack // DEPRECATED
|
||||
Buffer int // api
|
||||
Charset string // irc
|
||||
Debug bool // general
|
||||
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
||||
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
||||
IconURL string // mattermost, slack
|
||||
@ -58,41 +61,48 @@ type Protocol struct {
|
||||
IgnoreMessages string // all protocols
|
||||
Jid string // xmpp
|
||||
Login string // mattermost, matrix
|
||||
Muc string // xmpp
|
||||
Name string // all protocols
|
||||
Nick string // all protocols
|
||||
NickFormatter string // mattermost, slack
|
||||
NickServNick string // IRC
|
||||
NickServUsername string // IRC
|
||||
NickServPassword string // IRC
|
||||
NicksPerRow int // mattermost, slack
|
||||
NoHomeServerSuffix bool // matrix
|
||||
NoTLS bool // mattermost
|
||||
Password string // IRC,mattermost,XMPP,matrix
|
||||
PrefixMessagesWithNick bool // mattemost, slack
|
||||
Protocol string //all protocols
|
||||
MessageQueue int // IRC, size of message queue for flood control
|
||||
MessageDelay int // IRC, time in millisecond to wait between messages
|
||||
MessageLength int // IRC, max length of a message allowed
|
||||
MessageFormat string // telegram
|
||||
RemoteNickFormat string // all protocols
|
||||
Server string // IRC,mattermost,XMPP,discord
|
||||
ShowJoinPart bool // all protocols
|
||||
ShowEmbeds bool // discord
|
||||
SkipTLSVerify bool // IRC, mattermost
|
||||
StripNick bool // all protocols
|
||||
Team string // mattermost
|
||||
Token string // gitter, slack, discord, api
|
||||
URL string // mattermost, slack // DEPRECATED
|
||||
UseAPI bool // mattermost, slack
|
||||
UseSASL bool // IRC
|
||||
UseTLS bool // IRC
|
||||
UseFirstName bool // telegram
|
||||
UseUserName bool // discord
|
||||
UseInsecureURL bool // telegram
|
||||
WebhookBindAddress string // mattermost, slack
|
||||
WebhookURL string // mattermost, slack
|
||||
WebhookUse string // mattermost, slack, discord
|
||||
MediaDownloadSize int // all protocols
|
||||
MediaServerDownload string
|
||||
MediaServerUpload string
|
||||
MessageDelay int // IRC, time in millisecond to wait between messages
|
||||
MessageFormat string // telegram
|
||||
MessageLength int // IRC, max length of a message allowed
|
||||
MessageQueue int // IRC, size of message queue for flood control
|
||||
MessageSplit bool // IRC, split long messages with newlines on MessageLength instead of clipping
|
||||
Muc string // xmpp
|
||||
Name string // all protocols
|
||||
Nick string // all protocols
|
||||
NickFormatter string // mattermost, slack
|
||||
NickServNick string // IRC
|
||||
NickServUsername string // IRC
|
||||
NickServPassword string // IRC
|
||||
NicksPerRow int // mattermost, slack
|
||||
NoHomeServerSuffix bool // matrix
|
||||
NoTLS bool // mattermost
|
||||
Password string // IRC,mattermost,XMPP,matrix
|
||||
PrefixMessagesWithNick bool // mattemost, slack
|
||||
Protocol string // all protocols
|
||||
RejoinDelay int // IRC
|
||||
ReplaceMessages [][]string // all protocols
|
||||
ReplaceNicks [][]string // all protocols
|
||||
RemoteNickFormat string // all protocols
|
||||
Server string // IRC,mattermost,XMPP,discord
|
||||
ShowJoinPart bool // all protocols
|
||||
ShowEmbeds bool // discord
|
||||
SkipTLSVerify bool // IRC, mattermost
|
||||
StripNick bool // all protocols
|
||||
Team string // mattermost
|
||||
Token string // gitter, slack, discord, api
|
||||
URL string // mattermost, slack // DEPRECATED
|
||||
UseAPI bool // mattermost, slack
|
||||
UseSASL bool // IRC
|
||||
UseTLS bool // IRC
|
||||
UseFirstName bool // telegram
|
||||
UseUserName bool // discord
|
||||
UseInsecureURL bool // telegram
|
||||
WebhookBindAddress string // mattermost, slack
|
||||
WebhookURL string // mattermost, slack
|
||||
WebhookUse string // mattermost, slack, discord
|
||||
}
|
||||
|
||||
type ChannelOptions struct {
|
||||
@ -134,11 +144,19 @@ type Config struct {
|
||||
Discord map[string]Protocol
|
||||
Telegram map[string]Protocol
|
||||
Rocketchat map[string]Protocol
|
||||
Sshchat map[string]Protocol
|
||||
General Protocol
|
||||
Gateway []Gateway
|
||||
SameChannelGateway []SameChannelGateway
|
||||
}
|
||||
|
||||
type BridgeConfig struct {
|
||||
Config Protocol
|
||||
General *Protocol
|
||||
Account string
|
||||
Remote chan Message
|
||||
}
|
||||
|
||||
func NewConfig(cfgfile string) *Config {
|
||||
var cfg Config
|
||||
if _, err := toml.DecodeFile(cfgfile, &cfg); err != nil {
|
||||
@ -166,6 +184,9 @@ func NewConfig(cfgfile string) *Config {
|
||||
if fail {
|
||||
log.Fatalf("Fix your config. Please see changelog for more information")
|
||||
}
|
||||
if cfg.General.MediaDownloadSize == 0 {
|
||||
cfg.General.MediaDownloadSize = 1000000
|
||||
}
|
||||
return &cfg
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,6 @@ import (
|
||||
|
||||
type bdiscord struct {
|
||||
c *discordgo.Session
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
Channels []*discordgo.Channel
|
||||
Nick string
|
||||
UseChannelID bool
|
||||
@ -24,6 +21,7 @@ type bdiscord struct {
|
||||
webhookToken string
|
||||
channelInfoMap map[string]*config.ChannelInfo
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -33,11 +31,8 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *bdiscord {
|
||||
b := &bdiscord{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
func New(cfg *config.BridgeConfig) *bdiscord {
|
||||
b := &bdiscord{BridgeConfig: cfg}
|
||||
b.userMemberMap = make(map[string]*discordgo.Member)
|
||||
b.channelInfoMap = make(map[string]*config.ChannelInfo)
|
||||
if b.Config.WebhookURL != "" {
|
||||
@ -151,11 +146,12 @@ func (b *bdiscord) Send(msg config.Message) (string, error) {
|
||||
fi := f.(config.FileInfo)
|
||||
files := []*discordgo.File{}
|
||||
files = append(files, &discordgo.File{fi.Name, "", bytes.NewReader(*fi.Data)})
|
||||
_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Text, Files: files})
|
||||
_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Username + fi.Comment, Files: files})
|
||||
if err != nil {
|
||||
flog.Errorf("file upload failed: %#v", err)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,11 @@ import (
|
||||
)
|
||||
|
||||
type Bgitter struct {
|
||||
c *gitter.Gitter
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
User *gitter.User
|
||||
Users []gitter.User
|
||||
Rooms []gitter.Room
|
||||
c *gitter.Gitter
|
||||
User *gitter.User
|
||||
Users []gitter.User
|
||||
Rooms []gitter.Room
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -25,12 +23,8 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bgitter {
|
||||
b := &Bgitter{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
return b
|
||||
func New(cfg *config.BridgeConfig) *Bgitter {
|
||||
return &Bgitter{BridgeConfig: cfg}
|
||||
}
|
||||
|
||||
func (b *Bgitter) Connect() error {
|
||||
@ -125,6 +119,23 @@ func (b *Bgitter) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if msg.Extra != nil {
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
}
|
||||
_, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -26,3 +26,15 @@ func DownloadFile(url string) (*[]byte, error) {
|
||||
resp.Body.Close()
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func SplitStringLength(input string, length int) string {
|
||||
a := []rune(input)
|
||||
str := ""
|
||||
for i, r := range a {
|
||||
str = str + string(r)
|
||||
if i > 0 && (i+1)%length == 0 {
|
||||
str += "\n"
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/lrstanley/girc"
|
||||
"github.com/paulrosania/go-charset/charset"
|
||||
@ -24,12 +25,11 @@ type Birc struct {
|
||||
i *girc.Client
|
||||
Nick string
|
||||
names map[string][]string
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
connected chan struct{}
|
||||
Local chan config.Message // local queue for flood control
|
||||
Account string
|
||||
FirstConnection bool
|
||||
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -39,13 +39,11 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Birc {
|
||||
func New(cfg *config.BridgeConfig) *Birc {
|
||||
b := &Birc{}
|
||||
b.Config = &cfg
|
||||
b.BridgeConfig = cfg
|
||||
b.Nick = b.Config.Nick
|
||||
b.Remote = c
|
||||
b.names = make(map[string][]string)
|
||||
b.Account = account
|
||||
b.connected = make(chan struct{})
|
||||
if b.Config.MessageDelay == 0 {
|
||||
b.Config.MessageDelay = 1300
|
||||
@ -81,12 +79,22 @@ func (b *Birc) Connect() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// fix strict user handling of girc
|
||||
user := b.Config.Nick
|
||||
for !girc.IsValidUser(user) {
|
||||
if len(user) == 1 {
|
||||
user = "matterbridge"
|
||||
break
|
||||
}
|
||||
user = user[1:]
|
||||
}
|
||||
|
||||
i := girc.New(girc.Config{
|
||||
Server: server,
|
||||
ServerPass: b.Config.Password,
|
||||
Port: port,
|
||||
Nick: b.Config.Nick,
|
||||
User: b.Config.Nick,
|
||||
User: user,
|
||||
Name: b.Config.Nick,
|
||||
SSL: b.Config.UseTLS,
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, ServerName: server},
|
||||
@ -168,9 +176,27 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
||||
msg.Text = buf.String()
|
||||
}
|
||||
|
||||
if msg.Extra != nil {
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
}
|
||||
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// split long messages on messageLength, to avoid clipped messages #281
|
||||
if b.Config.MessageSplit {
|
||||
msg.Text = helper.SplitStringLength(msg.Text, b.Config.MessageLength)
|
||||
}
|
||||
for _, text := range strings.Split(msg.Text, "\n") {
|
||||
input := []rune(text)
|
||||
if len(text) > b.Config.MessageLength {
|
||||
text = text[:b.Config.MessageLength] + " <message clipped>"
|
||||
text = string(input[:b.Config.MessageLength]) + " <message clipped>"
|
||||
}
|
||||
if len(b.Local) < b.Config.MessageQueue {
|
||||
if len(b.Local) == b.Config.MessageQueue-1 {
|
||||
@ -241,6 +267,7 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
|
||||
channel := event.Params[0]
|
||||
if event.Command == "KICK" {
|
||||
flog.Infof("Got kicked from %s by %s", channel, event.Source.Name)
|
||||
time.Sleep(time.Duration(b.Config.RejoinDelay) * time.Second)
|
||||
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
||||
return
|
||||
}
|
||||
@ -296,14 +323,13 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
||||
if event.Source.Name == b.Nick {
|
||||
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)
|
||||
msg := ""
|
||||
if event.Command == "CTCP_ACTION" {
|
||||
// msg = event.Source.Name + " "
|
||||
if event.IsAction() {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
msg += event.Trailing
|
||||
msg += event.StripAction()
|
||||
// strip IRC colors
|
||||
re := regexp.MustCompile(`[[:cntrl:]](?:\d{1,2}(?:,\d{1,2})?)?`)
|
||||
msg = re.ReplaceAllString(msg, "")
|
||||
|
@ -1,22 +1,24 @@
|
||||
package bmatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"mime"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
matrix "github.com/matrix-org/gomatrix"
|
||||
matrix "github.com/matterbridge/gomatrix"
|
||||
)
|
||||
|
||||
type Bmatrix struct {
|
||||
mc *matrix.Client
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
UserID string
|
||||
RoomMap map[string]string
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -26,12 +28,9 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bmatrix {
|
||||
b := &Bmatrix{}
|
||||
func New(cfg *config.BridgeConfig) *Bmatrix {
|
||||
b := &Bmatrix{BridgeConfig: cfg}
|
||||
b.RoomMap = make(map[string]string)
|
||||
b.Config = &cfg
|
||||
b.Account = account
|
||||
b.Remote = c
|
||||
return b
|
||||
}
|
||||
|
||||
@ -76,19 +75,70 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
||||
|
||||
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
channel := b.getRoomID(msg.Channel)
|
||||
// ignore delete messages
|
||||
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)
|
||||
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})
|
||||
return "", nil
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.EventID, err
|
||||
}
|
||||
b.mc.SendText(channel, msg.Username+msg.Text)
|
||||
return "", nil
|
||||
|
||||
if msg.Extra != nil {
|
||||
// check if we have files to upload (from slack, telegram or mattermost)
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
content := bytes.NewReader(*fi.Data)
|
||||
sp := strings.Split(fi.Name, ".")
|
||||
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
||||
if strings.Contains(mtype, "image") ||
|
||||
strings.Contains(mtype, "video") {
|
||||
flog.Debugf("uploading file: %s %s", fi.Name, mtype)
|
||||
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
||||
if err != nil {
|
||||
flog.Errorf("file upload failed: %#v", err)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(mtype, "video") {
|
||||
flog.Debugf("sendVideo %s", res.ContentURI)
|
||||
_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI)
|
||||
if err != nil {
|
||||
flog.Errorf("sendVideo failed: %#v", err)
|
||||
}
|
||||
}
|
||||
if strings.Contains(mtype, "image") {
|
||||
flog.Debugf("sendImage %s", res.ContentURI)
|
||||
_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI)
|
||||
if err != nil {
|
||||
flog.Errorf("sendImage failed: %#v", err)
|
||||
}
|
||||
}
|
||||
flog.Debugf("result: %#v", res)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := b.mc.SendText(channel, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.EventID, err
|
||||
}
|
||||
|
||||
func (b *Bmatrix) getRoomID(channel string) string {
|
||||
@ -101,31 +151,11 @@ func (b *Bmatrix) getRoomID(channel string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bmatrix) handlematrix() error {
|
||||
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
|
||||
syncer.OnEventType("m.room.message", func(ev *matrix.Event) {
|
||||
if (ev.Content["msgtype"].(string) == "m.text" || ev.Content["msgtype"].(string) == "m.notice" || ev.Content["msgtype"].(string) == "m.emote") && 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
|
||||
}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
flog.Debugf("Received: %#v", ev)
|
||||
})
|
||||
syncer.OnEventType("m.room.redaction", b.handleEvent)
|
||||
syncer.OnEventType("m.room.message", b.handleEvent)
|
||||
go func() {
|
||||
for {
|
||||
if err := b.mc.Sync(); err != nil {
|
||||
@ -135,3 +165,73 @@ func (b *Bmatrix) handlematrix() error {
|
||||
}()
|
||||
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,8 @@ type MMMessage struct {
|
||||
type Bmattermost struct {
|
||||
MMhook
|
||||
MMapi
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
TeamId string
|
||||
Account string
|
||||
TeamId string
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -45,11 +43,8 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bmattermost {
|
||||
b := &Bmattermost{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
func New(cfg *config.BridgeConfig) *Bmattermost {
|
||||
b := &Bmattermost{BridgeConfig: cfg}
|
||||
b.mmMap = make(map[string]string)
|
||||
return b
|
||||
}
|
||||
@ -190,9 +185,9 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("ERROR %#v", err)
|
||||
return "", err
|
||||
}
|
||||
message = "uploaded a file: " + fi.Name
|
||||
message = fi.Comment
|
||||
if b.Config.PrefixMessagesWithNick {
|
||||
message = nick + "uploaded a file: " + fi.Name
|
||||
message = nick + fi.Comment
|
||||
}
|
||||
res, err = b.mc.PostMessageWithFiles(b.mc.GetChannelId(channel, ""), message, []string{id})
|
||||
}
|
||||
|
@ -14,9 +14,7 @@ type MMhook struct {
|
||||
|
||||
type Brocketchat struct {
|
||||
MMhook
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -26,12 +24,8 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Brocketchat {
|
||||
b := &Brocketchat{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
return b
|
||||
func New(cfg *config.BridgeConfig) *Brocketchat {
|
||||
return &Brocketchat{BridgeConfig: cfg}
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Command(cmd string) string {
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/matterbridge/slack"
|
||||
"github.com/nlopes/slack"
|
||||
"html"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -27,14 +27,12 @@ type MMMessage struct {
|
||||
type Bslack struct {
|
||||
mh *matterhook.Client
|
||||
sc *slack.Client
|
||||
Config *config.Protocol
|
||||
rtm *slack.RTM
|
||||
Plus bool
|
||||
Remote chan config.Message
|
||||
Users []slack.User
|
||||
Account string
|
||||
si *slack.Info
|
||||
channels []slack.Channel
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -44,12 +42,8 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bslack {
|
||||
b := &Bslack{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
return b
|
||||
func New(cfg *config.BridgeConfig) *Bslack {
|
||||
return &Bslack{BridgeConfig: cfg}
|
||||
}
|
||||
|
||||
func (b *Bslack) Command(cmd string) string {
|
||||
@ -113,7 +107,7 @@ func (b *Bslack) Disconnect() error {
|
||||
|
||||
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
||||
// 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") {
|
||||
// TODO check if bot has already joined channel
|
||||
return nil
|
||||
@ -161,7 +155,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
||||
np.AsUser = true
|
||||
}
|
||||
np.Username = nick
|
||||
np.IconURL = config.GetIconURL(&msg, b.Config)
|
||||
np.IconURL = config.GetIconURL(&msg, &b.Config)
|
||||
if msg.Avatar != "" {
|
||||
np.IconURL = msg.Avatar
|
||||
}
|
||||
@ -195,9 +189,10 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
_, err = b.sc.UploadFile(slack.FileUploadParameters{
|
||||
Reader: bytes.NewReader(*fi.Data),
|
||||
Filename: fi.Name,
|
||||
Channels: []string{schannel.ID},
|
||||
Reader: bytes.NewReader(*fi.Data),
|
||||
Filename: fi.Name,
|
||||
Channels: []string{schannel.ID},
|
||||
InitialComment: fi.Comment,
|
||||
})
|
||||
if err != nil {
|
||||
flog.Errorf("uploadfile %#v", err)
|
||||
@ -293,12 +288,17 @@ func (b *Bslack) handleSlack() {
|
||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||
if message.Raw.File != nil {
|
||||
// limit to 1MB for now
|
||||
if message.Raw.File.Size <= 1000000 {
|
||||
if message.Raw.File.Size <= b.General.MediaDownloadSize {
|
||||
comment := ""
|
||||
data, err := b.downloadFile(message.Raw.File.URLPrivateDownload)
|
||||
if err != nil {
|
||||
flog.Errorf("download %s failed %#v", message.Raw.File.URLPrivateDownload, err)
|
||||
} else {
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: message.Raw.File.Name, Data: data})
|
||||
results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(msg.Text, -1)
|
||||
if len(results) > 0 {
|
||||
comment = results[0][1]
|
||||
}
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: message.Raw.File.Name, Data: data, Comment: comment})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,9 +309,11 @@ func (b *Bslack) handleSlack() {
|
||||
|
||||
func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||
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) {
|
||||
case *slack.MessageEvent:
|
||||
flog.Debugf("Receiving from slackclient %#v", ev)
|
||||
if len(ev.Attachments) > 0 {
|
||||
// skip messages we made ourselves
|
||||
if ev.Attachments[0].CallbackID == "matterbridge" {
|
||||
@ -394,6 +396,8 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||
}
|
||||
case *slack.InvalidAuthEvent:
|
||||
flog.Fatalf("Invalid Token %#v", ev)
|
||||
case *slack.ConnectionErrorEvent:
|
||||
flog.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
132
bridge/sshchat/sshchat.go
Normal file
132
bridge/sshchat/sshchat.go
Normal file
@ -0,0 +1,132 @@
|
||||
package bsshchat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/shazow/ssh-chat/sshd"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Bsshchat struct {
|
||||
r *bufio.Scanner
|
||||
w io.WriteCloser
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "sshchat"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bsshchat {
|
||||
return &Bsshchat{BridgeConfig: cfg}
|
||||
}
|
||||
|
||||
func (b *Bsshchat) Connect() error {
|
||||
var err error
|
||||
flog.Infof("Connecting %s", b.Config.Server)
|
||||
go func() {
|
||||
err = sshd.ConnectShell(b.Config.Server, b.Config.Nick, func(r io.Reader, w io.WriteCloser) error {
|
||||
b.r = bufio.NewScanner(r)
|
||||
b.w = w
|
||||
b.r.Scan()
|
||||
w.Write([]byte("/theme mono\r\n"))
|
||||
b.handleSshChat()
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bsshchat) Disconnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bsshchat) JoinChannel(channel config.ChannelInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bsshchat) Send(msg config.Message) (string, error) {
|
||||
// ignore delete messages
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
return "", nil
|
||||
}
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
if msg.Extra != nil {
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
}
|
||||
b.w.Write([]byte(msg.Username + msg.Text))
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
b.w.Write([]byte(msg.Username + msg.Text + "\r\n"))
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (b *Bsshchat) sshchatKeepAlive() chan bool {
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
ticker := time.NewTicker(90 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
flog.Debugf("PING")
|
||||
err := b.xc.PingC2S("", "")
|
||||
if err != nil {
|
||||
flog.Debugf("PING failed %#v", err)
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return done
|
||||
}
|
||||
*/
|
||||
|
||||
func stripPrompt(s string) string {
|
||||
pos := strings.LastIndex(s, "\033[K")
|
||||
if pos < 0 {
|
||||
return s
|
||||
}
|
||||
return s[pos+3:]
|
||||
}
|
||||
|
||||
func (b *Bsshchat) handleSshChat() error {
|
||||
/*
|
||||
done := b.sshchatKeepAlive()
|
||||
defer close(done)
|
||||
*/
|
||||
wait := true
|
||||
for {
|
||||
if b.r.Scan() {
|
||||
res := strings.Split(stripPrompt(b.r.Text()), ":")
|
||||
if res[0] == "-> Set theme" {
|
||||
wait = false
|
||||
log.Debugf("mono found, allowing")
|
||||
continue
|
||||
}
|
||||
if !wait {
|
||||
flog.Debugf("message %#v", res)
|
||||
rmsg := config.Message{Username: res[0], Text: strings.Join(res[1:], ":"), Channel: "sshchat", Account: b.Account, UserID: "nick"}
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,11 +16,9 @@ import (
|
||||
type Bsteam struct {
|
||||
c *steam.Client
|
||||
connected chan struct{}
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
userMap map[steamid.SteamId]string
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -30,11 +28,8 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bsteam {
|
||||
b := &Bsteam{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
func New(cfg *config.BridgeConfig) *Bsteam {
|
||||
b := &Bsteam{BridgeConfig: cfg}
|
||||
b.userMap = make(map[steamid.SteamId]string)
|
||||
b.connected = make(chan struct{})
|
||||
return b
|
||||
|
@ -3,6 +3,7 @@ package btelegram
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
@ -11,10 +12,8 @@ import (
|
||||
)
|
||||
|
||||
type Btelegram struct {
|
||||
c *tgbotapi.BotAPI
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
c *tgbotapi.BotAPI
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -24,12 +23,8 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
|
||||
b := &Btelegram{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
return b
|
||||
func New(cfg *config.BridgeConfig) *Btelegram {
|
||||
return &Btelegram{BridgeConfig: cfg}
|
||||
}
|
||||
|
||||
func (b *Btelegram) Connect() error {
|
||||
@ -89,6 +84,9 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
|
||||
if b.Config.MessageFormat == "HTML" {
|
||||
m.ParseMode = tgbotapi.ModeHTML
|
||||
}
|
||||
_, err = b.c.Send(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -113,20 +111,14 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||
if err != nil {
|
||||
log.Errorf("file upload failed: %#v", err)
|
||||
}
|
||||
if fi.Comment != "" {
|
||||
b.sendMessage(chatid, msg.Username+fi.Comment)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
|
||||
if b.Config.MessageFormat == "HTML" {
|
||||
m.ParseMode = tgbotapi.ModeHTML
|
||||
}
|
||||
res, err := b.c.Send(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(res.MessageID), nil
|
||||
|
||||
return b.sendMessage(chatid, msg.Username+msg.Text)
|
||||
}
|
||||
|
||||
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
@ -178,12 +170,34 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
if message.Video != nil {
|
||||
b.handleDownload(message.Video, &fmsg)
|
||||
}
|
||||
if message.Photo != nil && b.Config.UseInsecureURL {
|
||||
if message.Photo != nil {
|
||||
b.handleDownload(message.Photo, &fmsg)
|
||||
}
|
||||
if message.Document != nil && b.Config.UseInsecureURL {
|
||||
b.handleDownload(message.Sticker, &fmsg)
|
||||
text = text + " " + message.Document.FileName + " : " + b.getFileDirectURL(message.Document.FileID)
|
||||
if message.Document != nil {
|
||||
b.handleDownload(message.Document, &fmsg)
|
||||
}
|
||||
if message.Voice != nil {
|
||||
b.handleDownload(message.Voice, &fmsg)
|
||||
}
|
||||
if message.Audio != nil {
|
||||
b.handleDownload(message.Audio, &fmsg)
|
||||
}
|
||||
|
||||
if message.ForwardFrom != nil {
|
||||
usernameForward := ""
|
||||
if b.Config.UseFirstName {
|
||||
usernameForward = message.ForwardFrom.FirstName
|
||||
}
|
||||
if usernameForward == "" {
|
||||
usernameForward = message.ForwardFrom.UserName
|
||||
if usernameForward == "" {
|
||||
usernameForward = message.ForwardFrom.FirstName
|
||||
}
|
||||
}
|
||||
if usernameForward == "" {
|
||||
usernameForward = "unknown"
|
||||
}
|
||||
text = "Forwarded from " + usernameForward + ": " + text
|
||||
}
|
||||
|
||||
// quote the previous message
|
||||
@ -208,7 +222,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
|
||||
if text != "" || len(fmsg.Extra) > 0 {
|
||||
flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
|
||||
msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID)}
|
||||
msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra}
|
||||
flog.Debugf("Message is %#v", msg)
|
||||
b.Remote <- msg
|
||||
}
|
||||
@ -228,28 +242,55 @@ func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
|
||||
url := ""
|
||||
name := ""
|
||||
text := ""
|
||||
fileid := ""
|
||||
switch v := file.(type) {
|
||||
case *tgbotapi.Audio:
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
fileid = v.FileID
|
||||
case *tgbotapi.Voice:
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
if !strings.HasSuffix(name, ".ogg") {
|
||||
name = name + ".ogg"
|
||||
}
|
||||
fileid = v.FileID
|
||||
case *tgbotapi.Sticker:
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
name = "sticker"
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
if !strings.HasSuffix(name, ".webp") {
|
||||
name = name + ".webp"
|
||||
}
|
||||
text = " " + url
|
||||
fileid = v.FileID
|
||||
case *tgbotapi.Video:
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
name = "video"
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
fileid = v.FileID
|
||||
case *[]tgbotapi.PhotoSize:
|
||||
photos := *v
|
||||
size = photos[len(photos)-1].FileSize
|
||||
url = b.getFileDirectURL(photos[len(photos)-1].FileID)
|
||||
name = "photo"
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
case *tgbotapi.Document:
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
name = v.FileName
|
||||
text = " " + v.FileName + " : " + url
|
||||
fileid = v.FileID
|
||||
}
|
||||
if b.Config.UseInsecureURL {
|
||||
msg.Text = text
|
||||
@ -257,12 +298,26 @@ func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
|
||||
}
|
||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||
// limit to 1MB for now
|
||||
if size <= 1000000 {
|
||||
flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
|
||||
if size <= 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))
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Btelegram) sendMessage(chatid int64, text string) (string, error) {
|
||||
m := tgbotapi.NewMessage(chatid, text)
|
||||
if b.Config.MessageFormat == "HTML" {
|
||||
m.ParseMode = tgbotapi.ModeHTML
|
||||
}
|
||||
res, err := b.c.Send(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(res.MessageID), nil
|
||||
}
|
||||
|
@ -14,9 +14,7 @@ import (
|
||||
type Bxmpp struct {
|
||||
xc *xmpp.Client
|
||||
xmppMap map[string]string
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
*config.BridgeConfig
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -26,12 +24,9 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bxmpp {
|
||||
b := &Bxmpp{}
|
||||
func New(cfg *config.BridgeConfig) *Bxmpp {
|
||||
b := &Bxmpp{BridgeConfig: cfg}
|
||||
b.xmppMap = make(map[string]string)
|
||||
b.Config = &cfg
|
||||
b.Account = account
|
||||
b.Remote = c
|
||||
return b
|
||||
}
|
||||
|
||||
@ -85,6 +80,19 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
if msg.Extra != nil {
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
}
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
||||
return "", nil
|
||||
}
|
||||
@ -102,7 +110,7 @@ func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
||||
TLSConfig: tc,
|
||||
|
||||
//StartTLS: false,
|
||||
Debug: true,
|
||||
Debug: b.General.Debug,
|
||||
Session: true,
|
||||
Status: "",
|
||||
StatusMessage: "",
|
||||
@ -158,7 +166,7 @@ func (b *Bxmpp) handleXmpp() error {
|
||||
if len(s) == 2 {
|
||||
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.Text, ok = b.replaceAction(rmsg.Text)
|
||||
if ok {
|
||||
|
69
changelog.md
69
changelog.md
@ -1,3 +1,72 @@
|
||||
# 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
|
||||
## New features
|
||||
* sshchat: New protocol support added (https://github.com/shazow/ssh-chat)
|
||||
* general: Allow specifying maximum download size of media using MediaDownloadSize (slack,telegram,matrix)
|
||||
* api: Add (simple, one listener) long-polling support (api). Closes #307
|
||||
* telegram: Add support for forwarded messages. Closes #313
|
||||
* telegram: Add support for Audio/Voice files (telegram). Closes #314
|
||||
* irc: Add RejoinDelay option. Delay to rejoin after channel kick (irc). Closes #322
|
||||
|
||||
## Bugfix
|
||||
* telegram: Also use HTML in edited messages (telegram). Closes #315
|
||||
* matrix: Fix panic (matrix). Closes #316
|
||||
|
||||
# v1.5.1
|
||||
|
||||
## Bugfix
|
||||
* irc: Fix irc ACTION regression (irc). Closes #306
|
||||
* irc: Split on UTF-8 for MessageSplit (irc). Closes #308
|
||||
|
||||
# v1.5.0
|
||||
## New features
|
||||
* general: remote mediaserver support. See MediaServerDownload and MediaServerUpload in matterbridge.toml.sample
|
||||
more information on https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%5Badvanced%5D
|
||||
* general: Add support for ReplaceNicks using regexp to replace nicks. Closes #269 (see matterbridge.toml.sample)
|
||||
* general: Add support for ReplaceMessages using regexp to replace messages. #269 (see matterbridge.toml.sample)
|
||||
* irc: Add MessageSplit option to split messages on MessageLength (irc). Closes #281
|
||||
* matrix: Add support for uploading images/video (matrix). Closes #302
|
||||
* matrix: Add support for uploaded images/video (matrix)
|
||||
|
||||
## Bugfix
|
||||
* telegram: Add webp extension to stickers if necessary (telegram)
|
||||
* mattermost: Break when re-login fails (mattermost)
|
||||
|
||||
# v1.4.1
|
||||
## Bugfix
|
||||
* telegram: fix issue with uploading for images/documents/stickers
|
||||
* slack: remove double messages sent to other bridges when uploading files
|
||||
* irc: Fix strict user handling of girc (irc). Closes #298
|
||||
|
||||
# v1.4.0
|
||||
## Breaking changes
|
||||
* general: `[general]` settings don't override the specific bridge settings
|
||||
|
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
|
@ -1,13 +1,16 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
// "github.com/davecgh/go-spew/spew"
|
||||
"crypto/sha1"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
"github.com/peterhellberg/emojilib"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@ -26,8 +29,9 @@ type Gateway struct {
|
||||
}
|
||||
|
||||
type BrMsgID struct {
|
||||
br *bridge.Bridge
|
||||
ID string
|
||||
br *bridge.Bridge
|
||||
ID string
|
||||
ChannelID string
|
||||
}
|
||||
|
||||
func New(cfg config.Gateway, r *Router) *Gateway {
|
||||
@ -90,6 +94,10 @@ func (gw *Gateway) mapChannelConfig(cfg []config.Bridge, direction string) {
|
||||
if isApi(br.Account) {
|
||||
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
|
||||
if _, ok := gw.Channels[ID]; !ok {
|
||||
channel := &config.ChannelInfo{Name: br.Channel, Direction: direction, ID: ID, Options: br.Options, Account: br.Account,
|
||||
@ -115,6 +123,12 @@ func (gw *Gateway) mapChannels() error {
|
||||
|
||||
func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []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
|
||||
for _, channel := range gw.Channels {
|
||||
// lookup the channel from the message
|
||||
@ -152,7 +166,12 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
||||
// only slack now, check will have to be done in the different bridges.
|
||||
// we need to check if we can't use fallback or text in other bridges
|
||||
if msg.Extra != nil {
|
||||
if dest.Protocol != "slack" {
|
||||
if dest.Protocol != "discord" &&
|
||||
dest.Protocol != "slack" &&
|
||||
dest.Protocol != "mattermost" &&
|
||||
dest.Protocol != "telegram" &&
|
||||
dest.Protocol != "matrix" &&
|
||||
dest.Protocol != "xmpp" {
|
||||
if msg.Text == "" {
|
||||
return brMsgIDs
|
||||
}
|
||||
@ -183,7 +202,9 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
||||
if res, ok := gw.Messages.Get(origmsg.ID); ok {
|
||||
IDs := res.([]*BrMsgID)
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -198,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
|
||||
if mID != "" {
|
||||
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID})
|
||||
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID, channel.ID})
|
||||
}
|
||||
}
|
||||
return brMsgIDs
|
||||
@ -210,8 +231,8 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
||||
return true
|
||||
}
|
||||
if msg.Text == "" {
|
||||
// we have an attachment
|
||||
if msg.Extra != nil && msg.Extra["attachments"] != nil {
|
||||
// we have an attachment or actual bytes
|
||||
if msg.Extra != nil && (msg.Extra["attachments"] != nil || len(msg.Extra["file"]) > 0) {
|
||||
return false
|
||||
}
|
||||
log.Debugf("ignoring empty message %#v from %s", msg, msg.Account)
|
||||
@ -251,6 +272,20 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin
|
||||
if nick == "" {
|
||||
nick = gw.Config.General.RemoteNickFormat
|
||||
}
|
||||
|
||||
// loop to replace nicks
|
||||
for _, outer := range br.Config.ReplaceNicks {
|
||||
search := outer[0]
|
||||
replace := outer[1]
|
||||
// TODO move compile to bridge init somewhere
|
||||
re, err := regexp.Compile(search)
|
||||
if err != nil {
|
||||
log.Errorf("regexp in %s failed: %s", msg.Account, err)
|
||||
break
|
||||
}
|
||||
msg.Username = re.ReplaceAllString(msg.Username, replace)
|
||||
}
|
||||
|
||||
if len(msg.Username) > 0 {
|
||||
// fix utf-8 issue #193
|
||||
i := 0
|
||||
@ -263,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, "{NICK}", msg.Username, -1)
|
||||
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
||||
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
||||
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||
return nick
|
||||
}
|
||||
|
||||
@ -284,7 +319,52 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string
|
||||
func (gw *Gateway) modifyMessage(msg *config.Message) {
|
||||
// replace :emoji: to unicode
|
||||
msg.Text = emojilib.Replace(msg.Text)
|
||||
msg.Gateway = gw.Name
|
||||
br := gw.Bridges[msg.Account]
|
||||
// loop to replace messages
|
||||
for _, outer := range br.Config.ReplaceMessages {
|
||||
search := outer[0]
|
||||
replace := outer[1]
|
||||
// TODO move compile to bridge init somewhere
|
||||
re, err := regexp.Compile(search)
|
||||
if err != nil {
|
||||
log.Errorf("regexp in %s failed: %s", msg.Account, err)
|
||||
break
|
||||
}
|
||||
msg.Text = re.ReplaceAllString(msg.Text, replace)
|
||||
}
|
||||
|
||||
// messages from api have Gateway specified, don't overwrite
|
||||
if msg.Protocol != "api" {
|
||||
msg.Gateway = gw.Name
|
||||
}
|
||||
}
|
||||
|
||||
func (gw *Gateway) handleFiles(msg *config.Message) {
|
||||
if msg.Extra == nil || gw.Config.General.MediaServerUpload == "" {
|
||||
return
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
for i, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))
|
||||
reader := bytes.NewReader(*fi.Data)
|
||||
url := gw.Config.General.MediaServerUpload + "/" + sha1sum + "/" + fi.Name
|
||||
durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
|
||||
extra := msg.Extra["file"][i].(config.FileInfo)
|
||||
extra.URL = durl
|
||||
msg.Extra["file"][i] = extra
|
||||
req, _ := http.NewRequest("PUT", url, reader)
|
||||
req.Header.Set("Content-Type", "binary/octet-stream")
|
||||
_, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("mediaserver upload failed: %#v", err)
|
||||
}
|
||||
log.Debugf("mediaserver download URL = %s", durl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelID(msg config.Message) string {
|
||||
|
@ -99,6 +99,7 @@ func (r *Router) handleReceive() {
|
||||
if !gw.ignoreMessage(&msg) {
|
||||
msg.Timestamp = time.Now()
|
||||
gw.modifyMessage(&msg)
|
||||
gw.handleFiles(&msg)
|
||||
for _, br := range gw.Bridges {
|
||||
msgIDs = append(msgIDs, gw.handleMessage(msg, br)...)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "1.4.0"
|
||||
version = "1.7.0"
|
||||
githash string
|
||||
)
|
||||
|
||||
@ -43,6 +43,7 @@ func main() {
|
||||
log.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
|
||||
}
|
||||
cfg := config.NewConfig(*flagConfig)
|
||||
cfg.General.Debug = *flagDebug
|
||||
r, err := gateway.NewRouter(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Starting gateway failed: %s", err)
|
||||
|
@ -80,6 +80,15 @@ MessageQueue=30
|
||||
#OPTIONAL (default 400)
|
||||
MessageLength=400
|
||||
|
||||
#Split messages on MessageLength instead of showing the <message clipped>
|
||||
#WARNING: this could lead to flooding
|
||||
#OPTIONAL (default false)
|
||||
MessageSplit=false
|
||||
|
||||
#Delay in seconds to rejoin a channel when kicked
|
||||
#OPTIONAL (default 0)
|
||||
RejoinDelay=0
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
@ -91,6 +100,23 @@ IgnoreNicks="ircspammer1 ircspammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -154,6 +180,23 @@ IgnoreNicks="ircspammer1 ircspammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#Messages you want to replace.
|
||||
#It replaces outgoing messages from the bridge.
|
||||
#So you need to place it by the sending bridge definition.
|
||||
#Regular expressions supported
|
||||
#Some examples:
|
||||
#This replaces cat => dog and sleep => awake
|
||||
#ReplaceMessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#This Replaces every number with number. 123 => numbernumbernumber
|
||||
#ReplaceMessages=[ ["[0-9]","number"] ]
|
||||
#OPTIONAL (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#Nicks you want to replace.
|
||||
#See ReplaceMessages for syntaxA
|
||||
#OPTIONAL (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -208,6 +251,23 @@ IgnoreNicks="spammer1 spammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -322,6 +382,23 @@ IgnoreNicks="ircspammer1 ircspammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -366,6 +443,23 @@ IgnoreNicks="ircspammer1 ircspammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -457,6 +551,23 @@ IgnoreNicks="ircspammer1 ircspammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -525,6 +636,23 @@ IgnoreNicks="ircspammer1 ircspammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -592,6 +720,23 @@ IgnoreNicks="spammer1 spammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -660,6 +805,23 @@ IgnoreNicks="ircspammer1 ircspammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -720,6 +882,23 @@ IgnoreNicks="spammer1 spammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -774,6 +953,23 @@ IgnoreNicks="spammer1 spammer2"
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -838,6 +1034,30 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
#OPTIONAL (default false)
|
||||
StripNick=false
|
||||
|
||||
|
||||
#MediaServerUpload and MediaServerDownload are used for uploading images/files/video to
|
||||
#a remote "mediaserver" (a webserver like caddy for example).
|
||||
#When configured images/files uploaded on bridges like mattermost,slack, telegram will be downloaded
|
||||
#and uploaded again to MediaServerUpload URL
|
||||
#The MediaServerDownload will be used so that bridges without native uploading support:
|
||||
#gitter, irc and xmpp will be shown links to the files on MediaServerDownload
|
||||
#
|
||||
#More information https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%5Badvanced%5D
|
||||
#OPTIONAL (default empty)
|
||||
MediaServerUpload="https://user:pass@yourserver.com/upload"
|
||||
#OPTIONAL (default empty)
|
||||
MediaServerDownload="https://youserver.com/download"
|
||||
|
||||
#MediaDownloadSize is the maximum size of attachments, videos, images
|
||||
#matterbridge will download and upload this file to bridges that also support uploading files.
|
||||
#eg downloading from slack to upload it to mattermost
|
||||
#
|
||||
#It will only download from bridges that don't have public links available, which are for the moment
|
||||
#slack, telegram and matrix
|
||||
#
|
||||
#Optional (default 1000000 (1 megabyte))
|
||||
MediaDownloadSize=1000000
|
||||
|
||||
###################################################################
|
||||
#Gateway configuration
|
||||
###################################################################
|
||||
|
@ -817,9 +817,14 @@ func (m *MMClient) StatusLoop() {
|
||||
backoff = time.Second * 60
|
||||
case <-time.After(time.Second * 5):
|
||||
if retries > 3 {
|
||||
m.log.Debug("StatusLoop() timeout")
|
||||
m.Logout()
|
||||
m.WsQuit = false
|
||||
m.Login()
|
||||
err := m.Login()
|
||||
if err != nil {
|
||||
log.Errorf("Login failed: %#v", err)
|
||||
break
|
||||
}
|
||||
if m.OnWsConnect != nil {
|
||||
m.OnWsConnect()
|
||||
}
|
||||
|
11
vendor/github.com/labstack/echo/context.go
generated
vendored
11
vendor/github.com/labstack/echo/context.go
generated
vendored
@ -494,14 +494,9 @@ func (c *context) Stream(code int, contentType string, r io.Reader) (err error)
|
||||
}
|
||||
|
||||
func (c *context) File(file string) (err error) {
|
||||
file, err = url.QueryUnescape(file) // Issue #839
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return ErrNotFound
|
||||
return NotFoundHandler(c)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@ -510,7 +505,7 @@ func (c *context) File(file string) (err error) {
|
||||
file = filepath.Join(file, indexPage)
|
||||
f, err = os.Open(file)
|
||||
if err != nil {
|
||||
return ErrNotFound
|
||||
return NotFoundHandler(c)
|
||||
}
|
||||
defer f.Close()
|
||||
if fi, err = f.Stat(); err != nil {
|
||||
@ -530,7 +525,7 @@ func (c *context) Inline(file, name string) (err error) {
|
||||
}
|
||||
|
||||
func (c *context) contentDisposition(file, name, dispositionType string) (err error) {
|
||||
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s", dispositionType, name))
|
||||
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
|
||||
c.File(file)
|
||||
return
|
||||
}
|
||||
|
136
vendor/github.com/labstack/echo/echo.go
generated
vendored
136
vendor/github.com/labstack/echo/echo.go
generated
vendored
@ -72,6 +72,7 @@ type (
|
||||
TLSServer *http.Server
|
||||
Listener net.Listener
|
||||
TLSListener net.Listener
|
||||
AutoTLSManager autocert.Manager
|
||||
DisableHTTP2 bool
|
||||
Debug bool
|
||||
HideBanner bool
|
||||
@ -79,22 +80,22 @@ type (
|
||||
Binder Binder
|
||||
Validator Validator
|
||||
Renderer Renderer
|
||||
AutoTLSManager autocert.Manager
|
||||
// Mutex sync.RWMutex
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// Route contains a handler and information for matching against requests.
|
||||
Route struct {
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Handler string `json:"handler"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// HTTPError represents an error that occurred while handling a request.
|
||||
HTTPError struct {
|
||||
Code int
|
||||
Message interface{}
|
||||
Inner error // Stores the error returned by an external dependency
|
||||
}
|
||||
|
||||
// MiddlewareFunc defines a function to process middleware.
|
||||
@ -121,7 +122,7 @@ type (
|
||||
|
||||
// i is the interface for Echo and Group.
|
||||
i interface {
|
||||
GET(string, HandlerFunc, ...MiddlewareFunc)
|
||||
GET(string, HandlerFunc, ...MiddlewareFunc) *Route
|
||||
}
|
||||
)
|
||||
|
||||
@ -212,7 +213,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
version = "3.1.0"
|
||||
version = "3.2.5"
|
||||
website = "https://echo.labstack.com"
|
||||
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
|
||||
banner = `
|
||||
@ -282,7 +283,7 @@ func New() (e *Echo) {
|
||||
e.TLSServer.Handler = e
|
||||
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
|
||||
e.Binder = &DefaultBinder{}
|
||||
e.Logger.SetLevel(log.OFF)
|
||||
e.Logger.SetLevel(log.ERROR)
|
||||
e.stdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
|
||||
e.pool.New = func() interface{} {
|
||||
return e.NewContext(nil, nil)
|
||||
@ -319,6 +320,9 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||
if he, ok := err.(*HTTPError); ok {
|
||||
code = he.Code
|
||||
msg = he.Message
|
||||
if he.Inner != nil {
|
||||
msg = fmt.Sprintf("%v, %v", err, he.Inner)
|
||||
}
|
||||
} else if e.Debug {
|
||||
msg = err.Error()
|
||||
} else {
|
||||
@ -328,19 +332,19 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||
msg = Map{"message": msg}
|
||||
}
|
||||
|
||||
e.Logger.Error(err)
|
||||
|
||||
// Send response
|
||||
if !c.Response().Committed {
|
||||
if c.Request().Method == HEAD { // Issue #608
|
||||
if err := c.NoContent(code); err != nil {
|
||||
goto ERROR
|
||||
}
|
||||
err = c.NoContent(code)
|
||||
} else {
|
||||
if err := c.JSON(code, msg); err != nil {
|
||||
goto ERROR
|
||||
}
|
||||
err = c.JSON(code, msg)
|
||||
}
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
}
|
||||
}
|
||||
ERROR:
|
||||
e.Logger.Error(err)
|
||||
}
|
||||
|
||||
// Pre adds middleware to the chain which is run before router.
|
||||
@ -355,104 +359,114 @@ func (e *Echo) Use(middleware ...MiddlewareFunc) {
|
||||
|
||||
// CONNECT registers a new CONNECT route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(CONNECT, path, h, m...)
|
||||
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(CONNECT, path, h, m...)
|
||||
}
|
||||
|
||||
// DELETE registers a new DELETE route for a path with matching handler in the router
|
||||
// with optional route-level middleware.
|
||||
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(DELETE, path, h, m...)
|
||||
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(DELETE, path, h, m...)
|
||||
}
|
||||
|
||||
// GET registers a new GET route for a path with matching handler in the router
|
||||
// with optional route-level middleware.
|
||||
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(GET, path, h, m...)
|
||||
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(GET, path, h, m...)
|
||||
}
|
||||
|
||||
// HEAD registers a new HEAD route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(HEAD, path, h, m...)
|
||||
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(HEAD, path, h, m...)
|
||||
}
|
||||
|
||||
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(OPTIONS, path, h, m...)
|
||||
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(OPTIONS, path, h, m...)
|
||||
}
|
||||
|
||||
// PATCH registers a new PATCH route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(PATCH, path, h, m...)
|
||||
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(PATCH, path, h, m...)
|
||||
}
|
||||
|
||||
// POST registers a new POST route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(POST, path, h, m...)
|
||||
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(POST, path, h, m...)
|
||||
}
|
||||
|
||||
// PUT registers a new PUT route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(PUT, path, h, m...)
|
||||
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(PUT, path, h, m...)
|
||||
}
|
||||
|
||||
// TRACE registers a new TRACE route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(TRACE, path, h, m...)
|
||||
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(TRACE, path, h, m...)
|
||||
}
|
||||
|
||||
// Any registers a new route for all HTTP methods and path with matching handler
|
||||
// in the router with optional route-level middleware.
|
||||
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, 0)
|
||||
for _, m := range methods {
|
||||
e.add(m, path, handler, middleware...)
|
||||
routes = append(routes, e.Add(m, path, handler, middleware...))
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Match registers a new route for multiple HTTP methods and path with matching
|
||||
// handler in the router with optional route-level middleware.
|
||||
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, 0)
|
||||
for _, m := range methods {
|
||||
e.add(m, path, handler, middleware...)
|
||||
routes = append(routes, e.Add(m, path, handler, middleware...))
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Static registers a new route with path prefix to serve static files from the
|
||||
// provided root directory.
|
||||
func (e *Echo) Static(prefix, root string) {
|
||||
func (e *Echo) Static(prefix, root string) *Route {
|
||||
if root == "" {
|
||||
root = "." // For security we want to restrict to CWD.
|
||||
}
|
||||
static(e, prefix, root)
|
||||
return static(e, prefix, root)
|
||||
}
|
||||
|
||||
func static(i i, prefix, root string) {
|
||||
func static(i i, prefix, root string) *Route {
|
||||
h := func(c Context) error {
|
||||
name := filepath.Join(root, path.Clean("/"+c.Param("*"))) // "/"+ for security
|
||||
p, err := PathUnescape(c.Param("*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
|
||||
return c.File(name)
|
||||
}
|
||||
i.GET(prefix, h)
|
||||
if prefix == "/" {
|
||||
i.GET(prefix+"*", h)
|
||||
} else {
|
||||
i.GET(prefix+"/*", h)
|
||||
return i.GET(prefix+"*", h)
|
||||
}
|
||||
|
||||
return i.GET(prefix+"/*", h)
|
||||
}
|
||||
|
||||
// File registers a new route with path to serve a static file.
|
||||
func (e *Echo) File(path, file string) {
|
||||
e.GET(path, func(c Context) error {
|
||||
func (e *Echo) File(path, file string) *Route {
|
||||
return e.GET(path, func(c Context) error {
|
||||
return c.File(file)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
// Add registers a new route for an HTTP method and path with matching handler
|
||||
// in the router with optional route-level middleware.
|
||||
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||
name := handlerName(handler)
|
||||
e.router.Add(method, path, func(c Context) error {
|
||||
h := handler
|
||||
@ -463,11 +477,12 @@ func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...Middl
|
||||
return h(c)
|
||||
})
|
||||
r := &Route{
|
||||
Method: method,
|
||||
Path: path,
|
||||
Handler: name,
|
||||
Method: method,
|
||||
Path: path,
|
||||
Name: name,
|
||||
}
|
||||
e.router.routes[method+path] = r
|
||||
return r
|
||||
}
|
||||
|
||||
// Group creates a new router group with prefix and optional group-level middleware.
|
||||
@ -479,12 +494,22 @@ func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
|
||||
|
||||
// URI generates a URI from handler.
|
||||
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
|
||||
name := handlerName(handler)
|
||||
return e.Reverse(name, params...)
|
||||
}
|
||||
|
||||
// URL is an alias for `URI` function.
|
||||
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
|
||||
return e.URI(h, params...)
|
||||
}
|
||||
|
||||
// Reverse generates an URL from route name and provided parameters.
|
||||
func (e *Echo) Reverse(name string, params ...interface{}) string {
|
||||
uri := new(bytes.Buffer)
|
||||
ln := len(params)
|
||||
n := 0
|
||||
name := handlerName(handler)
|
||||
for _, r := range e.router.routes {
|
||||
if r.Handler == name {
|
||||
if r.Name == name {
|
||||
for i, l := 0, len(r.Path); i < l; i++ {
|
||||
if r.Path[i] == ':' && n < ln {
|
||||
for ; i < l && r.Path[i] != '/'; i++ {
|
||||
@ -502,11 +527,6 @@ func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
|
||||
return uri.String()
|
||||
}
|
||||
|
||||
// URL is an alias for `URI` function.
|
||||
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
|
||||
return e.URI(h, params...)
|
||||
}
|
||||
|
||||
// Routes returns the registered routes.
|
||||
func (e *Echo) Routes() []*Route {
|
||||
routes := []*Route{}
|
||||
@ -653,7 +673,7 @@ func NewHTTPError(code int, message ...interface{}) *HTTPError {
|
||||
|
||||
// Error makes it compatible with `error` interface.
|
||||
func (he *HTTPError) Error() string {
|
||||
return fmt.Sprintf("code=%d, message=%s", he.Code, he.Message)
|
||||
return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
|
||||
}
|
||||
|
||||
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
|
||||
|
47
vendor/github.com/labstack/echo/group.go
generated
vendored
47
vendor/github.com/labstack/echo/group.go
generated
vendored
@ -21,66 +21,66 @@ func (g *Group) Use(middleware ...MiddlewareFunc) {
|
||||
// Allow all requests to reach the group as they might get dropped if router
|
||||
// doesn't find a match, making none of the group middleware process.
|
||||
g.echo.Any(path.Clean(g.prefix+"/*"), func(c Context) error {
|
||||
return ErrNotFound
|
||||
return NotFoundHandler(c)
|
||||
}, g.middleware...)
|
||||
}
|
||||
|
||||
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
|
||||
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(CONNECT, path, h, m...)
|
||||
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(CONNECT, path, h, m...)
|
||||
}
|
||||
|
||||
// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
|
||||
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(DELETE, path, h, m...)
|
||||
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(DELETE, path, h, m...)
|
||||
}
|
||||
|
||||
// GET implements `Echo#GET()` for sub-routes within the Group.
|
||||
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(GET, path, h, m...)
|
||||
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(GET, path, h, m...)
|
||||
}
|
||||
|
||||
// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
|
||||
func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(HEAD, path, h, m...)
|
||||
func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(HEAD, path, h, m...)
|
||||
}
|
||||
|
||||
// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
|
||||
func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(OPTIONS, path, h, m...)
|
||||
func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(OPTIONS, path, h, m...)
|
||||
}
|
||||
|
||||
// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
|
||||
func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(PATCH, path, h, m...)
|
||||
func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(PATCH, path, h, m...)
|
||||
}
|
||||
|
||||
// POST implements `Echo#POST()` for sub-routes within the Group.
|
||||
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(POST, path, h, m...)
|
||||
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(POST, path, h, m...)
|
||||
}
|
||||
|
||||
// PUT implements `Echo#PUT()` for sub-routes within the Group.
|
||||
func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(PUT, path, h, m...)
|
||||
func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(PUT, path, h, m...)
|
||||
}
|
||||
|
||||
// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
|
||||
func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
g.add(TRACE, path, h, m...)
|
||||
func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(TRACE, path, h, m...)
|
||||
}
|
||||
|
||||
// Any implements `Echo#Any()` for sub-routes within the Group.
|
||||
func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
for _, m := range methods {
|
||||
g.add(m, path, handler, middleware...)
|
||||
g.Add(m, path, handler, middleware...)
|
||||
}
|
||||
}
|
||||
|
||||
// Match implements `Echo#Match()` for sub-routes within the Group.
|
||||
func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
for _, m := range methods {
|
||||
g.add(m, path, handler, middleware...)
|
||||
g.Add(m, path, handler, middleware...)
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,12 +102,13 @@ func (g *Group) File(path, file string) {
|
||||
g.echo.File(g.prefix+path, file)
|
||||
}
|
||||
|
||||
func (g *Group) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
// Add implements `Echo#Add()` for sub-routes within the Group.
|
||||
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||
// Combine into a new slice to avoid accidentally passing the same slice for
|
||||
// multiple routes, which would lead to later add() calls overwriting the
|
||||
// middleware from earlier calls.
|
||||
m := []MiddlewareFunc{}
|
||||
m = append(m, g.middleware...)
|
||||
m = append(m, middleware...)
|
||||
g.echo.add(method, g.prefix+path, handler, m...)
|
||||
return g.echo.Add(method, g.prefix+path, handler, m...)
|
||||
}
|
||||
|
7
vendor/github.com/labstack/echo/middleware/basic_auth.go
generated
vendored
7
vendor/github.com/labstack/echo/middleware/basic_auth.go
generated
vendored
@ -3,6 +3,7 @@ package middleware
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
@ -27,7 +28,7 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
basic = "Basic"
|
||||
basic = "basic"
|
||||
defaultRealm = "Restricted"
|
||||
)
|
||||
|
||||
@ -54,7 +55,7 @@ func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc {
|
||||
func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Validator == nil {
|
||||
panic("basic-auth middleware requires a validator function")
|
||||
panic("echo: basic-auth middleware requires a validator function")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBasicAuthConfig.Skipper
|
||||
@ -72,7 +73,7 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
|
||||
auth := c.Request().Header.Get(echo.HeaderAuthorization)
|
||||
l := len(basic)
|
||||
|
||||
if len(auth) > l+1 && auth[:l] == basic {
|
||||
if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {
|
||||
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
|
112
vendor/github.com/labstack/echo/middleware/body_dump.go
generated
vendored
Normal file
112
vendor/github.com/labstack/echo/middleware/body_dump.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// BodyDumpConfig defines the config for BodyDump middleware.
|
||||
BodyDumpConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Handler receives request and response payload.
|
||||
// Required.
|
||||
Handler BodyDumpHandler
|
||||
}
|
||||
|
||||
// BodyDumpHandler receives the request and response payload.
|
||||
BodyDumpHandler func(echo.Context, []byte, []byte)
|
||||
|
||||
bodyDumpResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBodyDumpConfig is the default Gzip middleware config.
|
||||
DefaultBodyDumpConfig = BodyDumpConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
)
|
||||
|
||||
// BodyDump returns a BodyDump middleware.
|
||||
//
|
||||
// BodyLimit middleware captures the request and response payload and calls the
|
||||
// registered handler.
|
||||
func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc {
|
||||
c := DefaultBodyDumpConfig
|
||||
c.Handler = handler
|
||||
return BodyDumpWithConfig(c)
|
||||
}
|
||||
|
||||
// BodyDumpWithConfig returns a BodyDump middleware with config.
|
||||
// See: `BodyDump()`.
|
||||
func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Handler == nil {
|
||||
panic("echo: body-dump middleware requires a handler function")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBodyDumpConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
// Request
|
||||
reqBody := []byte{}
|
||||
if c.Request().Body != nil { // Read
|
||||
reqBody, _ = ioutil.ReadAll(c.Request().Body)
|
||||
}
|
||||
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset
|
||||
|
||||
// Response
|
||||
resBody := new(bytes.Buffer)
|
||||
mw := io.MultiWriter(c.Response().Writer, resBody)
|
||||
writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
|
||||
c.Response().Writer = writer
|
||||
|
||||
if err = next(c); err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
|
||||
// Callback
|
||||
config.Handler(c, reqBody, resBody.Bytes())
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) WriteHeader(code int) {
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Flush() {
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) CloseNotify() <-chan bool {
|
||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
4
vendor/github.com/labstack/echo/middleware/body_limit.go
generated
vendored
4
vendor/github.com/labstack/echo/middleware/body_limit.go
generated
vendored
@ -30,7 +30,7 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBodyLimitConfig is the default Gzip middleware config.
|
||||
// DefaultBodyLimitConfig is the default BodyLimit middleware config.
|
||||
DefaultBodyLimitConfig = BodyLimitConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
@ -60,7 +60,7 @@ func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
|
||||
|
||||
limit, err := bytes.Parse(config.Limit)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("invalid body-limit=%s", config.Limit))
|
||||
panic(fmt.Errorf("echo: invalid body-limit=%s", config.Limit))
|
||||
}
|
||||
config.limit = limit
|
||||
pool := limitedReaderPool(config)
|
||||
|
3
vendor/github.com/labstack/echo/middleware/compress.go
generated
vendored
3
vendor/github.com/labstack/echo/middleware/compress.go
generated
vendored
@ -67,7 +67,7 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
|
||||
res := c.Response()
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
|
||||
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
|
||||
res.Header().Add(echo.HeaderContentEncoding, gzipScheme) // Issue #806
|
||||
res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
|
||||
rw := res.Writer
|
||||
w, err := gzip.NewWriterLevel(rw, config.Level)
|
||||
if err != nil {
|
||||
@ -98,6 +98,7 @@ func (w *gzipResponseWriter) WriteHeader(code int) {
|
||||
if code == http.StatusNoContent { // Issue #489
|
||||
w.ResponseWriter.Header().Del(echo.HeaderContentEncoding)
|
||||
}
|
||||
w.Header().Del(echo.HeaderContentLength) // Issue #444
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
|
26
vendor/github.com/labstack/echo/middleware/jwt.go
generated
vendored
26
vendor/github.com/labstack/echo/middleware/jwt.go
generated
vendored
@ -1,7 +1,6 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@ -57,6 +56,12 @@ const (
|
||||
AlgorithmHS256 = "HS256"
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "Missing or malformed jwt")
|
||||
ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired jwt")
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultJWTConfig is the default JWT auth middleware config.
|
||||
DefaultJWTConfig = JWTConfig{
|
||||
@ -77,7 +82,7 @@ var (
|
||||
//
|
||||
// See: https://jwt.io/introduction
|
||||
// See `JWTConfig.TokenLookup`
|
||||
func JWT(key []byte) echo.MiddlewareFunc {
|
||||
func JWT(key interface{}) echo.MiddlewareFunc {
|
||||
c := DefaultJWTConfig
|
||||
c.SigningKey = key
|
||||
return JWTWithConfig(c)
|
||||
@ -134,14 +139,15 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
|
||||
auth, err := extractor(c)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
return err
|
||||
}
|
||||
token := new(jwt.Token)
|
||||
// Issue #647, #656
|
||||
if _, ok := config.Claims.(jwt.MapClaims); ok {
|
||||
token, err = jwt.Parse(auth, config.keyFunc)
|
||||
} else {
|
||||
claims := reflect.ValueOf(config.Claims).Interface().(jwt.Claims)
|
||||
t := reflect.ValueOf(config.Claims).Type().Elem()
|
||||
claims := reflect.New(t).Interface().(jwt.Claims)
|
||||
token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
|
||||
}
|
||||
if err == nil && token.Valid {
|
||||
@ -149,7 +155,11 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
c.Set(config.ContextKey, token)
|
||||
return next(c)
|
||||
}
|
||||
return echo.ErrUnauthorized
|
||||
return &echo.HTTPError{
|
||||
Code: ErrJWTInvalid.Code,
|
||||
Message: ErrJWTInvalid.Message,
|
||||
Inner: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -162,7 +172,7 @@ func jwtFromHeader(header string, authScheme string) jwtExtractor {
|
||||
if len(auth) > l+1 && auth[:l] == authScheme {
|
||||
return auth[l+1:], nil
|
||||
}
|
||||
return "", errors.New("Missing or invalid jwt in the request header")
|
||||
return "", ErrJWTMissing
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +181,7 @@ func jwtFromQuery(param string) jwtExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
token := c.QueryParam(param)
|
||||
if token == "" {
|
||||
return "", errors.New("Missing jwt in the query string")
|
||||
return "", ErrJWTMissing
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
@ -182,7 +192,7 @@ func jwtFromCookie(name string) jwtExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
cookie, err := c.Cookie(name)
|
||||
if err != nil {
|
||||
return "", errors.New("Missing jwt in the cookie")
|
||||
return "", ErrJWTMissing
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
2
vendor/github.com/labstack/echo/middleware/key_auth.go
generated
vendored
2
vendor/github.com/labstack/echo/middleware/key_auth.go
generated
vendored
@ -72,7 +72,7 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
|
||||
config.KeyLookup = DefaultKeyAuthConfig.KeyLookup
|
||||
}
|
||||
if config.Validator == nil {
|
||||
panic("key-auth middleware requires a validator function")
|
||||
panic("echo: key-auth middleware requires a validator function")
|
||||
}
|
||||
|
||||
// Initialize
|
||||
|
42
vendor/github.com/labstack/echo/middleware/proxy.go
generated
vendored
42
vendor/github.com/labstack/echo/middleware/proxy.go
generated
vendored
@ -1,7 +1,6 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
@ -54,35 +53,38 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultProxyConfig is the default Proxy middleware config.
|
||||
DefaultProxyConfig = ProxyConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
)
|
||||
|
||||
func proxyHTTP(t *ProxyTarget) http.Handler {
|
||||
return httputil.NewSingleHostReverseProxy(t.URL)
|
||||
}
|
||||
|
||||
func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
c.Error(errors.New("proxy raw, not a hijacker"))
|
||||
return
|
||||
}
|
||||
in, _, err := h.Hijack()
|
||||
in, _, err := c.Response().Hijack()
|
||||
if err != nil {
|
||||
c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", r.URL, err))
|
||||
c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", t.URL, err))
|
||||
return
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := net.Dial("tcp", t.URL.Host)
|
||||
if err != nil {
|
||||
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", r.URL, err))
|
||||
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err))
|
||||
c.Error(he)
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write header
|
||||
err = r.Write(out)
|
||||
if err != nil {
|
||||
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request copy error=%v, url=%s", r.URL, err))
|
||||
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err))
|
||||
c.Error(he)
|
||||
return
|
||||
}
|
||||
@ -97,7 +99,7 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
|
||||
go cp(in, out)
|
||||
err = <-errc
|
||||
if err != nil && err != io.EOF {
|
||||
c.Logger().Errorf("proxy raw, error=%v, url=%s", r.URL, err)
|
||||
c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -118,8 +120,18 @@ func (r *RoundRobinBalancer) Next() *ProxyTarget {
|
||||
return t
|
||||
}
|
||||
|
||||
// Proxy returns an HTTP/WebSocket reverse proxy middleware.
|
||||
func Proxy(config ProxyConfig) echo.MiddlewareFunc {
|
||||
// Proxy returns a Proxy middleware.
|
||||
//
|
||||
// Proxy middleware forwards the request to upstream server using a configured load balancing technique.
|
||||
func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
|
||||
c := DefaultProxyConfig
|
||||
c.Balancer = balancer
|
||||
return ProxyWithConfig(c)
|
||||
}
|
||||
|
||||
// ProxyWithConfig returns a Proxy middleware with config.
|
||||
// See: `Proxy()`
|
||||
func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultLoggerConfig.Skipper
|
||||
@ -130,6 +142,10 @@ func Proxy(config ProxyConfig) echo.MiddlewareFunc {
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
tgt := config.Balancer.Next()
|
||||
|
10
vendor/github.com/labstack/echo/middleware/recover.go
generated
vendored
10
vendor/github.com/labstack/echo/middleware/recover.go
generated
vendored
@ -5,7 +5,6 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/gommon/color"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -64,17 +63,14 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var err error
|
||||
switch r := r.(type) {
|
||||
case error:
|
||||
err = r
|
||||
default:
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
stack := make([]byte, config.StackSize)
|
||||
length := runtime.Stack(stack, !config.DisableStackAll)
|
||||
if !config.DisablePrintStack {
|
||||
c.Logger().Printf("[%s] %s %s\n", color.Red("PANIC RECOVER"), err, stack[:length])
|
||||
c.Logger().Printf("[PANIC RECOVER] %v %s\n", err, stack[:length])
|
||||
}
|
||||
c.Error(err)
|
||||
}
|
||||
|
33
vendor/github.com/labstack/echo/middleware/static.go
generated
vendored
33
vendor/github.com/labstack/echo/middleware/static.go
generated
vendored
@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@ -66,7 +67,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
@ -75,17 +76,25 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
|
||||
p = c.Param("*")
|
||||
}
|
||||
p, err = echo.PathUnescape(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if config.HTML5 && path.Ext(p) == "" {
|
||||
return c.File(filepath.Join(config.Root, config.Index))
|
||||
if err = next(c); err != nil {
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
if config.HTML5 && he.Code == http.StatusNotFound {
|
||||
return c.File(filepath.Join(config.Root, config.Index))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
@ -99,7 +108,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
if os.IsNotExist(err) {
|
||||
return next(c)
|
||||
}
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
return c.File(index)
|
||||
@ -110,20 +119,20 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func listDir(name string, res *echo.Response) error {
|
||||
func listDir(name string, res *echo.Response) (err error) {
|
||||
dir, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
dirs, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
// Create a directory index
|
||||
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
||||
if _, err = fmt.Fprintf(res, "<pre>\n"); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
for _, d := range dirs {
|
||||
name := d.Name()
|
||||
@ -133,9 +142,9 @@ func listDir(name string, res *echo.Response) error {
|
||||
name += "/"
|
||||
}
|
||||
if _, err = fmt.Fprintf(res, "<a href=\"%s\" style=\"color: %s;\">%s</a>\n", name, color, name); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = fmt.Fprintf(res, "</pre>\n")
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
19
vendor/github.com/labstack/echo/response.go
generated
vendored
19
vendor/github.com/labstack/echo/response.go
generated
vendored
@ -11,11 +11,12 @@ type (
|
||||
// by an HTTP handler to construct an HTTP response.
|
||||
// See: https://golang.org/pkg/net/http/#ResponseWriter
|
||||
Response struct {
|
||||
Writer http.ResponseWriter
|
||||
Status int
|
||||
Size int64
|
||||
Committed bool
|
||||
echo *Echo
|
||||
echo *Echo
|
||||
beforeFuncs []func()
|
||||
Writer http.ResponseWriter
|
||||
Status int
|
||||
Size int64
|
||||
Committed bool
|
||||
}
|
||||
)
|
||||
|
||||
@ -34,6 +35,11 @@ func (r *Response) Header() http.Header {
|
||||
return r.Writer.Header()
|
||||
}
|
||||
|
||||
// Before registers a function which is called just before the response is written.
|
||||
func (r *Response) Before(fn func()) {
|
||||
r.beforeFuncs = append(r.beforeFuncs, fn)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
|
||||
// not called explicitly, the first call to Write will trigger an implicit
|
||||
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
|
||||
@ -43,6 +49,9 @@ func (r *Response) WriteHeader(code int) {
|
||||
r.echo.Logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
for _, fn := range r.beforeFuncs {
|
||||
fn()
|
||||
}
|
||||
r.Status = code
|
||||
r.Writer.WriteHeader(code)
|
||||
r.Committed = true
|
||||
|
2
vendor/github.com/labstack/echo/router.go
generated
vendored
2
vendor/github.com/labstack/echo/router.go
generated
vendored
@ -394,7 +394,7 @@ func (r *Router) Find(method, path string, c Context) {
|
||||
if cn = cn.findChildByKind(akind); cn == nil {
|
||||
if nn != nil {
|
||||
cn = nn
|
||||
nn = nil // Next
|
||||
nn = cn.parent // Next (Issue #954)
|
||||
search = ns
|
||||
if nk == pkind {
|
||||
goto Param
|
||||
|
12
vendor/github.com/labstack/echo/util_go17.go
generated
vendored
Normal file
12
vendor/github.com/labstack/echo/util_go17.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build go1.7, !go1.8
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// PathUnescape is wraps `url.QueryUnescape`
|
||||
func PathUnescape(s string) (string, error) {
|
||||
return url.QueryUnescape(s)
|
||||
}
|
10
vendor/github.com/labstack/echo/util_go18.go
generated
vendored
Normal file
10
vendor/github.com/labstack/echo/util_go18.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// +build go1.8
|
||||
|
||||
package echo
|
||||
|
||||
import "net/url"
|
||||
|
||||
// PathUnescape is wraps `url.PathUnescape`
|
||||
func PathUnescape(s string) (string, error) {
|
||||
return url.PathUnescape(s)
|
||||
}
|
80
vendor/github.com/lrstanley/girc/builtin.go
generated
vendored
80
vendor/github.com/lrstanley/girc/builtin.go
generated
vendored
@ -16,64 +16,62 @@ func (c *Client) registerBuiltins() {
|
||||
c.Handlers.mu.Lock()
|
||||
|
||||
// Built-in things that should always be supported.
|
||||
c.Handlers.register(true, RPL_WELCOME, HandlerFunc(func(c *Client, e Event) {
|
||||
go handleConnect(c, e)
|
||||
}))
|
||||
c.Handlers.register(true, PING, HandlerFunc(handlePING))
|
||||
c.Handlers.register(true, PONG, HandlerFunc(handlePONG))
|
||||
c.Handlers.register(true, true, RPL_WELCOME, HandlerFunc(handleConnect))
|
||||
c.Handlers.register(true, false, PING, HandlerFunc(handlePING))
|
||||
c.Handlers.register(true, false, PONG, HandlerFunc(handlePONG))
|
||||
|
||||
if !c.Config.disableTracking {
|
||||
// Joins/parts/anything that may add/remove/rename users.
|
||||
c.Handlers.register(true, JOIN, HandlerFunc(handleJOIN))
|
||||
c.Handlers.register(true, PART, HandlerFunc(handlePART))
|
||||
c.Handlers.register(true, KICK, HandlerFunc(handleKICK))
|
||||
c.Handlers.register(true, QUIT, HandlerFunc(handleQUIT))
|
||||
c.Handlers.register(true, NICK, HandlerFunc(handleNICK))
|
||||
c.Handlers.register(true, RPL_NAMREPLY, HandlerFunc(handleNAMES))
|
||||
c.Handlers.register(true, false, JOIN, HandlerFunc(handleJOIN))
|
||||
c.Handlers.register(true, false, PART, HandlerFunc(handlePART))
|
||||
c.Handlers.register(true, false, KICK, HandlerFunc(handleKICK))
|
||||
c.Handlers.register(true, false, QUIT, HandlerFunc(handleQUIT))
|
||||
c.Handlers.register(true, false, NICK, HandlerFunc(handleNICK))
|
||||
c.Handlers.register(true, false, RPL_NAMREPLY, HandlerFunc(handleNAMES))
|
||||
|
||||
// Modes.
|
||||
c.Handlers.register(true, MODE, HandlerFunc(handleMODE))
|
||||
c.Handlers.register(true, RPL_CHANNELMODEIS, HandlerFunc(handleMODE))
|
||||
c.Handlers.register(true, false, MODE, HandlerFunc(handleMODE))
|
||||
c.Handlers.register(true, false, RPL_CHANNELMODEIS, HandlerFunc(handleMODE))
|
||||
|
||||
// WHO/WHOX responses.
|
||||
c.Handlers.register(true, RPL_WHOREPLY, HandlerFunc(handleWHO))
|
||||
c.Handlers.register(true, RPL_WHOSPCRPL, HandlerFunc(handleWHO))
|
||||
c.Handlers.register(true, false, RPL_WHOREPLY, HandlerFunc(handleWHO))
|
||||
c.Handlers.register(true, false, RPL_WHOSPCRPL, HandlerFunc(handleWHO))
|
||||
|
||||
// Other misc. useful stuff.
|
||||
c.Handlers.register(true, TOPIC, HandlerFunc(handleTOPIC))
|
||||
c.Handlers.register(true, RPL_TOPIC, HandlerFunc(handleTOPIC))
|
||||
c.Handlers.register(true, RPL_MYINFO, HandlerFunc(handleMYINFO))
|
||||
c.Handlers.register(true, RPL_ISUPPORT, HandlerFunc(handleISUPPORT))
|
||||
c.Handlers.register(true, RPL_MOTDSTART, HandlerFunc(handleMOTD))
|
||||
c.Handlers.register(true, RPL_MOTD, HandlerFunc(handleMOTD))
|
||||
c.Handlers.register(true, false, TOPIC, HandlerFunc(handleTOPIC))
|
||||
c.Handlers.register(true, false, RPL_TOPIC, HandlerFunc(handleTOPIC))
|
||||
c.Handlers.register(true, false, RPL_MYINFO, HandlerFunc(handleMYINFO))
|
||||
c.Handlers.register(true, false, RPL_ISUPPORT, HandlerFunc(handleISUPPORT))
|
||||
c.Handlers.register(true, false, RPL_MOTDSTART, HandlerFunc(handleMOTD))
|
||||
c.Handlers.register(true, false, RPL_MOTD, HandlerFunc(handleMOTD))
|
||||
|
||||
// Keep users lastactive times up to date.
|
||||
c.Handlers.register(true, PRIVMSG, HandlerFunc(updateLastActive))
|
||||
c.Handlers.register(true, NOTICE, HandlerFunc(updateLastActive))
|
||||
c.Handlers.register(true, TOPIC, HandlerFunc(updateLastActive))
|
||||
c.Handlers.register(true, KICK, HandlerFunc(updateLastActive))
|
||||
c.Handlers.register(true, false, PRIVMSG, HandlerFunc(updateLastActive))
|
||||
c.Handlers.register(true, false, NOTICE, HandlerFunc(updateLastActive))
|
||||
c.Handlers.register(true, false, TOPIC, HandlerFunc(updateLastActive))
|
||||
c.Handlers.register(true, false, KICK, HandlerFunc(updateLastActive))
|
||||
|
||||
// CAP IRCv3-specific tracking and functionality.
|
||||
c.Handlers.register(true, CAP, HandlerFunc(handleCAP))
|
||||
c.Handlers.register(true, CAP_CHGHOST, HandlerFunc(handleCHGHOST))
|
||||
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY))
|
||||
c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
|
||||
c.Handlers.register(true, ALL_EVENTS, HandlerFunc(handleTags))
|
||||
c.Handlers.register(true, false, CAP, HandlerFunc(handleCAP))
|
||||
c.Handlers.register(true, false, CAP_CHGHOST, HandlerFunc(handleCHGHOST))
|
||||
c.Handlers.register(true, false, CAP_AWAY, HandlerFunc(handleAWAY))
|
||||
c.Handlers.register(true, false, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
|
||||
c.Handlers.register(true, false, ALL_EVENTS, HandlerFunc(handleTags))
|
||||
|
||||
// SASL IRCv3 support.
|
||||
c.Handlers.register(true, AUTHENTICATE, HandlerFunc(handleSASL))
|
||||
c.Handlers.register(true, RPL_SASLSUCCESS, HandlerFunc(handleSASL))
|
||||
c.Handlers.register(true, RPL_NICKLOCKED, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, ERR_SASLFAIL, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, ERR_SASLTOOLONG, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, ERR_SASLABORTED, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, RPL_SASLMECHS, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, false, AUTHENTICATE, HandlerFunc(handleSASL))
|
||||
c.Handlers.register(true, false, RPL_SASLSUCCESS, HandlerFunc(handleSASL))
|
||||
c.Handlers.register(true, false, RPL_NICKLOCKED, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, false, ERR_SASLFAIL, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, false, ERR_SASLTOOLONG, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, false, ERR_SASLABORTED, HandlerFunc(handleSASLError))
|
||||
c.Handlers.register(true, false, RPL_SASLMECHS, HandlerFunc(handleSASLError))
|
||||
}
|
||||
|
||||
// Nickname collisions.
|
||||
c.Handlers.register(true, ERR_NICKNAMEINUSE, HandlerFunc(nickCollisionHandler))
|
||||
c.Handlers.register(true, ERR_NICKCOLLISION, HandlerFunc(nickCollisionHandler))
|
||||
c.Handlers.register(true, ERR_UNAVAILRESOURCE, HandlerFunc(nickCollisionHandler))
|
||||
c.Handlers.register(true, false, ERR_NICKNAMEINUSE, HandlerFunc(nickCollisionHandler))
|
||||
c.Handlers.register(true, false, ERR_NICKCOLLISION, HandlerFunc(nickCollisionHandler))
|
||||
c.Handlers.register(true, false, ERR_UNAVAILRESOURCE, HandlerFunc(nickCollisionHandler))
|
||||
|
||||
c.Handlers.mu.Unlock()
|
||||
}
|
||||
@ -389,7 +387,7 @@ func handleISUPPORT(c *Client, e Event) {
|
||||
c.state.Lock()
|
||||
// Skip the first parameter, as it's our nickname.
|
||||
for i := 1; i < len(e.Params); i++ {
|
||||
j := strings.IndexByte(e.Params[i], 0x3D) // =
|
||||
j := strings.IndexByte(e.Params[i], '=')
|
||||
|
||||
if j < 1 || (j+1) == len(e.Params[i]) {
|
||||
c.state.serverOptions[e.Params[i]] = ""
|
||||
|
16
vendor/github.com/lrstanley/girc/cap.go
generated
vendored
16
vendor/github.com/lrstanley/girc/cap.go
generated
vendored
@ -136,7 +136,7 @@ func handleCAP(c *Client, e Event) {
|
||||
}
|
||||
|
||||
// Let them know which ones we'd like to enable.
|
||||
c.write(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(c.state.tmpCap, " ")})
|
||||
c.write(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(c.state.tmpCap, " "), EmptyTrailing: true})
|
||||
|
||||
// Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
|
||||
// due to cap-notify, we can re-evaluate what we can support.
|
||||
@ -375,11 +375,11 @@ func handleTags(c *Client, e Event) {
|
||||
}
|
||||
|
||||
const (
|
||||
prefixTag byte = 0x40 // @
|
||||
prefixTagValue byte = 0x3D // =
|
||||
prefixUserTag byte = 0x2B // +
|
||||
tagSeparator byte = 0x3B // ;
|
||||
maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included.
|
||||
prefixTag byte = '@'
|
||||
prefixTagValue byte = '='
|
||||
prefixUserTag byte = '+'
|
||||
tagSeparator byte = ';'
|
||||
maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included.
|
||||
)
|
||||
|
||||
// Tags represents the key-value pairs in IRCv3 message tags. The map contains
|
||||
@ -618,7 +618,7 @@ func validTag(name string) bool {
|
||||
|
||||
for i := 0; i < len(name); i++ {
|
||||
// A-Z, a-z, 0-9, -/._
|
||||
if (name[i] < 0x41 || name[i] > 0x5A) && (name[i] < 0x61 || name[i] > 0x7A) && (name[i] < 0x2D || name[i] > 0x39) && name[i] != 0x5F {
|
||||
if (name[i] < 'A' || name[i] > 'Z') && (name[i] < 'a' || name[i] > 'z') && (name[i] < '-' || name[i] > '9') && name[i] != '_' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -631,7 +631,7 @@ func validTag(name string) bool {
|
||||
func validTagValue(value string) bool {
|
||||
for i := 0; i < len(value); i++ {
|
||||
// Don't allow any invisible chars within the tag, or semicolons.
|
||||
if value[i] < 0x21 || value[i] > 0x7E || value[i] == 0x3B {
|
||||
if value[i] < '!' || value[i] > '~' || value[i] == ';' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
105
vendor/github.com/lrstanley/girc/client.go
generated
vendored
105
vendor/github.com/lrstanley/girc/client.go
generated
vendored
@ -191,18 +191,6 @@ func (conf *Config) isValid() error {
|
||||
// connected.
|
||||
var ErrNotConnected = errors.New("client is not connected to server")
|
||||
|
||||
// ErrDisconnected is called when Config.Retries is less than 1, and we
|
||||
// non-intentionally disconnected from the server.
|
||||
var ErrDisconnected = errors.New("unexpectedly disconnected")
|
||||
|
||||
// ErrInvalidTarget should be returned if the target which you are
|
||||
// attempting to send an event to is invalid or doesn't match RFC spec.
|
||||
type ErrInvalidTarget struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
func (e *ErrInvalidTarget) Error() string { return "invalid target: " + e.Target }
|
||||
|
||||
// New creates a new IRC client with the specified server, name and config.
|
||||
func New(config Config) *Client {
|
||||
c := &Client{
|
||||
@ -253,6 +241,37 @@ func (c *Client) String() string {
|
||||
)
|
||||
}
|
||||
|
||||
// TLSConnectionState returns the TLS connection state from tls.Conn{}, which
|
||||
// is useful to return needed TLS fingerprint info, certificates, verify cert
|
||||
// expiration dates, etc. Will only return an error if the underlying
|
||||
// connection wasn't established using TLS (see ErrConnNotTLS), or if the
|
||||
// client isn't connected.
|
||||
func (c *Client) TLSConnectionState() (*tls.ConnectionState, error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if c.conn == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
|
||||
c.conn.mu.RLock()
|
||||
defer c.conn.mu.RUnlock()
|
||||
|
||||
if !c.conn.connected {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
|
||||
if tlsConn, ok := c.conn.sock.(*tls.Conn); ok {
|
||||
cs := tlsConn.ConnectionState()
|
||||
return &cs, nil
|
||||
}
|
||||
|
||||
return nil, ErrConnNotTLS
|
||||
}
|
||||
|
||||
// ErrConnNotTLS is returned when Client.TLSConnectionState() is called, and
|
||||
// the connection to the server wasn't made with TLS.
|
||||
var ErrConnNotTLS = errors.New("underlying connection is not tls")
|
||||
|
||||
// Close closes the network connection to the server, and sends a STOPPED
|
||||
// event. This should cause Connect() to return with nil. This should be
|
||||
// safe to call multiple times. See Connect()'s documentation on how
|
||||
@ -387,7 +406,7 @@ func (c *Client) ConnSince() (since *time.Duration, err error) {
|
||||
}
|
||||
|
||||
// IsConnected returns true if the client is connected to the server.
|
||||
func (c *Client) IsConnected() (connected bool) {
|
||||
func (c *Client) IsConnected() bool {
|
||||
c.mu.RLock()
|
||||
if c.conn == nil {
|
||||
c.mu.RUnlock()
|
||||
@ -395,7 +414,7 @@ func (c *Client) IsConnected() (connected bool) {
|
||||
}
|
||||
|
||||
c.conn.mu.RLock()
|
||||
connected = c.conn.connected
|
||||
connected := c.conn.connected
|
||||
c.conn.mu.RUnlock()
|
||||
c.mu.RUnlock()
|
||||
|
||||
@ -445,9 +464,9 @@ func (c *Client) GetHost() string {
|
||||
return c.state.host
|
||||
}
|
||||
|
||||
// Channels returns the active list of channels that the client is in.
|
||||
// ChannelList returns the active list of channel names that the client is in.
|
||||
// Panics if tracking is disabled.
|
||||
func (c *Client) Channels() []string {
|
||||
func (c *Client) ChannelList() []string {
|
||||
c.panicIfNotTracking()
|
||||
|
||||
c.state.RLock()
|
||||
@ -463,9 +482,26 @@ func (c *Client) Channels() []string {
|
||||
return channels
|
||||
}
|
||||
|
||||
// Users returns the active list of users that the client is tracking across
|
||||
// all files. Panics if tracking is disabled.
|
||||
func (c *Client) Users() []string {
|
||||
// Channels returns the active channels that the client is in. Panics if
|
||||
// tracking is disabled.
|
||||
func (c *Client) Channels() []*Channel {
|
||||
c.panicIfNotTracking()
|
||||
|
||||
c.state.RLock()
|
||||
channels := make([]*Channel, len(c.state.channels))
|
||||
var i int
|
||||
for channel := range c.state.channels {
|
||||
channels[i] = c.state.channels[channel].Copy()
|
||||
i++
|
||||
}
|
||||
c.state.RUnlock()
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
// UserList returns the active list of nicknames that the client is tracking
|
||||
// across all networks. Panics if tracking is disabled.
|
||||
func (c *Client) UserList() []string {
|
||||
c.panicIfNotTracking()
|
||||
|
||||
c.state.RLock()
|
||||
@ -481,6 +517,23 @@ func (c *Client) Users() []string {
|
||||
return users
|
||||
}
|
||||
|
||||
// Users returns the active users that the client is tracking across all
|
||||
// networks. Panics if tracking is disabled.
|
||||
func (c *Client) Users() []*User {
|
||||
c.panicIfNotTracking()
|
||||
|
||||
c.state.RLock()
|
||||
users := make([]*User, len(c.state.users))
|
||||
var i int
|
||||
for user := range c.state.users {
|
||||
users[i] = c.state.users[user].Copy()
|
||||
i++
|
||||
}
|
||||
c.state.RUnlock()
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
// LookupChannel looks up a given channel in state. If the channel doesn't
|
||||
// exist, nil is returned. Panics if tracking is disabled.
|
||||
func (c *Client) LookupChannel(name string) *Channel {
|
||||
@ -562,30 +615,30 @@ func (c *Client) NetworkName() (name string) {
|
||||
// supplied this information during connection. May be empty if the server
|
||||
// does not support RPL_MYINFO. Will panic if used when tracking has been
|
||||
// disabled.
|
||||
func (c *Client) ServerVersion() (version string) {
|
||||
func (c *Client) ServerVersion() string {
|
||||
c.panicIfNotTracking()
|
||||
|
||||
version, _ = c.GetServerOption("VERSION")
|
||||
version, _ := c.GetServerOption("VERSION")
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// ServerMOTD returns the servers message of the day, if the server has sent
|
||||
// it upon connect. Will panic if used when tracking has been disabled.
|
||||
func (c *Client) ServerMOTD() (motd string) {
|
||||
func (c *Client) ServerMOTD() string {
|
||||
c.panicIfNotTracking()
|
||||
|
||||
c.state.RLock()
|
||||
motd = c.state.motd
|
||||
motd := c.state.motd
|
||||
c.state.RUnlock()
|
||||
|
||||
return motd
|
||||
}
|
||||
|
||||
// Lag is the latency between the server and the client. This is measured by
|
||||
// determining the difference in time between when we ping the server, and
|
||||
// Latency is the latency between the server and the client. This is measured
|
||||
// by determining the difference in time between when we ping the server, and
|
||||
// when we receive a pong.
|
||||
func (c *Client) Lag() time.Duration {
|
||||
func (c *Client) Latency() time.Duration {
|
||||
c.mu.RLock()
|
||||
c.conn.mu.RLock()
|
||||
delta := c.conn.lastPong.Sub(c.conn.lastPing)
|
||||
|
10
vendor/github.com/lrstanley/girc/cmdhandler/cmd.go
generated
vendored
10
vendor/github.com/lrstanley/girc/cmdhandler/cmd.go
generated
vendored
@ -12,8 +12,9 @@ import (
|
||||
|
||||
// Input is a wrapper for events, based around private messages.
|
||||
type Input struct {
|
||||
Origin *girc.Event
|
||||
Args []string
|
||||
Origin *girc.Event
|
||||
Args []string
|
||||
RawArgs string
|
||||
}
|
||||
|
||||
// Command is an IRC command, supporting aliases, help documentation and easy
|
||||
@ -189,8 +190,9 @@ func (ch *CmdHandler) Execute(client *girc.Client, event girc.Event) {
|
||||
}
|
||||
|
||||
in := &Input{
|
||||
Origin: &event,
|
||||
Args: args,
|
||||
Origin: &event,
|
||||
Args: args,
|
||||
RawArgs: parsed[2],
|
||||
}
|
||||
|
||||
go cmd.Fn(client, in)
|
||||
|
261
vendor/github.com/lrstanley/girc/commands.go
generated
vendored
261
vendor/github.com/lrstanley/girc/commands.go
generated
vendored
@ -16,18 +16,13 @@ type Commands struct {
|
||||
}
|
||||
|
||||
// Nick changes the client nickname.
|
||||
func (cmd *Commands) Nick(name string) error {
|
||||
if !IsValidNick(name) {
|
||||
return &ErrInvalidTarget{Target: name}
|
||||
}
|
||||
|
||||
func (cmd *Commands) Nick(name string) {
|
||||
cmd.c.Send(&Event{Command: NICK, Params: []string{name}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Join attempts to enter a list of IRC channels, at bulk if possible to
|
||||
// prevent sending extensive JOIN commands.
|
||||
func (cmd *Commands) Join(channels ...string) error {
|
||||
func (cmd *Commands) Join(channels ...string) {
|
||||
// We can join multiple channels at once, however we need to ensure that
|
||||
// we are not exceeding the line length. (see maxLength)
|
||||
max := maxLength - len(JOIN) - 1
|
||||
@ -35,10 +30,6 @@ func (cmd *Commands) Join(channels ...string) error {
|
||||
var buffer string
|
||||
|
||||
for i := 0; i < len(channels); i++ {
|
||||
if !IsValidChannel(channels[i]) {
|
||||
return &ErrInvalidTarget{Target: channels[i]}
|
||||
}
|
||||
|
||||
if len(buffer+","+channels[i]) > max {
|
||||
cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
|
||||
buffer = ""
|
||||
@ -53,91 +44,74 @@ func (cmd *Commands) Join(channels ...string) error {
|
||||
|
||||
if i == len(channels)-1 {
|
||||
cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JoinKey attempts to enter an IRC channel with a password.
|
||||
func (cmd *Commands) JoinKey(channel, password string) error {
|
||||
if !IsValidChannel(channel) {
|
||||
return &ErrInvalidTarget{Target: channel}
|
||||
}
|
||||
|
||||
func (cmd *Commands) JoinKey(channel, password string) {
|
||||
cmd.c.Send(&Event{Command: JOIN, Params: []string{channel, password}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Part leaves an IRC channel.
|
||||
func (cmd *Commands) Part(channel, message string) error {
|
||||
if !IsValidChannel(channel) {
|
||||
return &ErrInvalidTarget{Target: channel}
|
||||
func (cmd *Commands) Part(channels ...string) {
|
||||
for i := 0; i < len(channels); i++ {
|
||||
cmd.c.Send(&Event{Command: PART, Params: []string{channels[i]}})
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// PartMessage leaves an IRC channel with a specified leave message.
|
||||
func (cmd *Commands) PartMessage(channel, message string) error {
|
||||
if !IsValidChannel(channel) {
|
||||
return &ErrInvalidTarget{Target: channel}
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message})
|
||||
return nil
|
||||
func (cmd *Commands) PartMessage(channel, message string) {
|
||||
cmd.c.Send(&Event{Command: PART, Params: []string{channel}, Trailing: message, EmptyTrailing: true})
|
||||
}
|
||||
|
||||
// SendCTCP sends a CTCP request to target. Note that this method uses
|
||||
// PRIVMSG specifically.
|
||||
func (cmd *Commands) SendCTCP(target, ctcpType, message string) error {
|
||||
// PRIVMSG specifically. ctcpType is the CTCP command, e.g. "FINGER", "TIME",
|
||||
// "VERSION", etc.
|
||||
func (cmd *Commands) SendCTCP(target, ctcpType, message string) {
|
||||
out := encodeCTCPRaw(ctcpType, message)
|
||||
if out == "" {
|
||||
return errors.New("invalid CTCP")
|
||||
panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
|
||||
}
|
||||
|
||||
return cmd.Message(target, out)
|
||||
cmd.Message(target, out)
|
||||
}
|
||||
|
||||
// SendCTCPf sends a CTCP request to target using a specific format. Note that
|
||||
// this method uses PRIVMSG specifically.
|
||||
func (cmd *Commands) SendCTCPf(target, ctcpType, format string, a ...interface{}) error {
|
||||
return cmd.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...))
|
||||
// this method uses PRIVMSG specifically. ctcpType is the CTCP command, e.g.
|
||||
// "FINGER", "TIME", "VERSION", etc.
|
||||
func (cmd *Commands) SendCTCPf(target, ctcpType, format string, a ...interface{}) {
|
||||
cmd.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// SendCTCPReplyf sends a CTCP response to target using a specific format.
|
||||
// Note that this method uses NOTICE specifically.
|
||||
func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) error {
|
||||
return cmd.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...))
|
||||
// Note that this method uses NOTICE specifically. ctcpType is the CTCP
|
||||
// command, e.g. "FINGER", "TIME", "VERSION", etc.
|
||||
func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) {
|
||||
cmd.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// SendCTCPReply sends a CTCP response to target. Note that this method uses
|
||||
// NOTICE specifically.
|
||||
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) error {
|
||||
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) {
|
||||
out := encodeCTCPRaw(ctcpType, message)
|
||||
if out == "" {
|
||||
return errors.New("invalid CTCP")
|
||||
panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
|
||||
}
|
||||
|
||||
return cmd.Notice(target, out)
|
||||
cmd.Notice(target, out)
|
||||
}
|
||||
|
||||
// Message sends a PRIVMSG to target (either channel, service, or user).
|
||||
func (cmd *Commands) Message(target, message string) error {
|
||||
if !IsValidNick(target) && !IsValidChannel(target) {
|
||||
return &ErrInvalidTarget{Target: target}
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message})
|
||||
return nil
|
||||
func (cmd *Commands) Message(target, message string) {
|
||||
cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message, EmptyTrailing: true})
|
||||
}
|
||||
|
||||
// Messagef sends a formated PRIVMSG to target (either channel, service, or
|
||||
// user).
|
||||
func (cmd *Commands) Messagef(target, format string, a ...interface{}) error {
|
||||
return cmd.Message(target, fmt.Sprintf(format, a...))
|
||||
func (cmd *Commands) Messagef(target, format string, a ...interface{}) {
|
||||
cmd.Message(target, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// ErrInvalidSource is returned when a method needs to know the origin of an
|
||||
@ -146,94 +120,95 @@ func (cmd *Commands) Messagef(target, format string, a ...interface{}) error {
|
||||
var ErrInvalidSource = errors.New("event has nil or invalid source address")
|
||||
|
||||
// Reply sends a reply to channel or user, based on where the supplied event
|
||||
// originated from. See also ReplyTo().
|
||||
func (cmd *Commands) Reply(event Event, message string) error {
|
||||
// originated from. See also ReplyTo(). Panics if the incoming event has no
|
||||
// source.
|
||||
func (cmd *Commands) Reply(event Event, message string) {
|
||||
if event.Source == nil {
|
||||
return ErrInvalidSource
|
||||
panic(ErrInvalidSource)
|
||||
}
|
||||
|
||||
if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
|
||||
return cmd.Message(event.Params[0], message)
|
||||
cmd.Message(event.Params[0], message)
|
||||
return
|
||||
}
|
||||
|
||||
return cmd.Message(event.Source.Name, message)
|
||||
cmd.Message(event.Source.Name, message)
|
||||
}
|
||||
|
||||
// Replyf sends a reply to channel or user with a format string, based on
|
||||
// where the supplied event originated from. See also ReplyTof().
|
||||
func (cmd *Commands) Replyf(event Event, format string, a ...interface{}) error {
|
||||
return cmd.Reply(event, fmt.Sprintf(format, a...))
|
||||
// where the supplied event originated from. See also ReplyTof(). Panics if
|
||||
// the incoming event has no source.
|
||||
func (cmd *Commands) Replyf(event Event, format string, a ...interface{}) {
|
||||
cmd.Reply(event, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// ReplyTo sends a reply to a channel or user, based on where the supplied
|
||||
// event originated from. ReplyTo(), when originating from a channel will
|
||||
// default to replying with "<user>, <message>". See also Reply().
|
||||
func (cmd *Commands) ReplyTo(event Event, message string) error {
|
||||
// default to replying with "<user>, <message>". See also Reply(). Panics if
|
||||
// the incoming event has no source.
|
||||
func (cmd *Commands) ReplyTo(event Event, message string) {
|
||||
if event.Source == nil {
|
||||
return ErrInvalidSource
|
||||
panic(ErrInvalidSource)
|
||||
}
|
||||
|
||||
if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
|
||||
return cmd.Message(event.Params[0], event.Source.Name+", "+message)
|
||||
cmd.Message(event.Params[0], event.Source.Name+", "+message)
|
||||
return
|
||||
}
|
||||
|
||||
return cmd.Message(event.Source.Name, message)
|
||||
cmd.Message(event.Source.Name, message)
|
||||
}
|
||||
|
||||
// ReplyTof sends a reply to a channel or user with a format string, based
|
||||
// on where the supplied event originated from. ReplyTo(), when originating
|
||||
// from a channel will default to replying with "<user>, <message>". See
|
||||
// also Replyf().
|
||||
func (cmd *Commands) ReplyTof(event Event, format string, a ...interface{}) error {
|
||||
return cmd.ReplyTo(event, fmt.Sprintf(format, a...))
|
||||
// also Replyf(). Panics if the incoming event has no source.
|
||||
func (cmd *Commands) ReplyTof(event Event, format string, a ...interface{}) {
|
||||
cmd.ReplyTo(event, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Action sends a PRIVMSG ACTION (/me) to target (either channel, service,
|
||||
// or user).
|
||||
func (cmd *Commands) Action(target, message string) error {
|
||||
if !IsValidNick(target) && !IsValidChannel(target) {
|
||||
return &ErrInvalidTarget{Target: target}
|
||||
}
|
||||
|
||||
func (cmd *Commands) Action(target, message string) {
|
||||
cmd.c.Send(&Event{
|
||||
Command: PRIVMSG,
|
||||
Params: []string{target},
|
||||
Trailing: fmt.Sprintf("\001ACTION %s\001", message),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel,
|
||||
// service, or user).
|
||||
func (cmd *Commands) Actionf(target, format string, a ...interface{}) error {
|
||||
return cmd.Action(target, fmt.Sprintf(format, a...))
|
||||
func (cmd *Commands) Actionf(target, format string, a ...interface{}) {
|
||||
cmd.Action(target, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Notice sends a NOTICE to target (either channel, service, or user).
|
||||
func (cmd *Commands) Notice(target, message string) error {
|
||||
if !IsValidNick(target) && !IsValidChannel(target) {
|
||||
return &ErrInvalidTarget{Target: target}
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message})
|
||||
return nil
|
||||
func (cmd *Commands) Notice(target, message string) {
|
||||
cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message, EmptyTrailing: true})
|
||||
}
|
||||
|
||||
// Noticef sends a formated NOTICE to target (either channel, service, or
|
||||
// user).
|
||||
func (cmd *Commands) Noticef(target, format string, a ...interface{}) error {
|
||||
return cmd.Notice(target, fmt.Sprintf(format, a...))
|
||||
func (cmd *Commands) Noticef(target, format string, a ...interface{}) {
|
||||
cmd.Notice(target, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// SendRaw sends a raw string back to the server, without carriage returns
|
||||
// or newlines.
|
||||
func (cmd *Commands) SendRaw(raw string) error {
|
||||
e := ParseEvent(raw)
|
||||
if e == nil {
|
||||
return errors.New("invalid event: " + raw)
|
||||
// SendRaw sends a raw string (or multiple) to the server, without carriage
|
||||
// returns or newlines. Returns an error if one of the raw strings cannot be
|
||||
// properly parsed.
|
||||
func (cmd *Commands) SendRaw(raw ...string) error {
|
||||
var event *Event
|
||||
|
||||
for i := 0; i < len(raw); i++ {
|
||||
event = ParseEvent(raw[i])
|
||||
if event == nil {
|
||||
return errors.New("invalid event: " + raw[i])
|
||||
}
|
||||
|
||||
cmd.c.Send(event)
|
||||
}
|
||||
|
||||
cmd.c.Send(e)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -246,31 +221,26 @@ func (cmd *Commands) SendRawf(format string, a ...interface{}) error {
|
||||
// Topic sets the topic of channel to message. Does not verify the length
|
||||
// of the topic.
|
||||
func (cmd *Commands) Topic(channel, message string) {
|
||||
cmd.c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message})
|
||||
cmd.c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message, EmptyTrailing: true})
|
||||
}
|
||||
|
||||
// Who sends a WHO query to the server, which will attempt WHOX by default.
|
||||
// See http://faerion.sourceforge.net/doc/irc/whox.var for more details. This
|
||||
// sends "%tcuhnr,2" per default. Do not use "1" as this will conflict with
|
||||
// girc's builtin tracking functionality.
|
||||
func (cmd *Commands) Who(target string) error {
|
||||
if !IsValidNick(target) && !IsValidChannel(target) && !IsValidUser(target) {
|
||||
return &ErrInvalidTarget{Target: target}
|
||||
func (cmd *Commands) Who(users ...string) {
|
||||
for i := 0; i < len(users); i++ {
|
||||
cmd.c.Send(&Event{Command: WHO, Params: []string{users[i], "%tcuhnr,2"}})
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Whois sends a WHOIS query to the server, targeted at a specific user.
|
||||
// as WHOIS is a bit slower, you may want to use WHO for brief user info.
|
||||
func (cmd *Commands) Whois(nick string) error {
|
||||
if !IsValidNick(nick) {
|
||||
return &ErrInvalidTarget{Target: nick}
|
||||
// Whois sends a WHOIS query to the server, targeted at a specific user (or
|
||||
// set of users). As WHOIS is a bit slower, you may want to use WHO for brief
|
||||
// user info.
|
||||
func (cmd *Commands) Whois(users ...string) {
|
||||
for i := 0; i < len(users); i++ {
|
||||
cmd.c.Send(&Event{Command: WHOIS, Params: []string{users[i]}})
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: WHOIS, Params: []string{nick}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ping sends a PING query to the server, with a specific identifier that
|
||||
@ -294,36 +264,40 @@ func (cmd *Commands) Oper(user, pass string) {
|
||||
// Kick sends a KICK query to the server, attempting to kick nick from
|
||||
// channel, with reason. If reason is blank, one will not be sent to the
|
||||
// server.
|
||||
func (cmd *Commands) Kick(channel, nick, reason string) error {
|
||||
if !IsValidChannel(channel) {
|
||||
return &ErrInvalidTarget{Target: channel}
|
||||
}
|
||||
|
||||
if !IsValidNick(nick) {
|
||||
return &ErrInvalidTarget{Target: nick}
|
||||
}
|
||||
|
||||
func (cmd *Commands) Kick(channel, user, reason string) {
|
||||
if reason != "" {
|
||||
cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason})
|
||||
return nil
|
||||
cmd.c.Send(&Event{Command: KICK, Params: []string{channel, user}, Trailing: reason, EmptyTrailing: true})
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}})
|
||||
return nil
|
||||
cmd.c.Send(&Event{Command: KICK, Params: []string{channel, user}})
|
||||
}
|
||||
|
||||
// Ban adds the +b mode on the given mask on a channel.
|
||||
func (cmd *Commands) Ban(channel, mask string) {
|
||||
cmd.Mode(channel, "+b", mask)
|
||||
}
|
||||
|
||||
// Unban removes the +b mode on the given mask on a channel.
|
||||
func (cmd *Commands) Unban(channel, mask string) {
|
||||
cmd.Mode(channel, "-b", mask)
|
||||
}
|
||||
|
||||
// Mode sends a mode change to the server which should be applied to target
|
||||
// (usually a channel or user), along with a set of modes (generally "+m",
|
||||
// "+mmmm", or "-m", where "m" is the mode you want to change). Params is only
|
||||
// needed if the mode change requires a parameter (ban or invite-only exclude.)
|
||||
func (cmd *Commands) Mode(target, modes string, params ...string) {
|
||||
out := []string{target, modes}
|
||||
out = append(out, params...)
|
||||
|
||||
cmd.c.Send(&Event{Command: MODE, Params: out})
|
||||
}
|
||||
|
||||
// Invite sends a INVITE query to the server, to invite nick to channel.
|
||||
func (cmd *Commands) Invite(channel, nick string) error {
|
||||
if !IsValidChannel(channel) {
|
||||
return &ErrInvalidTarget{Target: channel}
|
||||
func (cmd *Commands) Invite(channel string, users ...string) {
|
||||
for i := 0; i < len(users); i++ {
|
||||
cmd.c.Send(&Event{Command: INVITE, Params: []string{users[i], channel}})
|
||||
}
|
||||
|
||||
if !IsValidNick(nick) {
|
||||
return &ErrInvalidTarget{Target: nick}
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: INVITE, Params: []string{nick, channel}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Away sends a AWAY query to the server, suggesting that the client is no
|
||||
@ -348,10 +322,10 @@ func (cmd *Commands) Back() {
|
||||
// Supports multiple channels at once, in hopes it will reduce extensive
|
||||
// LIST queries to the server. Supply no channels to run a list against the
|
||||
// entire server (warning, that may mean LOTS of channels!)
|
||||
func (cmd *Commands) List(channels ...string) error {
|
||||
func (cmd *Commands) List(channels ...string) {
|
||||
if len(channels) == 0 {
|
||||
cmd.c.Send(&Event{Command: LIST})
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// We can LIST multiple channels at once, however we need to ensure that
|
||||
@ -361,10 +335,6 @@ func (cmd *Commands) List(channels ...string) error {
|
||||
var buffer string
|
||||
|
||||
for i := 0; i < len(channels); i++ {
|
||||
if !IsValidChannel(channels[i]) {
|
||||
return &ErrInvalidTarget{Target: channels[i]}
|
||||
}
|
||||
|
||||
if len(buffer+","+channels[i]) > max {
|
||||
cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
|
||||
buffer = ""
|
||||
@ -379,20 +349,13 @@ func (cmd *Commands) List(channels ...string) error {
|
||||
|
||||
if i == len(channels)-1 {
|
||||
cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Whowas sends a WHOWAS query to the server. amount is the amount of results
|
||||
// you want back.
|
||||
func (cmd *Commands) Whowas(nick string, amount int) error {
|
||||
if !IsValidNick(nick) {
|
||||
return &ErrInvalidTarget{Target: nick}
|
||||
}
|
||||
|
||||
cmd.c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}})
|
||||
return nil
|
||||
func (cmd *Commands) Whowas(user string, amount int) {
|
||||
cmd.c.Send(&Event{Command: WHOWAS, Params: []string{user, string(amount)}})
|
||||
}
|
||||
|
6
vendor/github.com/lrstanley/girc/ctcp.go
generated
vendored
6
vendor/github.com/lrstanley/girc/ctcp.go
generated
vendored
@ -58,7 +58,7 @@ func decodeCTCP(e *Event) *CTCPEvent {
|
||||
if s < 0 {
|
||||
for i := 0; i < len(text); i++ {
|
||||
// Check for A-Z, 0-9.
|
||||
if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
|
||||
if (text[i] < 'A' || text[i] > 'Z') && (text[i] < '0' || text[i] > '9') {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -74,7 +74,7 @@ func decodeCTCP(e *Event) *CTCPEvent {
|
||||
// Loop through checking the tag first.
|
||||
for i := 0; i < s; i++ {
|
||||
// Check for A-Z, 0-9.
|
||||
if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
|
||||
if (text[i] < 'A' || text[i] > 'Z') && (text[i] < '0' || text[i] > '9') {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -168,7 +168,7 @@ func (c *CTCP) parseCMD(cmd string) string {
|
||||
|
||||
for i := 0; i < len(cmd); i++ {
|
||||
// Check for A-Z, 0-9.
|
||||
if (cmd[i] < 0x41 || cmd[i] > 0x5A) && (cmd[i] < 0x30 || cmd[i] > 0x39) {
|
||||
if (cmd[i] < 'A' || cmd[i] > 'Z') && (cmd[i] < '0' || cmd[i] > '9') {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
12
vendor/github.com/lrstanley/girc/event.go
generated
vendored
12
vendor/github.com/lrstanley/girc/event.go
generated
vendored
@ -11,8 +11,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
eventSpace byte = 0x20 // Separator.
|
||||
maxLength = 510 // Maximum length is 510 (2 for line endings).
|
||||
eventSpace byte = ' ' // Separator.
|
||||
maxLength = 510 // Maximum length is 510 (2 for line endings).
|
||||
)
|
||||
|
||||
// cutCRFunc is used to trim CR characters from prefixes/messages.
|
||||
@ -256,7 +256,7 @@ func (e *Event) Bytes() []byte {
|
||||
|
||||
// Strip newlines and carriage returns.
|
||||
for i := 0; i < len(out); i++ {
|
||||
if out[i] == 0x0A || out[i] == 0x0D {
|
||||
if out[i] == '\n' || out[i] == '\r' {
|
||||
out = append(out[:i], out[i+1:]...)
|
||||
i-- // Decrease the index so we can pick up where we left off.
|
||||
}
|
||||
@ -432,9 +432,9 @@ func (e *Event) StripAction() string {
|
||||
}
|
||||
|
||||
const (
|
||||
messagePrefix byte = 0x3A // ":" -- prefix or last argument
|
||||
prefixIdent byte = 0x21 // "!" -- username
|
||||
prefixHost byte = 0x40 // "@" -- hostname
|
||||
messagePrefix byte = ':' // Prefix or last argument.
|
||||
prefixIdent byte = '!' // Username.
|
||||
prefixHost byte = '@' // Hostname.
|
||||
)
|
||||
|
||||
// Source represents the sender of an IRC event, see RFC1459 section 2.3.1.
|
||||
|
46
vendor/github.com/lrstanley/girc/format.go
generated
vendored
46
vendor/github.com/lrstanley/girc/format.go
generated
vendored
@ -12,8 +12,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
fmtOpenChar = 0x7B // {
|
||||
fmtCloseChar = 0x7D // }
|
||||
fmtOpenChar = '{'
|
||||
fmtCloseChar = '}'
|
||||
)
|
||||
|
||||
var fmtColors = map[string]int{
|
||||
@ -113,7 +113,7 @@ func Fmt(text string) string {
|
||||
|
||||
if last > -1 {
|
||||
// A-Z, a-z, and ","
|
||||
if text[i] != 0x2c && (text[i] <= 0x41 || text[i] >= 0x5a) && (text[i] <= 0x61 || text[i] >= 0x7a) {
|
||||
if text[i] != ',' && (text[i] <= 'A' || text[i] >= 'Z') && (text[i] <= 'a' || text[i] >= 'z') {
|
||||
last = -1
|
||||
continue
|
||||
}
|
||||
@ -127,10 +127,10 @@ func Fmt(text string) string {
|
||||
// See Fmt() for more information.
|
||||
func TrimFmt(text string) string {
|
||||
for color := range fmtColors {
|
||||
text = strings.Replace(text, "{"+color+"}", "", -1)
|
||||
text = strings.Replace(text, string(fmtOpenChar)+color+string(fmtCloseChar), "", -1)
|
||||
}
|
||||
for code := range fmtCodes {
|
||||
text = strings.Replace(text, "{"+code+"}", "", -1)
|
||||
text = strings.Replace(text, string(fmtOpenChar)+code+string(fmtCloseChar), "", -1)
|
||||
}
|
||||
|
||||
return text
|
||||
@ -175,9 +175,10 @@ func IsValidChannel(channel string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// #, +, !<channelid>, or &
|
||||
// Including "*" in the prefix list, as this is commonly used (e.g. ZNC)
|
||||
if bytes.IndexByte([]byte{0x21, 0x23, 0x26, 0x2A, 0x2B}, channel[0]) == -1 {
|
||||
// #, +, !<channelid>, ~, or &
|
||||
// Including "*" and "~" in the prefix list, as these are commonly used
|
||||
// (e.g. ZNC.)
|
||||
if bytes.IndexByte([]byte{'!', '#', '&', '*', '~', '+'}, channel[0]) == -1 {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -186,14 +187,14 @@ func IsValidChannel(channel string) bool {
|
||||
// 1 (prefix) + 5 (id) + 1 (+, channel name)
|
||||
// On some networks, this may be extended with ISUPPORT capabilities,
|
||||
// however this is extremely uncommon.
|
||||
if channel[0] == 0x21 {
|
||||
if channel[0] == '!' {
|
||||
if len(channel) < 7 {
|
||||
return false
|
||||
}
|
||||
|
||||
// check for valid ID
|
||||
for i := 1; i < 6; i++ {
|
||||
if (channel[i] < 0x30 || channel[i] > 0x39) && (channel[i] < 0x41 || channel[i] > 0x5A) {
|
||||
if (channel[i] < '0' || channel[i] > '9') && (channel[i] < 'A' || channel[i] > 'Z') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -222,17 +223,15 @@ func IsValidNick(nick string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
nick = ToRFC1459(nick)
|
||||
|
||||
// Check the first index. Some characters aren't allowed for the first
|
||||
// index of an IRC nickname.
|
||||
if nick[0] < 0x41 || nick[0] > 0x7D {
|
||||
// a-z, A-Z, and _\[]{}^|
|
||||
if (nick[0] < 'A' || nick[0] > '}') && nick[0] != '?' {
|
||||
// a-z, A-Z, '_\[]{}^|', and '?' in the case of znc.
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 1; i < len(nick); i++ {
|
||||
if (nick[i] < 0x41 || nick[i] > 0x7D) && (nick[i] < 0x30 || nick[i] > 0x39) && nick[i] != 0x2D {
|
||||
if (nick[i] < 'A' || nick[i] > '}') && (nick[i] < '0' || nick[i] > '9') && nick[i] != '-' {
|
||||
// a-z, A-Z, 0-9, -, and _\[]{}^|
|
||||
return false
|
||||
}
|
||||
@ -261,10 +260,8 @@ func IsValidUser(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
name = ToRFC1459(name)
|
||||
|
||||
// "~" is prepended (commonly) if there was no ident server response.
|
||||
if name[0] == 0x7E {
|
||||
if name[0] == '~' {
|
||||
// Means name only contained "~".
|
||||
if len(name) < 2 {
|
||||
return false
|
||||
@ -274,12 +271,12 @@ func IsValidUser(name string) bool {
|
||||
}
|
||||
|
||||
// Check to see if the first index is alphanumeric.
|
||||
if (name[0] < 0x41 || name[0] > 0x4A) && (name[0] < 0x61 || name[0] > 0x7A) && (name[0] < 0x30 || name[0] > 0x39) {
|
||||
if (name[0] < 'A' || name[0] > 'J') && (name[0] < 'a' || name[0] > 'z') && (name[0] < '0' || name[0] > '9') {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 1; i < len(name); i++ {
|
||||
if (name[i] < 0x41 || name[i] > 0x7D) && (name[i] < 0x30 || name[i] > 0x39) && name[i] != 0x2D && name[i] != 0x2E {
|
||||
if (name[i] < 'A' || name[i] > '}') && (name[i] < '0' || name[i] > '9') && name[i] != '-' && name[i] != '.' {
|
||||
// a-z, A-Z, 0-9, -, and _\[]{}^|
|
||||
return false
|
||||
}
|
||||
@ -290,8 +287,13 @@ func IsValidUser(name string) bool {
|
||||
|
||||
// ToRFC1459 converts a string to the stripped down conversion within RFC
|
||||
// 1459. This will do things like replace an "A" with an "a", "[]" with "{}",
|
||||
// and so forth. Useful to compare two nicknames or channels.
|
||||
func ToRFC1459(input string) (out string) {
|
||||
// and so forth. Useful to compare two nicknames or channels. Note that this
|
||||
// should not be used to normalize nicknames or similar, as this may convert
|
||||
// valid input characters to non-rfc-valid characters. As such, it's main use
|
||||
// is for comparing two nicks.
|
||||
func ToRFC1459(input string) string {
|
||||
var out string
|
||||
|
||||
for i := 0; i < len(input); i++ {
|
||||
if input[i] >= 65 && input[i] <= 94 {
|
||||
out += string(rune(input[i]) + 32)
|
||||
|
131
vendor/github.com/lrstanley/girc/handler.go
generated
vendored
131
vendor/github.com/lrstanley/girc/handler.go
generated
vendored
@ -29,11 +29,12 @@ func (c *Client) RunHandlers(event *Event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Regular wildcard handlers.
|
||||
c.Handlers.exec(ALL_EVENTS, c, event.Copy())
|
||||
// Background handlers first.
|
||||
c.Handlers.exec(ALL_EVENTS, true, c, event.Copy())
|
||||
c.Handlers.exec(event.Command, true, c, event.Copy())
|
||||
|
||||
// Then regular handlers.
|
||||
c.Handlers.exec(event.Command, c, event.Copy())
|
||||
c.Handlers.exec(ALL_EVENTS, false, c, event.Copy())
|
||||
c.Handlers.exec(event.Command, false, c, event.Copy())
|
||||
|
||||
// Check if it's a CTCP.
|
||||
if ctcp := decodeCTCP(event.Copy()); ctcp != nil {
|
||||
@ -144,7 +145,7 @@ func (c *Caller) cuid(cmd string, n int) (cuid, uid string) {
|
||||
// cuidToID allows easy mapping between a generated cuid and the caller
|
||||
// external/internal handler maps.
|
||||
func (c *Caller) cuidToID(input string) (cmd, uid string) {
|
||||
i := strings.IndexByte(input, 0x3A)
|
||||
i := strings.IndexByte(input, ':')
|
||||
if i < 0 {
|
||||
return "", ""
|
||||
}
|
||||
@ -160,9 +161,9 @@ type execStack struct {
|
||||
// exec executes all handlers pertaining to specified event. Internal first,
|
||||
// then external.
|
||||
//
|
||||
// Please note that there is no specific order/priority for which the
|
||||
// handler types themselves or the handlers are executed.
|
||||
func (c *Caller) exec(command string, client *Client, event *Event) {
|
||||
// Please note that there is no specific order/priority for which the handlers
|
||||
// are executed.
|
||||
func (c *Caller) exec(command string, bg bool, client *Client, event *Event) {
|
||||
// Build a stack of handlers which can be executed concurrently.
|
||||
var stack []execStack
|
||||
|
||||
@ -170,13 +171,21 @@ func (c *Caller) exec(command string, client *Client, event *Event) {
|
||||
// Get internal handlers first.
|
||||
if _, ok := c.internal[command]; ok {
|
||||
for cuid := range c.internal[command] {
|
||||
if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) {
|
||||
continue
|
||||
}
|
||||
|
||||
stack = append(stack, execStack{c.internal[command][cuid], cuid})
|
||||
}
|
||||
}
|
||||
|
||||
// Aaand then external handlers.
|
||||
// Then external handlers.
|
||||
if _, ok := c.external[command]; ok {
|
||||
for cuid := range c.external[command] {
|
||||
if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) {
|
||||
continue
|
||||
}
|
||||
|
||||
stack = append(stack, execStack{c.external[command][cuid], cuid})
|
||||
}
|
||||
}
|
||||
@ -189,18 +198,29 @@ func (c *Caller) exec(command string, client *Client, event *Event) {
|
||||
wg.Add(len(stack))
|
||||
for i := 0; i < len(stack); i++ {
|
||||
go func(index int) {
|
||||
c.debug.Printf("executing handler %s for event %s (%d of %d)", stack[index].cuid, command, index+1, len(stack))
|
||||
defer wg.Done()
|
||||
c.debug.Printf("[%d/%d] exec %s => %s", index+1, len(stack), stack[index].cuid, command)
|
||||
start := time.Now()
|
||||
|
||||
// If they want to catch any panics, add to defer stack.
|
||||
if bg {
|
||||
go func() {
|
||||
if client.Config.RecoverFunc != nil {
|
||||
defer recoverHandlerPanic(client, event, stack[index].cuid, 3)
|
||||
}
|
||||
|
||||
stack[index].Execute(client, *event)
|
||||
c.debug.Printf("[%d/%d] done %s == %s", index+1, len(stack), stack[index].cuid, time.Since(start))
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if client.Config.RecoverFunc != nil {
|
||||
defer recoverHandlerPanic(client, event, stack[index].cuid, 3)
|
||||
}
|
||||
|
||||
stack[index].Execute(client, *event)
|
||||
|
||||
c.debug.Printf("execution of %s took %s (%d of %d)", stack[index].cuid, time.Since(start), index+1, len(stack))
|
||||
wg.Done()
|
||||
c.debug.Printf("[%d/%d] done %s == %s", index+1, len(stack), stack[index].cuid, time.Since(start))
|
||||
}(i)
|
||||
}
|
||||
|
||||
@ -281,9 +301,9 @@ func (c *Caller) remove(cuid string) (success bool) {
|
||||
|
||||
// sregister is much like Caller.register(), except that it safely locks
|
||||
// the Caller mutex.
|
||||
func (c *Caller) sregister(internal bool, cmd string, handler Handler) (cuid string) {
|
||||
func (c *Caller) sregister(internal, bg bool, cmd string, handler Handler) (cuid string) {
|
||||
c.mu.Lock()
|
||||
cuid = c.register(internal, cmd, handler)
|
||||
cuid = c.register(internal, bg, cmd, handler)
|
||||
c.mu.Unlock()
|
||||
|
||||
return cuid
|
||||
@ -291,30 +311,34 @@ func (c *Caller) sregister(internal bool, cmd string, handler Handler) (cuid str
|
||||
|
||||
// register will register a handler in the internal tracker. Unsafe (you
|
||||
// must lock c.mu yourself!)
|
||||
func (c *Caller) register(internal bool, cmd string, handler Handler) (cuid string) {
|
||||
func (c *Caller) register(internal, bg bool, cmd string, handler Handler) (cuid string) {
|
||||
var uid string
|
||||
|
||||
cmd = strings.ToUpper(cmd)
|
||||
|
||||
cuid, uid = c.cuid(cmd, 20)
|
||||
if bg {
|
||||
uid += ":bg"
|
||||
cuid += ":bg"
|
||||
}
|
||||
|
||||
if internal {
|
||||
if _, ok := c.internal[cmd]; !ok {
|
||||
c.internal[cmd] = map[string]Handler{}
|
||||
}
|
||||
|
||||
cuid, uid = c.cuid(cmd, 20)
|
||||
c.internal[cmd][uid] = handler
|
||||
} else {
|
||||
if _, ok := c.external[cmd]; !ok {
|
||||
c.external[cmd] = map[string]Handler{}
|
||||
}
|
||||
|
||||
cuid, uid = c.cuid(cmd, 20)
|
||||
c.external[cmd][uid] = handler
|
||||
}
|
||||
|
||||
_, file, line, _ := runtime.Caller(3)
|
||||
|
||||
c.debug.Printf("registering handler for %q with cuid %q (internal: %t) from: %s:%d", cmd, cuid, internal, file, line)
|
||||
c.debug.Printf("reg %q => %s [int:%t bg:%t] %s:%d", uid, cmd, internal, bg, file, line)
|
||||
|
||||
return cuid
|
||||
}
|
||||
@ -323,31 +347,20 @@ func (c *Caller) register(internal bool, cmd string, handler Handler) (cuid stri
|
||||
// given event. cuid is the handler uid which can be used to remove the
|
||||
// handler with Caller.Remove().
|
||||
func (c *Caller) AddHandler(cmd string, handler Handler) (cuid string) {
|
||||
return c.sregister(false, cmd, handler)
|
||||
return c.sregister(false, false, cmd, handler)
|
||||
}
|
||||
|
||||
// Add registers the handler function for the given event. cuid is the
|
||||
// handler uid which can be used to remove the handler with Caller.Remove().
|
||||
func (c *Caller) Add(cmd string, handler func(client *Client, event Event)) (cuid string) {
|
||||
return c.sregister(false, cmd, HandlerFunc(handler))
|
||||
return c.sregister(false, false, cmd, HandlerFunc(handler))
|
||||
}
|
||||
|
||||
// AddBg registers the handler function for the given event and executes it
|
||||
// in a go-routine. cuid is the handler uid which can be used to remove the
|
||||
// handler with Caller.Remove().
|
||||
func (c *Caller) AddBg(cmd string, handler func(client *Client, event Event)) (cuid string) {
|
||||
return c.sregister(false, cmd, HandlerFunc(func(client *Client, event Event) {
|
||||
// Setting up background-based handlers this way allows us to get
|
||||
// clean call stacks for use with panic recovery.
|
||||
go func() {
|
||||
// If they want to catch any panics, add to defer stack.
|
||||
if client.Config.RecoverFunc != nil {
|
||||
defer recoverHandlerPanic(client, &event, "goroutine", 3)
|
||||
}
|
||||
|
||||
handler(client, event)
|
||||
}()
|
||||
}))
|
||||
return c.sregister(false, true, cmd, HandlerFunc(handler))
|
||||
}
|
||||
|
||||
// AddTmp adds a "temporary" handler, which is good for one-time or few-time
|
||||
@ -361,47 +374,37 @@ func (c *Caller) AddBg(cmd string, handler func(client *Client, event Event)) (c
|
||||
//
|
||||
// Additionally, AddTmp has a useful option, deadline. When set to greater
|
||||
// than 0, deadline will be the amount of time that passes before the handler
|
||||
// is removed from the stack, regardless if the handler returns true or not.
|
||||
// is removed from the stack, regardless of if the handler returns true or not.
|
||||
// This is useful in that it ensures that the handler is cleaned up if the
|
||||
// server does not respond appropriately, or takes too long to respond.
|
||||
//
|
||||
// Note that handlers supplied with AddTmp are executed in a goroutine to
|
||||
// ensure that they are not blocking other handlers. Additionally, use cuid
|
||||
// with Caller.Remove() to prematurely remove the handler from the stack,
|
||||
// bypassing the timeout or waiting for the handler to return that it wants
|
||||
// to be removed from the stack.
|
||||
// ensure that they are not blocking other handlers. However, if you are
|
||||
// creating a temporary handler from another handler, it should be a
|
||||
// background handler.
|
||||
//
|
||||
// Use cuid with Caller.Remove() to prematurely remove the handler from the
|
||||
// stack, bypassing the timeout or waiting for the handler to return that it
|
||||
// wants to be removed from the stack.
|
||||
func (c *Caller) AddTmp(cmd string, deadline time.Duration, handler func(client *Client, event Event) bool) (cuid string, done chan struct{}) {
|
||||
var uid string
|
||||
cuid, uid = c.cuid(cmd, 20)
|
||||
|
||||
done = make(chan struct{})
|
||||
|
||||
c.mu.Lock()
|
||||
if _, ok := c.external[cmd]; !ok {
|
||||
c.external[cmd] = map[string]Handler{}
|
||||
}
|
||||
c.external[cmd][uid] = HandlerFunc(func(client *Client, event Event) {
|
||||
// Setting up background-based handlers this way allows us to get
|
||||
// clean call stacks for use with panic recovery.
|
||||
go func() {
|
||||
// If they want to catch any panics, add to defer stack.
|
||||
if client.Config.RecoverFunc != nil {
|
||||
defer recoverHandlerPanic(client, &event, "tmp-goroutine", 3)
|
||||
cuid = c.sregister(false, true, cmd, HandlerFunc(func(client *Client, event Event) {
|
||||
remove := handler(client, event)
|
||||
if remove {
|
||||
if ok := c.Remove(cuid); ok {
|
||||
close(done)
|
||||
}
|
||||
|
||||
remove := handler(client, event)
|
||||
if remove {
|
||||
if ok := c.Remove(cuid); ok {
|
||||
close(done)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}))
|
||||
|
||||
if deadline > 0 {
|
||||
go func() {
|
||||
<-time.After(deadline)
|
||||
select {
|
||||
case <-time.After(deadline):
|
||||
case <-done:
|
||||
}
|
||||
|
||||
if ok := c.Remove(cuid); ok {
|
||||
close(done)
|
||||
}
|
||||
|
10
vendor/github.com/lrstanley/girc/modes.go
generated
vendored
10
vendor/github.com/lrstanley/girc/modes.go
generated
vendored
@ -206,11 +206,11 @@ func (c *CModes) Parse(flags string, args []string) (out []CMode) {
|
||||
var argCount int
|
||||
|
||||
for i := 0; i < len(flags); i++ {
|
||||
if flags[i] == 0x2B {
|
||||
if flags[i] == '+' {
|
||||
add = true
|
||||
continue
|
||||
}
|
||||
if flags[i] == 0x2D {
|
||||
if flags[i] == '-' {
|
||||
add = false
|
||||
continue
|
||||
}
|
||||
@ -265,7 +265,7 @@ func IsValidChannelMode(raw string) bool {
|
||||
|
||||
for i := 0; i < len(raw); i++ {
|
||||
// Allowed are: ",", A-Z and a-z.
|
||||
if raw[i] != 0x2C && (raw[i] < 0x41 || raw[i] > 0x5A) && (raw[i] < 0x61 || raw[i] > 0x7A) {
|
||||
if raw[i] != ',' && (raw[i] < 'A' || raw[i] > 'Z') && (raw[i] < 'a' || raw[i] > 'z') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -279,7 +279,7 @@ func isValidUserPrefix(raw string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if raw[0] != 0x28 { // (.
|
||||
if raw[0] != '(' {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -288,7 +288,7 @@ func isValidUserPrefix(raw string) bool {
|
||||
|
||||
// Skip the first one as we know it's (.
|
||||
for i := 1; i < len(raw); i++ {
|
||||
if raw[i] == 0x29 { // ).
|
||||
if raw[i] == ')' {
|
||||
passedKeys = true
|
||||
continue
|
||||
}
|
||||
|
90
vendor/github.com/matrix-org/gomatrix/client.go
generated
vendored
90
vendor/github.com/matrix-org/gomatrix/client.go
generated
vendored
@ -79,7 +79,7 @@ func (cli *Client) BuildBaseURL(urlPath ...string) string {
|
||||
return hsURL.String()
|
||||
}
|
||||
|
||||
// BuildURLWithQuery builds a URL with query paramters in addition to the Client's homeserver/prefix/access_token set already.
|
||||
// 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()
|
||||
@ -387,6 +387,20 @@ func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{
|
||||
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")
|
||||
@ -450,6 +464,35 @@ func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) {
|
||||
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()
|
||||
@ -518,6 +561,14 @@ func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanU
|
||||
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
|
||||
@ -556,8 +607,15 @@ func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, co
|
||||
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",
|
||||
Message: "Upload request failed: " + string(contents),
|
||||
Code: res.StatusCode,
|
||||
}
|
||||
}
|
||||
@ -588,6 +646,34 @@ func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
|
||||
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)
|
||||
}
|
||||
|
43
vendor/github.com/matrix-org/gomatrix/events.go
generated
vendored
43
vendor/github.com/matrix-org/gomatrix/events.go
generated
vendored
@ -7,13 +7,13 @@ import (
|
||||
|
||||
// Event represents a single Matrix event.
|
||||
type Event struct {
|
||||
StateKey string `json:"state_key"` // 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 int `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.
|
||||
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.
|
||||
}
|
||||
|
||||
// Body returns the value of the "body" key in the event content if it is
|
||||
@ -44,12 +44,31 @@ type TextMessage struct {
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// ImageInfo contains info about an image
|
||||
// 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"`
|
||||
Width uint `json:"w"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
Size uint `json:"size"`
|
||||
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
|
||||
|
43
vendor/github.com/matrix-org/gomatrix/filter.go
generated
vendored
Normal file
43
vendor/github.com/matrix-org/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"`
|
||||
}
|
6
vendor/github.com/matrix-org/gomatrix/requests.go
generated
vendored
6
vendor/github.com/matrix-org/gomatrix/requests.go
generated
vendored
@ -70,3 +70,9 @@ type ReqBanUser struct {
|
||||
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"`
|
||||
}
|
||||
|
32
vendor/github.com/matrix-org/gomatrix/responses.go
generated
vendored
32
vendor/github.com/matrix-org/gomatrix/responses.go
generated
vendored
@ -45,6 +45,9 @@ 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"`
|
||||
@ -58,6 +61,13 @@ type RespJoinedMembers struct {
|
||||
} `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"`
|
||||
@ -90,6 +100,11 @@ func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool {
|
||||
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"`
|
||||
@ -125,6 +140,16 @@ type RespSync 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"`
|
||||
@ -142,3 +167,10 @@ type RespSync struct {
|
||||
} `json:"invite"`
|
||||
} `json:"rooms"`
|
||||
}
|
||||
|
||||
type RespTurnServer struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
TTL int `json:"ttl"`
|
||||
URIs []string `json:"uris"`
|
||||
}
|
||||
|
2
vendor/github.com/matrix-org/gomatrix/room.go
generated
vendored
2
vendor/github.com/matrix-org/gomatrix/room.go
generated
vendored
@ -13,7 +13,7 @@ func (room Room) UpdateState(event *Event) {
|
||||
if !exists {
|
||||
room.State[event.Type] = make(map[string]*Event)
|
||||
}
|
||||
room.State[event.Type][event.StateKey] = event
|
||||
room.State[event.Type][*event.StateKey] = event
|
||||
}
|
||||
|
||||
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
|
||||
|
12
vendor/github.com/matrix-org/gomatrix/sync.go
generated
vendored
12
vendor/github.com/matrix-org/gomatrix/sync.go
generated
vendored
@ -73,6 +73,16 @@ func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error)
|
||||
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
|
||||
}
|
||||
|
||||
@ -102,7 +112,7 @@ func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool
|
||||
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 == s.UserID {
|
||||
if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID {
|
||||
m := e.Content["membership"]
|
||||
mship, ok := m.(string)
|
||||
if !ok {
|
||||
|
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.
|
||||
OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
|
||||
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
|
||||
URL string `json:"url,omitempty"` // Optional.
|
||||
}
|
||||
|
||||
// AttachmentActionOption the individual option to appear in action menu.
|
||||
@ -48,6 +49,9 @@ type AttachmentActionCallback struct {
|
||||
Channel Channel `json:"channel"`
|
||||
User User `json:"user"`
|
||||
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
|
||||
OriginalMessage Message `json:"original_message"`
|
||||
|
||||
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
|
||||
func (api *Client) ArchiveChannel(channel string) error {
|
||||
return api.ArchiveChannelContext(context.Background(), channel)
|
||||
// see https://api.slack.com/methods/channels.archive
|
||||
func (api *Client) ArchiveChannel(channelID string) error {
|
||||
return api.ArchiveChannelContext(context.Background(), channelID)
|
||||
}
|
||||
|
||||
// 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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
}
|
||||
_, err := channelRequest(ctx, "channels.archive", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// UnarchiveChannel unarchives the given channel
|
||||
func (api *Client) UnarchiveChannel(channel string) error {
|
||||
return api.UnarchiveChannelContext(context.Background(), channel)
|
||||
// see https://api.slack.com/methods/channels.unarchive
|
||||
func (api *Client) UnarchiveChannel(channelID string) error {
|
||||
return api.UnarchiveChannelContext(context.Background(), channelID)
|
||||
}
|
||||
|
||||
// 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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
}
|
||||
_, err := channelRequest(ctx, "channels.unarchive", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateChannel creates a channel with the given name and returns a *Channel
|
||||
func (api *Client) CreateChannel(channel string) (*Channel, error) {
|
||||
return api.CreateChannelContext(context.Background(), channel)
|
||||
// see https://api.slack.com/methods/channels.create
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"name": {channel},
|
||||
"name": {channelName},
|
||||
}
|
||||
response, err := channelRequest(ctx, "channels.create", values, api.debug)
|
||||
if err != nil {
|
||||
@ -92,15 +92,17 @@ func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*C
|
||||
}
|
||||
|
||||
// GetChannelHistory retrieves the channel history
|
||||
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
|
||||
return api.GetChannelHistoryContext(context.Background(), channel, params)
|
||||
// see https://api.slack.com/methods/channels.history
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
}
|
||||
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||
values.Add("latest", params.Latest)
|
||||
@ -133,15 +135,17 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
|
||||
}
|
||||
|
||||
// GetChannelInfo retrieves the given channel
|
||||
func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
|
||||
return api.GetChannelInfoContext(context.Background(), channel)
|
||||
// see https://api.slack.com/methods/channels.info
|
||||
func (api *Client) GetChannelInfo(channelID string) (*Channel, error) {
|
||||
return api.GetChannelInfoContext(context.Background(), channelID)
|
||||
}
|
||||
|
||||
// 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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
}
|
||||
response, err := channelRequest(ctx, "channels.info", values, api.debug)
|
||||
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
|
||||
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
|
||||
return api.InviteUserToChannelContext(context.Background(), channel, user)
|
||||
// see https://api.slack.com/methods/channels.invite
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
"user": {user},
|
||||
}
|
||||
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
|
||||
func (api *Client) JoinChannel(channel string) (*Channel, error) {
|
||||
return api.JoinChannelContext(context.Background(), channel)
|
||||
// see https://api.slack.com/methods/channels.join
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"name": {channel},
|
||||
"name": {channelName},
|
||||
}
|
||||
response, err := channelRequest(ctx, "channels.join", values, api.debug)
|
||||
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
|
||||
func (api *Client) LeaveChannel(channel string) (bool, error) {
|
||||
return api.LeaveChannelContext(context.Background(), channel)
|
||||
// see https://api.slack.com/methods/channels.leave
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
}
|
||||
response, err := channelRequest(ctx, "channels.leave", values, api.debug)
|
||||
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
|
||||
func (api *Client) KickUserFromChannel(channel, user string) error {
|
||||
return api.KickUserFromChannelContext(context.Background(), channel, user)
|
||||
// see https://api.slack.com/methods/channels.kick
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
"user": {user},
|
||||
}
|
||||
_, err := channelRequest(ctx, "channels.kick", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// GetChannels retrieves all the channels
|
||||
// see https://api.slack.com/methods/channels.list
|
||||
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
||||
return api.GetChannelsContext(context.Background(), excludeArchived)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
values := url.Values{
|
||||
"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
|
||||
// (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.
|
||||
func (api *Client) SetChannelReadMark(channel, ts string) error {
|
||||
return api.SetChannelReadMarkContext(context.Background(), channel, ts)
|
||||
// see https://api.slack.com/methods/channels.mark
|
||||
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
|
||||
// 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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
"ts": {ts},
|
||||
}
|
||||
_, err := channelRequest(ctx, "channels.mark", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// RenameChannel renames a given channel
|
||||
func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
|
||||
return api.RenameChannelContext(context.Background(), channel, name)
|
||||
// see https://api.slack.com/methods/channels.rename
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
"name": {name},
|
||||
}
|
||||
// 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
|
||||
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
|
||||
return api.SetChannelPurposeContext(context.Background(), channel, purpose)
|
||||
// see https://api.slack.com/methods/channels.setPurpose
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
"purpose": {purpose},
|
||||
}
|
||||
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
|
||||
func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
|
||||
return api.SetChannelTopicContext(context.Background(), channel, topic)
|
||||
// see https://api.slack.com/methods/channels.setTopic
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
"topic": {topic},
|
||||
}
|
||||
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).
|
||||
func (api *Client) GetChannelReplies(channel, thread_ts string) ([]Message, error) {
|
||||
return api.GetChannelRepliesContext(context.Background(), channel, thread_ts)
|
||||
// see https://api.slack.com/methods/channels.replies
|
||||
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
|
||||
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{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"channel": {channelID},
|
||||
"thread_ts": {thread_ts},
|
||||
}
|
||||
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 (
|
||||
DEFAULT_MESSAGE_USERNAME = ""
|
||||
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
|
||||
DEFAULT_MESSAGE_REPLY_BROADCAST = false
|
||||
DEFAULT_MESSAGE_ASUSER = false
|
||||
DEFAULT_MESSAGE_PARSE = ""
|
||||
DEFAULT_MESSAGE_LINK_NAMES = 0
|
||||
@ -36,6 +37,7 @@ type PostMessageParameters struct {
|
||||
AsUser bool `json:"as_user"`
|
||||
Parse string `json:"parse"`
|
||||
ThreadTimestamp string `json:"thread_ts"`
|
||||
ReplyBroadcast bool `json:"reply_broadcast"`
|
||||
LinkNames int `json:"link_names"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
UnfurlLinks bool `json:"unfurl_links"`
|
||||
@ -44,12 +46,17 @@ type PostMessageParameters struct {
|
||||
IconEmoji string `json:"icon_emoji"`
|
||||
Markdown bool `json:"mrkdwn,omitempty"`
|
||||
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
|
||||
func NewPostMessageParameters() PostMessageParameters {
|
||||
return PostMessageParameters{
|
||||
Username: DEFAULT_MESSAGE_USERNAME,
|
||||
User: DEFAULT_MESSAGE_USERNAME,
|
||||
AsUser: DEFAULT_MESSAGE_ASUSER,
|
||||
Parse: DEFAULT_MESSAGE_PARSE,
|
||||
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
|
||||
@ -102,6 +109,37 @@ func (api *Client) PostMessageContext(ctx context.Context, channel, text string,
|
||||
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
|
||||
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
|
||||
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
|
||||
|
||||
const (
|
||||
chatUpdate sendMode = "chat.update"
|
||||
chatPostMessage sendMode = "chat.postMessage"
|
||||
chatDelete sendMode = "chat.delete"
|
||||
chatUpdate sendMode = "chat.update"
|
||||
chatPostMessage sendMode = "chat.postMessage"
|
||||
chatDelete sendMode = "chat.delete"
|
||||
chatPostEphemeral sendMode = "chat.postEphemeral"
|
||||
)
|
||||
|
||||
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.
|
||||
func MsgOptionUpdate(timestamp string) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
@ -279,6 +327,11 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
||||
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.
|
||||
MsgOptionAsUser(params.AsUser)(config)
|
||||
|
||||
@ -314,6 +367,9 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
||||
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
|
||||
config.values.Set("thread_ts", params.ThreadTimestamp)
|
||||
}
|
||||
if params.ReplyBroadcast != DEFAULT_MESSAGE_REPLY_BROADCAST {
|
||||
config.values.Set("reply_broadcast", "true")
|
||||
}
|
||||
|
||||
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},
|
||||
}
|
||||
_, err := fileRequest(ctx, "files.delete", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
|
||||
}
|
||||
|
@ -208,10 +208,7 @@ func (api *Client) LeaveGroupContext(ctx context.Context, group string) error {
|
||||
"channel": {group},
|
||||
}
|
||||
_, err := groupRequest(ctx, "groups.leave", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// KickUserFromGroup kicks a user from a group
|
||||
@ -227,10 +224,7 @@ func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user str
|
||||
"user": {user},
|
||||
}
|
||||
_, err := groupRequest(ctx, "groups.kick", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// GetGroups retrieves all groups
|
||||
@ -289,10 +283,7 @@ func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string
|
||||
"ts": {ts},
|
||||
}
|
||||
_, err := groupRequest(ctx, "groups.mark", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// 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.
|
||||
type OutgoingMessage struct {
|
||||
ID int `json:"id"`
|
||||
// channel ID
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
@ -121,12 +122,12 @@ type Pong struct {
|
||||
// NewOutgoingMessage prepares an OutgoingMessage that the user can
|
||||
// use to send a message. Use this function to properly set the
|
||||
// messageID.
|
||||
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
|
||||
func (rtm *RTM) NewOutgoingMessage(text string, channelID string) *OutgoingMessage {
|
||||
id := rtm.idGen.Next()
|
||||
return &OutgoingMessage{
|
||||
ID: id,
|
||||
Type: "message",
|
||||
Channel: channel,
|
||||
Channel: channelID,
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
@ -134,11 +135,11 @@ func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage
|
||||
// NewTypingMessage prepares an OutgoingMessage that the user can
|
||||
// use to send as a typing indicator. Use this function to properly set the
|
||||
// messageID.
|
||||
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
|
||||
func (rtm *RTM) NewTypingMessage(channelID string) *OutgoingMessage {
|
||||
id := rtm.idGen.Next()
|
||||
return &OutgoingMessage{
|
||||
ID: id,
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -42,6 +43,14 @@ func (s WebError) Error() string {
|
||||
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) {
|
||||
body := &bytes.Buffer{}
|
||||
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))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(response, &intf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return json.Unmarshal(response, &intf)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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.
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logResponse(resp, debug)
|
||||
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()
|
||||
|
||||
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.
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logResponse(resp, debug)
|
||||
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user