mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-26 21:19:22 +00:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
bb38a61f3b | |||
c447647af9 | |||
1de64f3f61 | |||
59e55cfbd5 | |||
788d3b32ac | |||
1d414cf2fd | |||
cc3c168162 | |||
1ee6837f0e | |||
27dcea7c5b | |||
dcda7f7b8c | |||
e0cbb69a4f | |||
7ec95f786d | |||
1efe40add5 | |||
cbd73ee313 | |||
34227a7a39 | |||
71cb9b2d1d | |||
cd4c9b194f | |||
98762a0235 | |||
2fd1fd9573 | |||
aff3964078 | |||
2778580397 |
@ -54,7 +54,7 @@ See https://github.com/42wim/matterbridge/wiki
|
||||
|
||||
# Installing
|
||||
## Binaries
|
||||
* Latest stable release [v1.4.1](https://github.com/42wim/matterbridge/releases/latest)
|
||||
* Latest stable release [v1.5.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 +175,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
|
||||
|
@ -36,6 +36,7 @@ type FileInfo struct {
|
||||
Name string
|
||||
Data *[]byte
|
||||
Comment string
|
||||
URL string
|
||||
}
|
||||
|
||||
type ChannelInfo struct {
|
||||
@ -59,6 +60,13 @@ type Protocol struct {
|
||||
IgnoreMessages string // all protocols
|
||||
Jid string // xmpp
|
||||
Login string // mattermost, matrix
|
||||
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
|
||||
@ -71,11 +79,9 @@ type Protocol struct {
|
||||
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
|
||||
Protocol string // all protocols
|
||||
ReplaceMessages [][]string // all protocols
|
||||
ReplaceNicks [][]string // all protocols
|
||||
RemoteNickFormat string // all protocols
|
||||
Server string // IRC,mattermost,XMPP,discord
|
||||
ShowJoinPart bool // all protocols
|
||||
|
@ -125,6 +125,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"
|
||||
@ -178,9 +179,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 {
|
||||
@ -309,11 +328,10 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
||||
rmsg := config.Message{Username: event.Source.Name, Channel: 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,10 +1,14 @@
|
||||
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"
|
||||
)
|
||||
@ -87,6 +91,43 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
matrix.TextMessage{"m.emote", 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
b.mc.SendText(channel, msg.Username+msg.Text)
|
||||
return "", nil
|
||||
}
|
||||
@ -104,7 +145,13 @@ func (b *Bmatrix) getRoomID(channel string) string {
|
||||
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 {
|
||||
flog.Debugf("Received: %#v", ev)
|
||||
if (ev.Content["msgtype"].(string) == "m.text" ||
|
||||
ev.Content["msgtype"].(string) == "m.notice" ||
|
||||
ev.Content["msgtype"].(string) == "m.emote" ||
|
||||
ev.Content["msgtype"].(string) == "m.file" ||
|
||||
ev.Content["msgtype"].(string) == "m.image" ||
|
||||
ev.Content["msgtype"].(string) == "m.video") && ev.Sender != b.UserID {
|
||||
b.RLock()
|
||||
channel, ok := b.RoomMap[ev.RoomID]
|
||||
b.RUnlock()
|
||||
@ -121,10 +168,31 @@ func (b *Bmatrix) handlematrix() error {
|
||||
if ev.Content["msgtype"].(string) == "m.emote" {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
if ev.Content["msgtype"].(string) == "m.image" ||
|
||||
ev.Content["msgtype"].(string) == "m.video" ||
|
||||
ev.Content["msgtype"].(string) == "m.file" {
|
||||
flog.Debugf("ev: %#v", ev)
|
||||
rmsg.Extra = make(map[string][]interface{})
|
||||
url := ev.Content["url"].(string)
|
||||
url = strings.Replace(url, "mxc://", b.Config.Server+"/_matrix/media/v1/download/", -1)
|
||||
info := ev.Content["info"].(map[string]interface{})
|
||||
size := info["size"].(float64)
|
||||
name := ev.Content["body"].(string)
|
||||
flog.Debugf("trying to download %#v with size %#v", name, size)
|
||||
if size <= 1000000 {
|
||||
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
|
||||
}
|
||||
flog.Debugf("Received: %#v", ev)
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
|
@ -229,6 +229,9 @@ func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
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:
|
||||
|
@ -85,6 +85,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
|
||||
}
|
||||
|
14
changelog.md
14
changelog.md
@ -1,3 +1,17 @@
|
||||
# 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
|
||||
|
@ -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"
|
||||
@ -155,7 +158,8 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
||||
if dest.Protocol != "discord" &&
|
||||
dest.Protocol != "slack" &&
|
||||
dest.Protocol != "mattermost" &&
|
||||
dest.Protocol != "telegram" {
|
||||
dest.Protocol != "telegram" &&
|
||||
dest.Protocol != "matrix" {
|
||||
if msg.Text == "" {
|
||||
return brMsgIDs
|
||||
}
|
||||
@ -254,6 +258,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
|
||||
@ -287,9 +305,50 @@ 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)
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
return msg.Channel + msg.Account
|
||||
}
|
||||
|
@ -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.1"
|
||||
version = "1.5.1"
|
||||
githash string
|
||||
)
|
||||
|
||||
|
@ -80,6 +80,11 @@ 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
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
@ -91,6 +96,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 +176,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 +247,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 +378,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 +439,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 +547,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 +632,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 +716,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 +801,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 +878,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 +949,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 +1030,21 @@ 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"
|
||||
|
||||
|
||||
###################################################################
|
||||
#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()
|
||||
}
|
||||
|
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]] = ""
|
||||
|
14
vendor/github.com/lrstanley/girc/cap.go
generated
vendored
14
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,10 +375,10 @@ func handleTags(c *Client, e Event) {
|
||||
}
|
||||
|
||||
const (
|
||||
prefixTag byte = 0x40 // @
|
||||
prefixTagValue byte = 0x3D // =
|
||||
prefixUserTag byte = 0x2B // +
|
||||
tagSeparator byte = 0x3B // ;
|
||||
prefixTag byte = '@'
|
||||
prefixTagValue byte = '='
|
||||
prefixUserTag byte = '+'
|
||||
tagSeparator byte = ';'
|
||||
maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included.
|
||||
)
|
||||
|
||||
@ -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)
|
||||
|
2
vendor/github.com/lrstanley/girc/cmdhandler/cmd.go
generated
vendored
2
vendor/github.com/lrstanley/girc/cmdhandler/cmd.go
generated
vendored
@ -14,6 +14,7 @@ import (
|
||||
type Input struct {
|
||||
Origin *girc.Event
|
||||
Args []string
|
||||
RawArgs string
|
||||
}
|
||||
|
||||
// Command is an IRC command, supporting aliases, help documentation and easy
|
||||
@ -191,6 +192,7 @@ func (ch *CmdHandler) Execute(client *girc.Client, event girc.Event) {
|
||||
in := &Input{
|
||||
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 ""
|
||||
}
|
||||
}
|
||||
|
10
vendor/github.com/lrstanley/girc/event.go
generated
vendored
10
vendor/github.com/lrstanley/girc/event.go
generated
vendored
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
eventSpace byte = 0x20 // Separator.
|
||||
eventSpace byte = ' ' // Separator.
|
||||
maxLength = 510 // Maximum length is 510 (2 for line endings).
|
||||
)
|
||||
|
||||
@ -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)
|
||||
|
119
vendor/github.com/lrstanley/girc/handler.go
generated
vendored
119
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))
|
||||
}()
|
||||
|
||||
c.debug.Printf("execution of %s took %s (%d of %d)", stack[index].cuid, time.Since(start), index+1, len(stack))
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}(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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
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",
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
33
vendor/github.com/matrix-org/gomatrix/events.go
generated
vendored
33
vendor/github.com/matrix-org/gomatrix/events.go
generated
vendored
@ -7,10 +7,10 @@ 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.
|
||||
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 int `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
|
||||
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.
|
||||
@ -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 {
|
||||
|
4
vendor/manifest
vendored
4
vendor/manifest
vendored
@ -282,7 +282,7 @@
|
||||
"importpath": "github.com/lrstanley/girc",
|
||||
"repository": "https://github.com/lrstanley/girc",
|
||||
"vcs": "git",
|
||||
"revision": "055075db54ebd311be5946efb3f62502846089ff",
|
||||
"revision": "5dff93b5453c1b2ac8382c9a38881635f47bba0e",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
@ -290,7 +290,7 @@
|
||||
"importpath": "github.com/matrix-org/gomatrix",
|
||||
"repository": "https://github.com/matrix-org/gomatrix",
|
||||
"vcs": "git",
|
||||
"revision": "812dcb5515581023371efaa6a82750d997f50d57",
|
||||
"revision": "a7fc80c8060c2544fe5d4dae465b584f8e9b4e27",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
|
Reference in New Issue
Block a user