diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..85f7f3bbc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Integration tests +on: [push, pull_request] +jobs: + integration: + name: Integration tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: stable + # Install prosody's latest release from upstream + - run: sudo wget https://prosody.im/downloads/repos/$(lsb_release -sc)/prosody.sources -O/etc/apt/sources.list.d/prosody.sources + - run: sudo apt update + - run: sudo apt install -y mercurial lua5.4 + - run: sudo update-alternatives --set lua-interpreter /usr/bin/lua5.4 + - run: sudo apt install -y prosody + # Setup prosody community modules + - run: hg clone https://hg.prosody.im/prosody-modules/ prosody-modules + # Copy mod_auth_any to global prosody modules + - run: sudo cp -R prosody-modules/mod_auth_any /usr/lib/prosody/modules/ + # Only one test is run for now + - run: ./tests/test.sh xmpp outgoing-message + # Upload logs when it failed + - run: cat tests/xmpp/setup.log + if: ${{ failure() }} + - run: cat tests/xmpp/matterbridge.log + if: ${{ failure() }} + - run: cat tests/xmpp/xmpp.log + if: ${{ failure() }} + - run: cat tests/xmpp/api.log + if: ${{ failure() }} diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 910f35b52..926fddc1f 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-go@v6 with: - go-version: 1.22.x + go-version: 1.24.x - name: golangci-lint uses: golangci/golangci-lint-action@v8 with: @@ -30,8 +30,8 @@ jobs: test: strategy: matrix: - # Test on latest release and v1.22 - go-version: [1.22.x, stable] + # Test on latest release and v1.24 + go-version: [1.24.x, stable] runs-on: ubuntu-latest steps: - name: Install Go diff --git a/.gitignore b/.gitignore index a19aea325..afaca1377 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,14 @@ /matterbridge.exe # Exclude configuration file -matterbridge.toml +/matterbridge.toml # Exclude IDE Files .vscode # Ignore vendoring in repository /vendor + +tests/**/*.log +tests/api/api +tests/xmpp/xmpp diff --git a/README.md b/README.md index 420aed733..f084ac956 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ For more development guidelines, see [docs/development/](docs/development/). Questions or want to test on your favorite platform? Join us on: -- federated networks: [Gitter][mb-gitter], [Jabber/XMPP][mb-xmpp], [Matrix][mb-matrix] +- federated networks: [Jabber/XMPP][mb-xmpp], [Matrix][mb-matrix] - non-free centralized networks: [Discord][mb-discord], [Keybase][mb-keybase], [Slack][mb-slack], [Telegram][mb-telegram], [Twitch][mb-twitch] - self-hostable centralized networks: [IRC][mb-irc], [Mattermost][mb-mattermost], [Rocket.Chat][mb-rocketchat], [Zulip][mb-zulip] diff --git a/bridge/config/config.go b/bridge/config/config.go index 75792ed0c..23ad83fc0 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -88,7 +88,6 @@ type ChannelMembers []ChannelMember type Protocol struct { AllowMention []string // discord - AuthCode string // steam BindAddress string // mattermost, slack // DEPRECATED Buffer int // api Charset string // irc @@ -97,8 +96,8 @@ type Protocol struct { Debug bool // general DebugLevel int // only for irc now DisableWebPagePreview bool // telegram - EditSuffix string // mattermost, slack, discord, telegram, gitter - EditDisable bool // mattermost, slack, discord, telegram, gitter + EditSuffix string // mattermost, slack, discord, telegram + EditDisable bool // mattermost, slack, discord, telegram HTMLDisable bool // matrix IconURL string // mattermost, slack IgnoreFailureOnStart bool // general @@ -159,10 +158,10 @@ type Protocol struct { StripMarkdown bool // irc SyncTopic bool // slack TengoModifyMessage string // general - Team string // mattermost, keybase + Team string // mattermost TeamID string // msteams TenantID string // msteams - Token string // gitter, slack, discord, api, matrix + Token string // slack, discord, api, matrix Topic string // zulip URL string // mattermost, slack // DEPRECATED UseAPI bool // mattermost, slack @@ -222,7 +221,6 @@ type BridgeValues struct { Slack map[string]Protocol SlackLegacy map[string]Protocol Steam map[string]Protocol - Gitter map[string]Protocol XMPP map[string]Protocol Discord map[string]Protocol Telegram map[string]Protocol diff --git a/bridge/harmony/harmony.go b/bridge/harmony/harmony.go deleted file mode 100644 index 14174c31e..000000000 --- a/bridge/harmony/harmony.go +++ /dev/null @@ -1,252 +0,0 @@ -package harmony - -import ( - "fmt" - "log" - "strconv" - "strings" - "time" - - "github.com/42wim/matterbridge/bridge" - "github.com/42wim/matterbridge/bridge/config" - "github.com/harmony-development/shibshib" - chatv1 "github.com/harmony-development/shibshib/gen/chat/v1" - typesv1 "github.com/harmony-development/shibshib/gen/harmonytypes/v1" - profilev1 "github.com/harmony-development/shibshib/gen/profile/v1" -) - -type cachedProfile struct { - data *profilev1.GetProfileResponse - lastUpdated time.Time -} - -type Bharmony struct { - *bridge.Config - - c *shibshib.Client - profileCache map[uint64]cachedProfile -} - -func uToStr(in uint64) string { - return strconv.FormatUint(in, 10) -} - -func strToU(in string) (uint64, error) { - return strconv.ParseUint(in, 10, 64) -} - -func New(cfg *bridge.Config) bridge.Bridger { - b := &Bharmony{ - Config: cfg, - profileCache: map[uint64]cachedProfile{}, - } - - return b -} - -func (b *Bharmony) getProfile(u uint64) (*profilev1.GetProfileResponse, error) { - if v, ok := b.profileCache[u]; ok && time.Since(v.lastUpdated) < time.Minute*10 { - return v.data, nil - } - - resp, err := b.c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{ - UserId: u, - }) - if err != nil { - if v, ok := b.profileCache[u]; ok { - return v.data, nil - } - return nil, err - } - b.profileCache[u] = cachedProfile{ - data: resp, - lastUpdated: time.Now(), - } - return resp, nil -} - -func (b *Bharmony) avatarFor(m *chatv1.Message) string { - if m.Overrides != nil { - return m.Overrides.GetAvatar() - } - - profi, err := b.getProfile(m.AuthorId) - if err != nil { - return "" - } - - return b.c.TransformHMCURL(profi.Profile.GetUserAvatar()) -} - -func (b *Bharmony) usernameFor(m *chatv1.Message) string { - if m.Overrides != nil { - return m.Overrides.GetUsername() - } - - profi, err := b.getProfile(m.AuthorId) - if err != nil { - return "" - } - - return profi.Profile.UserName -} - -func (b *Bharmony) toMessage(msg *shibshib.LocatedMessage) config.Message { - message := config.Message{} - message.Account = b.Account - message.UserID = uToStr(msg.Message.AuthorId) - message.Avatar = b.avatarFor(msg.Message) - message.Username = b.usernameFor(msg.Message) - message.Channel = uToStr(msg.ChannelID) - message.ID = uToStr(msg.MessageId) - - switch content := msg.Message.Content.Content.(type) { - case *chatv1.Content_EmbedMessage: - message.Text = "Embed" - case *chatv1.Content_AttachmentMessage: - var s strings.Builder - for idx, attach := range content.AttachmentMessage.Files { - s.WriteString(b.c.TransformHMCURL(attach.Id)) - if idx < len(content.AttachmentMessage.Files)-1 { - s.WriteString(", ") - } - } - message.Text = s.String() - case *chatv1.Content_PhotoMessage: - var s strings.Builder - for idx, attach := range content.PhotoMessage.GetPhotos() { - s.WriteString(attach.GetCaption().GetText()) - s.WriteString("\n") - s.WriteString(b.c.TransformHMCURL(attach.GetHmc())) - if idx < len(content.PhotoMessage.GetPhotos())-1 { - s.WriteString("\n\n") - } - } - message.Text = s.String() - case *chatv1.Content_TextMessage: - message.Text = content.TextMessage.Content.Text - } - - return message -} - -func (b *Bharmony) outputMessages() { - for { - msg := <-b.c.EventsStream() - - if msg.Message.AuthorId == b.c.UserID { - continue - } - - b.Remote <- b.toMessage(msg) - } -} - -func (b *Bharmony) GetUint64(conf string) uint64 { - num, err := strToU(b.GetString(conf)) - if err != nil { - log.Fatal(err) - } - - return num -} - -func (b *Bharmony) Connect() (err error) { - b.c, err = shibshib.NewClient(b.GetString("Homeserver"), b.GetString("Token"), b.GetUint64("UserID")) - if err != nil { - return - } - b.c.SubscribeToGuild(b.GetUint64("Community")) - - go b.outputMessages() - - return nil -} - -func (b *Bharmony) send(msg config.Message) (id string, err error) { - msgChan, err := strToU(msg.Channel) - if err != nil { - return - } - - retID, err := b.c.ChatKit.SendMessage(&chatv1.SendMessageRequest{ - GuildId: b.GetUint64("Community"), - ChannelId: msgChan, - Content: &chatv1.Content{ - Content: &chatv1.Content_TextMessage{ - TextMessage: &chatv1.Content_TextContent{ - Content: &chatv1.FormattedText{ - Text: msg.Text, - }, - }, - }, - }, - Overrides: &chatv1.Overrides{ - Username: &msg.Username, - Avatar: &msg.Avatar, - Reason: &chatv1.Overrides_Bridge{Bridge: &typesv1.Empty{}}, - }, - InReplyTo: nil, - EchoId: nil, - Metadata: nil, - }) - if err != nil { - err = fmt.Errorf("send: error sending message: %w", err) - log.Println(err.Error()) - } - - return uToStr(retID.MessageId), err -} - -func (b *Bharmony) delete(msg config.Message) (id string, err error) { - msgChan, err := strToU(msg.Channel) - if err != nil { - return "", err - } - - msgID, err := strToU(msg.ID) - if err != nil { - return "", err - } - - _, err = b.c.ChatKit.DeleteMessage(&chatv1.DeleteMessageRequest{ - GuildId: b.GetUint64("Community"), - ChannelId: msgChan, - MessageId: msgID, - }) - return "", err -} - -func (b *Bharmony) typing(msg config.Message) (id string, err error) { - msgChan, err := strToU(msg.Channel) - if err != nil { - return "", err - } - - _, err = b.c.ChatKit.Typing(&chatv1.TypingRequest{ - GuildId: b.GetUint64("Community"), - ChannelId: msgChan, - }) - return "", err -} - -func (b *Bharmony) Send(msg config.Message) (id string, err error) { - switch msg.Event { - case "": - return b.send(msg) - case config.EventMsgDelete: - return b.delete(msg) - case config.EventUserTyping: - return b.typing(msg) - default: - return "", nil - } -} - -func (b *Bharmony) JoinChannel(channel config.ChannelInfo) error { - return nil -} - -func (b *Bharmony) Disconnect() error { - return nil -} diff --git a/bridge/keybase/handlers.go b/bridge/keybase/handlers.go deleted file mode 100644 index a29208d40..000000000 --- a/bridge/keybase/handlers.go +++ /dev/null @@ -1,59 +0,0 @@ -package bkeybase - -import ( - "strconv" - - "github.com/42wim/matterbridge/bridge/config" - "github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1" -) - -func (b *Bkeybase) handleKeybase() { - sub, err := b.kbc.ListenForNewTextMessages() - if err != nil { - b.Log.Errorf("Error listening: %s", err.Error()) - } - - go func() { - for { - msg, err := sub.Read() - if err != nil { - b.Log.Errorf("failed to read message: %s", err.Error()) - } - - if msg.Message.Content.TypeName != "text" { - continue - } - - if msg.Message.Sender.Username == b.kbc.GetUsername() { - continue - } - - b.handleMessage(msg.Message) - - } - }() -} - -func (b *Bkeybase) handleMessage(msg chat1.MsgSummary) { - b.Log.Debugf("== Receiving event: %#v", msg) - if msg.Channel.TopicName != b.channel || msg.Channel.Name != b.team { - return - } - - if msg.Sender.Username != b.kbc.GetUsername() { - - // TODO download avatar - - // Create our message - rmsg := config.Message{Username: msg.Sender.Username, Text: msg.Content.Text.Body, UserID: string(msg.Sender.Uid), Channel: msg.Channel.TopicName, ID: strconv.Itoa(int(msg.Id)), Account: b.Account} - - // Text must be a string - if msg.Content.TypeName != "text" { - b.Log.Errorf("message is not text") - return - } - - b.Log.Debugf("<= Sending message from %s on %s to gateway", msg.Sender.Username, msg.Channel.Name) - b.Remote <- rmsg - } -} diff --git a/bridge/keybase/keybase.go b/bridge/keybase/keybase.go deleted file mode 100644 index d41e95f95..000000000 --- a/bridge/keybase/keybase.go +++ /dev/null @@ -1,106 +0,0 @@ -package bkeybase - -import ( - "io/ioutil" - "os" - "path/filepath" - "strconv" - - "github.com/42wim/matterbridge/bridge" - "github.com/42wim/matterbridge/bridge/config" - "github.com/keybase/go-keybase-chat-bot/kbchat" -) - -// Bkeybase bridge structure -type Bkeybase struct { - kbc *kbchat.API - user string - channel string - team string - *bridge.Config -} - -// New initializes Bkeybase object and sets team -func New(cfg *bridge.Config) bridge.Bridger { - b := &Bkeybase{Config: cfg} - b.team = b.Config.GetString("Team") - return b -} - -// Connect starts keybase API and listener loop -func (b *Bkeybase) Connect() error { - var err error - b.Log.Infof("Connecting %s", b.GetString("Team")) - - // use default keybase location (`keybase`) - b.kbc, err = kbchat.Start(kbchat.RunOptions{}) - if err != nil { - return err - } - b.user = b.kbc.GetUsername() - b.Log.Info("Connection succeeded") - go b.handleKeybase() - return nil -} - -// Disconnect doesn't do anything for now -func (b *Bkeybase) Disconnect() error { - return nil -} - -// JoinChannel sets channel name in struct -func (b *Bkeybase) JoinChannel(channel config.ChannelInfo) error { - if _, err := b.kbc.JoinChannel(b.team, channel.Name); err != nil { - return err - } - b.channel = channel.Name - return nil -} - -// Send receives bridge messages and sends them to Keybase chat room -func (b *Bkeybase) Send(msg config.Message) (string, error) { - b.Log.Debugf("=> Receiving %#v", msg) - - // Handle /me events - if msg.Event == config.EventUserAction { - msg.Text = "_" + msg.Text + "_" - } - - // Delete message if we have an ID - // Delete message not supported by keybase go library yet - - // Edit message if we have an ID - // kbchat lib does not support message editing yet - - if len(msg.Extra["file"]) > 0 { - // Upload a file - dir, err := ioutil.TempDir("", "matterbridge") - if err != nil { - return "", err - } - defer os.RemoveAll(dir) - - for _, f := range msg.Extra["file"] { - fname := f.(config.FileInfo).Name - fdata := *f.(config.FileInfo).Data - fcaption := f.(config.FileInfo).Comment - fpath := filepath.Join(dir, fname) - - if err = ioutil.WriteFile(fpath, fdata, 0600); err != nil { - return "", err - } - - _, _ = b.kbc.SendAttachmentByTeam(b.team, &b.channel, fpath, fcaption) - } - - return "", nil - } - - // Send regular message - text := msg.Username + msg.Text - resp, err := b.kbc.SendMessageByTeamName(b.team, &b.channel, text) - if err != nil { - return "", err - } - return strconv.Itoa(int(*resp.Result.MessageID)), err -} diff --git a/bridge/steam/handlers.go b/bridge/steam/handlers.go deleted file mode 100644 index fa5015c71..000000000 --- a/bridge/steam/handlers.go +++ /dev/null @@ -1,126 +0,0 @@ -package bsteam - -import ( - "fmt" - "strconv" - - "github.com/42wim/matterbridge/bridge/config" - "github.com/Philipp15b/go-steam" - "github.com/Philipp15b/go-steam/protocol/steamlang" -) - -func (b *Bsteam) handleChatMsg(e *steam.ChatMsgEvent) { - b.Log.Debugf("Receiving ChatMsgEvent: %#v", e) - b.Log.Debugf("<= Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account) - var channel int64 - if e.ChatRoomId == 0 { - channel = int64(e.ChatterId) - } else { - // for some reason we have to remove 0x18000000000000 - // TODO - // https://github.com/42wim/matterbridge/pull/630#discussion_r238102751 - // channel = int64(e.ChatRoomId) & 0xfffffffffffff - channel = int64(e.ChatRoomId) - 0x18000000000000 - } - msg := config.Message{ - Username: b.getNick(e.ChatterId), - Text: e.Message, - Channel: strconv.FormatInt(channel, 10), - Account: b.Account, - UserID: strconv.FormatInt(int64(e.ChatterId), 10), - } - b.Remote <- msg -} - -func (b *Bsteam) handleEvents() { - myLoginInfo := &steam.LogOnDetails{ - Username: b.GetString("Login"), - Password: b.GetString("Password"), - AuthCode: b.GetString("AuthCode"), - } - // TODO Attempt to read existing auth hash to avoid steam guard. - // Maybe works - //myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry") - for event := range b.c.Events() { - switch e := event.(type) { - case *steam.ChatMsgEvent: - b.handleChatMsg(e) - case *steam.PersonaStateEvent: - b.Log.Debugf("PersonaStateEvent: %#v\n", e) - b.Lock() - b.userMap[e.FriendId] = e.Name - b.Unlock() - case *steam.ConnectedEvent: - b.c.Auth.LogOn(myLoginInfo) - case *steam.MachineAuthUpdateEvent: - // TODO sentry files for 2 auth - /* - b.Log.Info("authupdate", e) - b.Log.Info("hash", e.Hash) - ioutil.WriteFile("sentry", e.Hash, 0666) - */ - case *steam.LogOnFailedEvent: - b.Log.Info("Logon failed", e) - err := b.handleLogOnFailed(e, myLoginInfo) - if err != nil { - b.Log.Error(err) - return - } - case *steam.LoggedOnEvent: - b.Log.Debugf("LoggedOnEvent: %#v", e) - b.connected <- struct{}{} - b.Log.Debugf("setting online") - b.c.Social.SetPersonaState(steamlang.EPersonaState_Online) - case *steam.DisconnectedEvent: - b.Log.Info("Disconnected") - b.Log.Info("Attempting to reconnect...") - b.c.Connect() - case steam.FatalErrorEvent: - b.Log.Errorf("steam FatalErrorEvent: %#v", e) - default: - b.Log.Debugf("unknown event %#v", e) - } - } -} - -func (b *Bsteam) handleLogOnFailed(e *steam.LogOnFailedEvent, myLoginInfo *steam.LogOnDetails) error { - switch e.Result { - case steamlang.EResult_AccountLoginDeniedNeedTwoFactor: - b.Log.Info("Steam guard isn't letting me in! Enter 2FA code:") - var code string - fmt.Scanf("%s", &code) - // TODO https://github.com/42wim/matterbridge/pull/630#discussion_r238103978 - myLoginInfo.TwoFactorCode = code - case steamlang.EResult_AccountLogonDenied: - b.Log.Info("Steam guard isn't letting me in! Enter auth code:") - var code string - fmt.Scanf("%s", &code) - // TODO https://github.com/42wim/matterbridge/pull/630#discussion_r238103978 - myLoginInfo.AuthCode = code - case steamlang.EResult_InvalidLoginAuthCode: - return fmt.Errorf("Steam guard: invalid login auth code: %#v ", e.Result) - default: - return fmt.Errorf("LogOnFailedEvent: %#v ", e.Result) - // TODO: Handle EResult_InvalidLoginAuthCode - } - return nil -} - -// handleFileInfo handles config.FileInfo and adds correct file comment or URL to msg.Text. -// Returns error if cast fails. -func (b *Bsteam) handleFileInfo(msg *config.Message, f interface{}) error { - if _, ok := f.(config.FileInfo); !ok { - return fmt.Errorf("handleFileInfo cast failed %#v", f) - } - fi := f.(config.FileInfo) - if fi.Comment != "" { - msg.Text += fi.Comment + ": " - } - if fi.URL != "" { - msg.Text = fi.URL - if fi.Comment != "" { - msg.Text = fi.Comment + ": " + fi.URL - } - } - return nil -} diff --git a/bridge/steam/steam.go b/bridge/steam/steam.go deleted file mode 100644 index 5a577a28e..000000000 --- a/bridge/steam/steam.go +++ /dev/null @@ -1,95 +0,0 @@ -package bsteam - -import ( - "fmt" - "sync" - "time" - - "github.com/42wim/matterbridge/bridge" - "github.com/42wim/matterbridge/bridge/config" - "github.com/42wim/matterbridge/bridge/helper" - "github.com/Philipp15b/go-steam" - "github.com/Philipp15b/go-steam/protocol/steamlang" - "github.com/Philipp15b/go-steam/steamid" -) - -type Bsteam struct { - c *steam.Client - connected chan struct{} - userMap map[steamid.SteamId]string - sync.RWMutex - *bridge.Config -} - -func New(cfg *bridge.Config) bridge.Bridger { - b := &Bsteam{Config: cfg} - b.userMap = make(map[steamid.SteamId]string) - b.connected = make(chan struct{}) - return b -} - -func (b *Bsteam) Connect() error { - b.Log.Info("Connecting") - b.c = steam.NewClient() - go b.handleEvents() - go b.c.Connect() - select { - case <-b.connected: - b.Log.Info("Connection succeeded") - case <-time.After(time.Second * 30): - return fmt.Errorf("connection timed out") - } - return nil -} - -func (b *Bsteam) Disconnect() error { - b.c.Disconnect() - return nil - -} - -func (b *Bsteam) JoinChannel(channel config.ChannelInfo) error { - id, err := steamid.NewId(channel.Name) - if err != nil { - return err - } - b.c.Social.JoinChat(id) - return nil -} - -func (b *Bsteam) Send(msg config.Message) (string, error) { - // ignore delete messages - if msg.Event == config.EventMsgDelete { - return "", nil - } - id, err := steamid.NewId(msg.Channel) - if err != nil { - return "", err - } - - // Handle files - if msg.Extra != nil { - for _, rmsg := range helper.HandleExtra(&msg, b.General) { - b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, rmsg.Username+rmsg.Text) - } - for i := range msg.Extra["file"] { - if err := b.handleFileInfo(&msg, msg.Extra["file"][i]); err != nil { - b.Log.Error(err) - } - b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text) - } - return "", nil - } - - b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text) - return "", nil -} - -func (b *Bsteam) getNick(id steamid.SteamId) string { - b.RLock() - defer b.RUnlock() - if name, ok := b.userMap[id]; ok { - return name - } - return "unknown" -} diff --git a/bridge/xmpp/handler.go b/bridge/xmpp/handler.go index 731998d9b..5f56ccc3a 100644 --- a/bridge/xmpp/handler.go +++ b/bridge/xmpp/handler.go @@ -3,7 +3,7 @@ package bxmpp import ( "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/helper" - "github.com/matterbridge/go-xmpp" + "github.com/xmppo/go-xmpp" ) // handleDownloadAvatar downloads the avatar of userid from channel diff --git a/bridge/xmpp/xmpp.go b/bridge/xmpp/xmpp.go index cf58a2fc4..18fa579f0 100644 --- a/bridge/xmpp/xmpp.go +++ b/bridge/xmpp/xmpp.go @@ -15,8 +15,8 @@ import ( "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/helper" "github.com/jpillora/backoff" - "github.com/matterbridge/go-xmpp" "github.com/rs/xid" + "github.com/xmppo/go-xmpp" ) type Bxmpp struct { @@ -62,7 +62,10 @@ func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error { b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name) b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil) } else { - b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick")) + // b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick")) + // TODO: this creates the room if it doesn't exist yet + // should it be the default? + _, _ = b.xc.JoinOrCreateMUCNoHistoryDoNotUseOutsideTests(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick")) } return nil } @@ -125,21 +128,18 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) { } // Post normal message. - var msgReplaceID string - msgID := xid.New().String() - if msg.ID != "" { - msgReplaceID = msg.ID - } b.Log.Debugf("=> Sending message %#v", msg) if _, err := b.xc.Send(xmpp.Chat{ - Type: "groupchat", - Remote: msg.Channel + "@" + b.GetString("Muc"), - Text: msg.Username + msg.Text, - ID: msgID, - ReplaceID: msgReplaceID, + Type: "groupchat", + Remote: msg.Channel + "@" + b.GetString("Muc"), + Text: msg.Username + msg.Text, }); err != nil { return "", err } + + // Generate a dummy ID because to avoid collision with other internal messages + // However this does not provide proper Edits/Replies integration on XMPP side. + msgID := xid.New().String() return msgID, nil } @@ -186,8 +186,6 @@ func (b *Bxmpp) createXMPP() error { InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec } - xmpp.DebugWriter = b.Log.Writer() - options := xmpp.Options{ Host: b.GetString("Server"), User: b.GetString("Jid"), @@ -201,6 +199,7 @@ func (b *Bxmpp) createXMPP() error { StatusMessage: "", Resource: "", InsecureAllowUnencryptedAuth: b.GetBool("NoTLS"), + DebugWriter: b.Log.Writer(), } var err error b.xc, err = options.NewClient() @@ -317,10 +316,6 @@ func (b *Bxmpp) handleXMPP() error { avatar = getAvatar(b.avatarMap, v.Remote, b.General) } - msgID := v.ID - if v.ReplaceID != "" { - msgID = v.ReplaceID - } rmsg := config.Message{ Username: b.parseNick(v.Remote), Text: v.Text, @@ -328,8 +323,10 @@ func (b *Bxmpp) handleXMPP() error { Account: b.Account, Avatar: avatar, UserID: v.Remote, - ID: msgID, - Event: event, + // Here the stanza-id has been set by the server and can be used to provide replies + // as explained in XEP-0461 https://xmpp.org/extensions/xep-0461.html#business-id + ID: v.StanzaID.ID, + Event: event, } // Check if we have an action event. @@ -439,11 +436,6 @@ func (b *Bxmpp) skipMessage(message xmpp.Chat) bool { return true } - // Ignore messages posted by our webhook - if b.GetString("WebhookURL") != "" && strings.Contains(message.ID, "webhookbot") { - return true - } - // skip delayed messages return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5 } diff --git a/changelog.md b/changelog.md index ab593f49a..d2561badf 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,19 @@ - Main development repository is now https://github.com/matterbridge-org/matterbridge - History was rewritten to remove the vendor/ folder, easing PR reviews and making the - repository much lighter. [See issue #5](https://github.com/matterbridge-org/community/issues/5). + repository much lighter. [See issue community/#5](https://github.com/matterbridge-org/community/issues/5). +- Removed protocols, see [issue community/#9](https://github.com/matterbridge-org/matterbridge/issues/9) + - harmony protocol does not exist anymore + - gitter protocol does not exist anymore ; gitter.im is now a matrix protocol server + - steam protocol has changed profoundly + - keybase has been removed because we don't have a maintainer for it. See + [issue #9](https://github.com/matterbridge-org/matterbridge/issues/9) +- xmpp: Initial replies/edits support has been removed, because it was incorrect ([#12](https://github.com/matterbridge-org/matterbridge/pull/12)) +- Go required version is now v1.24 + +## Upstream + +- xmpp: go-xmpp updated to xmppo/go-xmpp v0.2.18 # v1.26.0 diff --git a/docs/FAQ.md b/docs/FAQ.md index b0a24e4df..22297d168 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -28,11 +28,11 @@ Matterbridge does not relay messages from the user account that it is logged in * [API](#api) # Support bridging between any protocols -Supported protocols are Discord, Gitter, IRC, Mattermost, Matrix, RocketChat, Slack, Steam, Telegram, XMPP (and our own rest API) +Supported protocols are Discord, IRC, Mattermost, Matrix, RocketChat, Slack, Steam, Telegram, XMPP (and our own rest API) You can bridge between any (number) protocols. Even the same protocol! -For example you can bridge Discord, Gitter, Matrix and Slack. -A message typed in Slack will be shown in Discord, Gitter and Matrix (and the other way around) +For example you can bridge Discord, Matrix and Slack. +A message typed in Slack will be shown in Discord, and Matrix (and the other way around) Follow the steps in [[How-to-create-your-config]] @@ -47,7 +47,6 @@ To create the setup above you'll have to create 2 gateways. See Gateway Config Advanced TODO # Message edits and deletes * Support incoming and outgoing edits and deletes: Discord, Mattermost, Slack, Matrix and Telegram. -* Support only incoming edits: Gitter. (gitter API doesn't support outgoing edits) * Support only incoming edits/deletes: RocketChat * Support only edits: XMPP * Support no deletes or edits: IRC, Steam(NR) @@ -58,10 +57,10 @@ NR = Not researched The logic is: Public links > Native file upload > External mediaserver > Private links This means that bridges: -- will receive the "public link" from protocols that have links to files without authentication (Gitter, IRC, Discord) +- will receive the "public link" from protocols that have links to files without authentication (IRC, Discord) - that support native file uploads (Discord,Matrix,Mattermost,Slack,Telegram,Rocketchat) will receive a uploaded copy of the file from protocols that have links to files with authentication (Matrix, Mattermost, Slack, Telegram) -- that do NOT support native file uploads (Gitter, IRC, Steam, XMPP) will receive the "public link" from your "external mediaserver" from protocols that have links to files with authentication (Matrix, Mattermost, Slack, Telegram). (If you have configured an "external mediaserver") -- that do NOT support native file uploads (Gitter, IRC, Steam, XMPP) will receive the "private link" from protocols that have links to files with authentication (Matrix, Mattermost, Slack, Telegram). (If you have not configured an "external mediaserver") +- that do NOT support native file uploads (IRC, Steam, XMPP) will receive the "public link" from your "external mediaserver" from protocols that have links to files with authentication (Matrix, Mattermost, Slack, Telegram). (If you have configured an "external mediaserver") +- that do NOT support native file uploads (IRC, Steam, XMPP) will receive the "private link" from protocols that have links to files with authentication (Matrix, Mattermost, Slack, Telegram). (If you have not configured an "external mediaserver") For example if you bridge between slack and discord: @@ -70,7 +69,7 @@ When you upload a file to slack. The link you get from slack needs authenticatio # Username and avatar spoofing Username spoofing (so it looks like the remote users) only works with webhooks for Discord, Mattermost, Slack. -Matterbridge reads avatars from Discord, Gitter, Mattermost and Slack but can only send them to Discord, Mattermost and Slack when webhooks are enabled. (Gitter doesn't allow avatar changes). +Matterbridge reads avatars from Discord, Mattermost and Slack but can only send them to Discord, Mattermost and Slack when webhooks are enabled. To have avatars from mattermost visible on the other bridges, you'll have to use the external mediaserver setup. This is because avatar images from mattermost aren't publicly visible when not logged into mattermost. This means we need to download the avatar images, and upload them to the public mediaserver, and use this (now public) avatar URL in the messages sent to another bridge, eg slack. diff --git a/docs/advanced/mediaserver.md b/docs/advanced/mediaserver.md index 921277d67..2f2bbc82b 100644 --- a/docs/advanced/mediaserver.md +++ b/docs/advanced/mediaserver.md @@ -1,6 +1,6 @@ Matterbridge is not going to implement it's own "mediaserver" instead we make use of other tools that are good at this sort of stuff. This mediaserver will be used to upload media to services that don't have support for uploading images/video/files. -At this moment this is xmpp, gitter and irc +At this moment this is xmpp and irc There are 2 options to set this up: * You already have a webserver running diff --git a/docs/api/README.md b/docs/api/README.md index 99aeeb606..10a3b07e7 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -11,9 +11,9 @@ the matterbridge API is here. Matterbridge API spec can be found on https://app.swaggerhub.com/apis-docs/matterbridge/matterbridge-api/0.1.0-oas3 -## Example discord/gitter/api gateway +## Example discord/api gateway -This example creates a gateway containing 3 bridges: discord,gitter and api. +This example creates a gateway containing 2 bridges: discord, and api. The API will listen on localhost port 4242. ### Configure matterbridge.toml @@ -44,9 +44,6 @@ The full example Token="MTk4NjIyNDgzNDcdOTI1MjQ4.Cl2FMZ.ZnCjm1XVW7vRze4b7Cq4se7kKWs-abD" Server="myserver" -[gitter.mygitter] -Token="319fda1761c6875739a489b6772daf2ace4b95d0" - [api.myapi] BindAddress="127.0.0.1:4242" Buffer=1000 @@ -60,10 +57,6 @@ enable=true account="discord.mydiscord" channel="general" -[[gateway.inout]] -account="gitter.mygitter" -channel="42wim/mygreatproject" - [[gateway.inout]] account="api.myapi" channel="api" @@ -127,7 +120,7 @@ At connect you first get a `api_connected` event, then you'll get a http stream We now post a `test` message from `randomuser` to the gateway `gateway1` -Every bridge on the gateway (gitter and discord) will now receive this message. +The discord bridge will now receive this message. ```bash curl -XPOST -H 'Content-Type: application/json' -d '{"text":"test","username":"randomuser","gateway":"gateway1"}' http://localhost:4242/api/message diff --git a/docs/credits.md b/docs/credits.md index db0be0a9a..14c664970 100644 --- a/docs/credits.md +++ b/docs/credits.md @@ -9,9 +9,7 @@ Matterbridge wouldn't exist without these libraries: - gops - - gozulipbot - - gumble - -- harmony - - irc - -- keybase - - matrix - - mattermost - - msgraph.go - @@ -20,7 +18,6 @@ Matterbridge wouldn't exist without these libraries: - rocketchat - - slack - - sshchat - -- steam - - telegram - - tengo - - vk - diff --git a/docs/development/protocol.md b/docs/development/protocol.md index af348a4a8..167ae4e73 100644 --- a/docs/development/protocol.md +++ b/docs/development/protocol.md @@ -6,7 +6,6 @@ This guide explains how to create a new protocol backend to support a new gatewa - [ ] Create a new catalog in [`/bridge` folder](https://github.com/42wim/matterbridge/tree/master/bridge) and a main file named after the bridge you are creating, such as `whatsapp.go` - [ ] Implement a [`Bridger` interface](https://github.com/42wim/matterbridge/blob/2cfd880cdb0df29771bf8f31df8d990ab897889d/bridge/bridge.go#L11-L16) -- [ ] [`gitter`](https://github.com/42wim/matterbridge/blob/master/bridge/gitter/gitter.go) is a relatively simple bridge that you might use as a reference to adapt - [ ] Mention your bridge exists in [`/gateway/bridgemap/bridgemap.go`](https://github.com/42wim/matterbridge/blob/master/gateway/bridgemap/bridgemap.go) - [ ] Divide functionality in several files, as it is done for [slack](https://github.com/42wim/matterbridge/tree/master/bridge) - `yourbridge.go` with main struct and implementation of the `Bridger` interface diff --git a/docs/protocols/README.md b/docs/protocols/README.md index 1f962b404..c853b316f 100644 --- a/docs/protocols/README.md +++ b/docs/protocols/README.md @@ -21,9 +21,6 @@ Matterbridge supports many protocols, although not all of them support all featu - [xmpp docs](xmpp/) - [xmpp settings](xmpp/settings.md) - Channel format: `channel_name` (for `channel_name@muc.server.org` where `muc.server.org` has been configured as `Muc` for the corresponding xmpp account) -- [Keybase](https://keybase.io) - - Matterbridge [keybase docs](keybase/) - - Channel format: `channel_name` (without the leading `#`), or `general` if your team doesn't have multiple channels - [Matrix](https://matrix.org) - Matterbridge docs: - [matrix docs](matrix/) @@ -93,11 +90,13 @@ Matterbridge supports many protocols, although not all of them support all featu ## Dropped official support +- [Keybase](https://keybase.io) + - Dropped because noone is maintaining it on our side + - Reach out [here](https://github.com/matterbridge-org/community/issues/1) if you want to help bring it back - [Gitter](https://gitter.im) - Has moved to matrix protocol - [Harmony](https://harmonyapp.io) - Does not exist anymore? - - [Steam](https://store.steampowered.com/) - Not supported anymore, see [here](https://github.com/Philipp15b/go-steam/issues/94) for more info. diff --git a/docs/protocols/keybase/README.md b/docs/protocols/keybase/README.md deleted file mode 100644 index 0e61ee981..000000000 --- a/docs/protocols/keybase/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Keybase - -- Status: ??? -- Maintainers: ??? -- Features: ??? - -> [!WARNING] -> **Remember to create a dedicated Keybase user (a new account specifically for the bot).** -``` - -## Configuration - -**Basic configuration example:** - -```toml -[keybase.mykeybase] -# Your bot account MUST already have access to the provided team or subteam! -Team="mykeybase.team" -RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> " -``` - -## FAQ - -### How to create a new account for the bot? - -Install Keybase on the server running matterbridge (https://keybase.io/download) -Log into the bot account on your server - -## Headless server install + systemd - -If you run matterbridge as a service in systemd and want to use a bridge with keybase, be sure to run the service as the default user via *user.slice*, since keybase also runs its services in *user.slice*. - -Create a `matterbridge-user.service` in `~/.config/systemd/user/`: - -``` -[Unit] -Description=matterbridge -After=network.target - -[Service] -ExecStart=/path-to-your-executable/matterbridge -Restart=on-failure -RestartSec=10s - -[Install] -WantedBy=multi-user.target -``` - -Then add the `--user` argument to all your `systemctl` commands. For example, `systemctl --user enable --now matterbridge-user.service`. - -> [!WARNING] -> **If you are using an SSH session, user services might shut down, use this to prevent:** `loginctl enable-linger` \ No newline at end of file diff --git a/docs/settings.md b/docs/settings.md index 51e005d02..e5399eff1 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -232,7 +232,7 @@ Example: ## MediaServerDownload 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 +irc and xmpp will be shown links to the files on MediaServerDownload More information https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%28advanced%29 Setting: OPTIONAL, RELOADABLE, GENERAL \ diff --git a/gateway/bridgemap/bharmony.go b/gateway/bridgemap/bharmony.go deleted file mode 100644 index a747dda43..000000000 --- a/gateway/bridgemap/bharmony.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !noharmony -// +build !noharmony - -package bridgemap - -import ( - bharmony "github.com/42wim/matterbridge/bridge/harmony" -) - -func init() { - FullMap["harmony"] = bharmony.New -} diff --git a/gateway/bridgemap/bkeybase.go b/gateway/bridgemap/bkeybase.go deleted file mode 100644 index 92ff8941e..000000000 --- a/gateway/bridgemap/bkeybase.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !nokeybase -// +build !nokeybase - -package bridgemap - -import ( - bkeybase "github.com/42wim/matterbridge/bridge/keybase" -) - -func init() { - FullMap["keybase"] = bkeybase.New -} diff --git a/gateway/bridgemap/bsteam.go b/gateway/bridgemap/bsteam.go deleted file mode 100644 index 706e6260b..000000000 --- a/gateway/bridgemap/bsteam.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !nosteam -// +build !nosteam - -package bridgemap - -import ( - bsteam "github.com/42wim/matterbridge/bridge/steam" -) - -func init() { - FullMap["steam"] = bsteam.New -} diff --git a/gateway/handlers_test.go b/gateway/handlers_test.go index db7988a9d..20a7cfcd4 100644 --- a/gateway/handlers_test.go +++ b/gateway/handlers_test.go @@ -48,9 +48,9 @@ func TestExtractNick(t *testing.T) { resultText string }{ "test1": { - search: "fromgitter", + search: "fromsomething", extract: "<(.*?)>\\s+", - username: "fromgitter", + username: "fromsomething", text: " blahblah", resultUsername: "userx", resultText: "blahblah", diff --git a/go.mod b/go.mod index 39539ae53..fb0507c32 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/42wim/matterbridge require ( github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f - github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c github.com/SevereCloud/vksdk/v2 v2.17.0 github.com/bwmarrin/discordgo v0.28.1 @@ -13,15 +12,12 @@ require ( github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 github.com/google/gops v0.3.27 github.com/gorilla/schema v1.4.1 - github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa github.com/hashicorp/golang-lru v1.0.2 github.com/jpillora/backoff v1.0.0 - github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20 github.com/kyokomi/emoji/v2 v2.2.13 github.com/labstack/echo/v4 v4.12.0 github.com/lrstanley/girc v0.0.0-20240823210506-80555f2adb03 github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 - github.com/matterbridge/go-xmpp v0.0.0-20240523230155-7154bfeb76e8 github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27 github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75 github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba @@ -44,12 +40,13 @@ require ( github.com/stretchr/testify v1.9.0 github.com/vincent-petithory/dataurl v1.0.0 github.com/writeas/go-strip-markdown v2.0.1+incompatible + github.com/xmppo/go-xmpp v0.2.18 github.com/yaegashi/msgraph.go v0.1.4 github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289 go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7 golang.org/x/image v0.19.0 golang.org/x/oauth2 v0.22.0 - golang.org/x/text v0.17.0 + golang.org/x/text v0.30.0 gomod.garykim.dev/nc-talk v0.3.0 google.golang.org/protobuf v1.34.2 layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14 @@ -127,11 +124,11 @@ require ( go.mau.fi/libsignal v0.1.1 // indirect go.mau.fi/util v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.43.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect google.golang.org/grpc v1.65.0 // indirect @@ -150,4 +147,6 @@ require ( //replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419 -go 1.22.0 +go 1.24.0 + +replace github.com/xmppo/go-xmpp => github.com/selfhoster1312/go-xmpp v0.0.0-20251025163107-4fc2a287a5ee diff --git a/go.sum b/go.sum index e2d6c6be3..d3bd77ab1 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,6 @@ github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f/go.mod h1:AQiQK github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= -github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 h1:ItnC9PEEMESzTbFayxrhKBbuFQOXDBI8yy7NudTcEWs= -github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560/go.mod h1:o38AwUFFS4gzbjSoyIgrZ1h9UeDrKwcci1Pj6baifvI= github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c h1:4mIZQXKYBymQ9coA82nNyG/CjicMNLBZ8cPVrhNUM3g= github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c/go.mod h1:DNSFRLFDFIqm2+0aJzSOVfn25020vldM4SRqz6YtLgI= github.com/SevereCloud/vksdk/v2 v2.17.0 h1:Wll63JSuBTdE0L7+V/PMn9PyhLrWSWIjX76XpWbXTFw= @@ -100,9 +98,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 h1:yEt5djSYb4iNtmV9iJGVday+i4e9u6Mrn5iP64HH5QM= @@ -113,8 +108,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -143,8 +136,6 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa h1:0EefSRfsNrdEwmoGVz4+cMG8++5M2XhvJ1tTRmmrJu8= -github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa/go.mod h1:+KEOMb29OC2kRa5BajwNM2NEjHTbQA/Z3gKYARLHREI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -176,8 +167,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kettek/apng v0.0.0-20191108220231-414630eed80f h1:dnCYnTSltLuPMfc7dMrkz2uBUcEf/OFBR8yRh3oRT98= github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= -github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20 h1:imPu/fy8VhpzD318SnouRyvIgJAoouEyph6+7XAZbbk= -github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20/go.mod h1:Yan1Krk0q1FglHdCkNrF5hFQ4Sgq8LnuG4gI2U4xQAk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= @@ -205,8 +194,6 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 h1:pmPKkN3RJM9wVMZidR99epzK0+gatQiqVtvP1FacZcQ= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A= -github.com/matterbridge/go-xmpp v0.0.0-20240523230155-7154bfeb76e8 h1:UkpezVlW6/j4qB2FSsz4na2FfZUbIDMAblpjw0Cgqcg= -github.com/matterbridge/go-xmpp v0.0.0-20240523230155-7154bfeb76e8/go.mod h1:Vl95jJ8rOBCHUR++A1n6nM5sMk1oHcwInsaxRTR9NM4= github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27 h1:9XSppnbvvReVom+wphkeF4lbhuT6vCYIdyzpwFtW89c= github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27/go.mod h1:/x38AoZf70fK9yZ5gs3BNCaF7/J4QEo4ZpwtLjX95eQ= github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75 h1:GslZKF7lW7oSisycGLpxPO+TnKJuA4VZuTWIfYZrClc= @@ -325,6 +312,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/selfhoster1312/go-xmpp v0.0.0-20251025163107-4fc2a287a5ee h1:O4oPVGk+/FUaZubagHHt/Ng5pe3gWVnk3dpfT+BFw00= +github.com/selfhoster1312/go-xmpp v0.0.0-20251025163107-4fc2a287a5ee/go.mod h1:hLa9WAf0VRpSVguhme06Df+7mIQ6enCEG8udUNYcqX4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 h1:zwQ1HBo5FYwn1ksMd19qBCKO8JAWE9wmHivEpkw/DvE= github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI= @@ -447,8 +436,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -460,8 +449,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -478,8 +467,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -495,8 +484,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -519,20 +508,20 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -547,12 +536,11 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomod.garykim.dev/nc-talk v0.3.0 h1:MZxLc/gX2/+bdOw4xt6pi+qQFUQld1woGfw1hEJ0fbM= gomod.garykim.dev/nc-talk v0.3.0/go.mod h1:q/Adot/H7iqi+H4lANopV7/xcMf+sX3AZXUXqiITwok= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -585,12 +573,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index 0665a5992..530f40e1c 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -512,11 +512,7 @@ StripNick=false ShowTopicChange=false ################################################################### -#Gitter section -#Gitter has been moved to matrix - see https://github.com/42wim/matterbridge/issues/1969 how to migrate -################################################################### - -################################################################### +<<<<<<< HEAD # # Keybase # You should have a separate bridge account on Keybase @@ -1336,98 +1332,6 @@ StripNick=false #OPTIONAL (default false) ShowTopicChange=false -################################################################### -#steam section -################################################################### -[steam] -#You can configure multiple servers "[steam.name]" or "[steam.name2]" -#In this example we use [steam.gamechat] -#REQUIRED - -[steam.gamechat] -#login/pass of your bot. -#Use a dedicated user for this and not your own account! -#REQUIRED -Login="yourlogin" -Password="yourpass" - -#steamguard mail authcode (not the 2FA code) -#OPTIONAL -Authcode="ABCE12" - -## RELOADABLE SETTINGS -## Settings below can be reloaded by editing the file - -#Whether to prefix messages from other bridges to matrix with the sender's nick. -#Useful if username overrides for incoming webhooks isn't enabled on the -#matrix server. If you set PrefixMessagesWithNick to true, each message -#from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i -#OPTIONAL (default false) -PrefixMessagesWithNick=false - -#Nicks you want to ignore. -#Regular expressions supported -#Messages from those users will not be sent to other bridges. -#OPTIONAL -IgnoreNicks="spammer1 spammer2" - -#Messages you want to ignore. -#Messages matching these regexp will be ignored and not sent to other bridges -#See https://regex-golang.appspot.com/assets/html/index.html for more regex info -#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword -IgnoreMessages="^~~ badword" - -#messages you want to replace. -#it replaces outgoing messages from the bridge. -#so you need to place it by the sending bridge definition. -#regular expressions supported -#some examples: -#this replaces cat => dog and sleep => awake -#replacemessages=[ ["cat","dog"], ["sleep","awake"] ] -#this replaces every number with number. 123 => numbernumbernumber -#replacemessages=[ ["[0-9]","number"] ] -#optional (default empty) -ReplaceMessages=[ ["cat","dog"] ] - -#nicks you want to replace. -#see replacemessages for syntax -#optional (default empty) -ReplaceNicks=[ ["user--","user"] ] - -#Extractnicks is used to for example rewrite messages from other relaybots -#See https://github.com/42wim/matterbridge/issues/713 and https://github.com/42wim/matterbridge/issues/466 -#some examples: -#this replaces a message like "Relaybot: something interesting" to "relayeduser: something interesting" -#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ] ] -#you can use multiple entries for multiplebots -#this also replaces a message like "otherbot: (relayeduser) something else" to "relayeduser: something else" -#ExtractNicks=[ [ "Relaybot", "<(.*?)>\\s+" ],[ "otherbot","\\((.*?)\\)\\s+" ] -#OPTIONAL (default empty) -ExtractNicks=[ ["otherbot","<(.*?)>\\s+" ] ] - -#extra label that can be used in the RemoteNickFormat -#optional (default empty) -Label="" - -#RemoteNickFormat defines how remote users appear on this bridge -#See [general] config section for default options -RemoteNickFormat="[{PROTOCOL}] <{NICK}> " - -#Enable to show users joins/parts from other bridges -#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord -#OPTIONAL (default false) -ShowJoinPart=false - -#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285 -#It will strip other characters from the nick -#OPTIONAL (default false) -StripNick=false - -#Enable to show topic changes from other bridges -#Only works hiding/show topic changes from slack bridge for now -#OPTIONAL (default false) -ShowTopicChange=false - ################################################################### # NCTalk (Nextcloud Talk) ################################################################### @@ -1637,18 +1541,6 @@ StripNick=false #OPTIONAL (default false) ShowTopicChange=false -################################################################### -# Harmony -################################################################### - -[harmony.chat_harmonyapp_io] -Homeserver = "https://chat.harmonyapp.io:2289" -Token = "your token goes here" -UserID = "user id of the bot account" -Community = "community id that channels will be located in" -UseUserName = true -RemoteNickFormat = "{NICK}" - ################################################################### #API ################################################################### @@ -1719,7 +1611,7 @@ StripNick=false #for if Matterbridge has write access to the directory your webserver is serving. #It is an alternative to MediaServerUpload. #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 +#irc and xmpp will be shown links to the files on MediaServerDownload # #More information https://github.com/42wim/matterbridge/wiki/Mediaserver-setup-%28advanced%29 #OPTIONAL (default empty) @@ -1861,8 +1753,6 @@ enable=true # discord | channel id | ID:123456789 | See https://github.com/42wim/matterbridge/issues/57 # | category/channel | Media/gaming | Without # symbol. If you're using discord categories to group your channels # ------------------------------------------------------------------------------------------------------------------------------------- - # gitter | username/room | general | As seen in the gitter.im URL - # ------------------------------------------------------------------------------------------------------------------------------------- # hipchat | id_channel | example needed | See https://www.hipchat.com/account/xmpp for the correct channel # ------------------------------------------------------------------------------------------------------------------------------------- # irc | channel | #general | The # symbol is required and should be lowercase! @@ -1881,8 +1771,6 @@ enable=true # slack | channel name | general | Do not include the # symbol # | channel id | ID:C123456 | The underlying ID of a channel. This doesn't work with webhooks. # ------------------------------------------------------------------------------------------------------------------------------------- - # steam | chatid | example needed | The number in the URL when you click "enter chat room" in the browser - # ------------------------------------------------------------------------------------------------------------------------------------- # nctalk | token | xs25tz5y | The token in the URL when you are in a chat. It will be the last part of the URL. # ------------------------------------------------------------------------------------------------------------------------------------- # telegram | chatid | -123456789 | A large negative number. see https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau @@ -1944,10 +1832,6 @@ enable=true account="zulip.streamchat" channel="general/topic:mytopic" - [[gateway.inout]] - account="harmony.chat_harmonyapp_io" - channel="channel id goes here" - #API example #[[gateway.inout]] #account="api.local" diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..aa4ce1f25 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,76 @@ +# Integration tests + +This directory is for integration tests only. Unit tests reside in their +respective modules. + +## Motivation + +Unit testing only checks that assumptions within the code are held. Integration +tests can provide real-life testing capabilities to ensure that a feature is +implemented properly, even when going through the remote chat service. + +## Methodology + +Each matterbrige supported protocol has its own corresponding folder in this +`tests` folder. Each protocol's integration's tests: + +- are run in a specific CI job with a specific environment +- are documented to be run locally with a specific environment +- provides implementation for incoming tests, and outgoing tests to avoid + feature mismatch (for example, a bridge that could receive attachments + from other networks but not the other way around) +- are run sequentially, although different protocols are tested in parallel + +> [!NOTE] +> `incoming` refers to a message received on that protocol (to be delivered +> to others via matterbridge), while `outgoing` refers to a message received +> from another matterbridge protocol to be delivered to this protocol. + +## Architecture + +At the moment, integration tests require 3 components to play together: + +- a real matterbridge instance configured with a protocol account (`pa`) to a protocol room (`pr`) +- another protocol client (`pc`), connected to the same protocol room (`pr`), following a test scenario +- a matterbridge API client (`ac`), connected client following the same test scenario (called `ac`) + +> [!NOTE] +> For each tested protocol, the test suite requires two accounts on the remote server. + +The `matterbridge.toml` in each integration test folder determines the config +used for the matterbridge daemon used in the tests. + +The API client is located in `tests/api/api.go` and accepts as arguments: + +- one of the supported tests (eg. `incoming-message`) +- a timeout in seconds, after which it will automatically fail (default: 5) + +The protocol client is located in `tests/PROTOCOL/PROTOCOL.go`, is a proper +go module that can be run with `go run .`, and accepts as arguments: + +- one of the supported tests (eg. `incoming-message`) +- a timeout in seconds, after which it will automatically fail (default: 5) + +Both binaries: + +- run in the background, with the same set of CLI arguments +- fail the entire pipeline if they individually fail +- success as soon as the testing criteria is met + +For example, in the case of the `incoming-message` test, the protocol client +will successfully exit as soon as the message is sent without errors, +but the API client will wait and either: + +- successfully exit when receiving the `test-incoming-message` +- fail after the timeout was reached + +## Supported tests + +The following test scenarios are supported at the moment: + +- `incoming-message` steps: + - `pc` sends `test-incoming-message` in `pr` using the native protocol + - `ac` receives ` test-incoming-message` in `pr` using the matterbridge API +- `outgoing-message` steps: + - `ac` sends `test-outgoing-message` in `pr` using the matterbridge API + - `pc` receives ` test-outgoing-message` in `pr` using the native protocol diff --git a/tests/api/api.go b/tests/api/api.go new file mode 100644 index 000000000..d46a5b152 --- /dev/null +++ b/tests/api/api.go @@ -0,0 +1,155 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "net/http" + "io" + "os" + "strconv" + "time" +) + +type Api struct { + BaseUrl string +} + +func NewApi() *Api { + s := Api{ BaseUrl: "http://localhost:4242/api" } + return &s +} + +func (a *Api) SendMessage(message *Message) error { + endpoint := fmt.Sprintf("%s/message", a.BaseUrl) + json_msg, err := json.Marshal(message) + if err != nil { + fmt.Printf("Failed to serialize message:\n%s", err) + return err + } + + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(json_msg)) + // req.Header.Set("X-Custom-Header", "myvalue") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + fmt.Println("response Status:", resp.Status) + fmt.Println("response Headers:", resp.Header) + body, _ := io.ReadAll(resp.Body) + fmt.Println("response Body:", string(body)) + + if resp.StatusCode != 200 { + return errors.New("Failed to POST message to the API") + } + + return nil +} + +type Message struct { + Text string `json:"text"` + Channel string `json:"channel"` + Username string `json:"username"` + UserId string `json:"userid"` + Avatar string `json:"avatar"` + Account string `json:"account"` + Event string `json:"event"` + Protocol string `json:"protocol"` + Gateway string `json:"gateway"` + ParentId string `json:"parent_id"` + Timestamp string `json:"timestamp"` + Id string `json:"id"` + Extra map[string][]interface {} `json:"Extra"` +} + +func newMessage(text string) *Message { + s:= Message { + Text: text, + Channel: "testchannel", + Username: "apitest", + UserId: "apitest_id", + Account: "api.test", + Protocol: "api", + Gateway: "test", + Timestamp: "2019-01-09T22:53:51.618575236+01:00", + } + + return &s +} + +func scenario_outgoing_message(api *Api) error { + err := api.SendMessage(newMessage("outgoing-message-test")) + if err != nil { + return err + } + + return nil +} + +func apply_scenario(scenario string, api *Api) error { + switch scenario { + case "outgoing-message": + return scenario_outgoing_message(api) + default: + fmt.Printf("ERROR: Unknown test scenario: %s\n", scenario) + os.Exit(1) + } + + return nil +} + +func apply_scenario_timeout(scenario string, timeout int, api *Api) error { + timeoutChan := make(chan error, 1) + + go func() { + timeoutChan <- apply_scenario(scenario, api) + }() + + select { + case err := <-timeoutChan: + if err != nil { + return fmt.Errorf("ERROR: Scenario %s failed because of error:\n%s", scenario, err) + } + + fmt.Printf("OK: Scenario %s", scenario) + case <-time.After(time.Duration(timeout) * time.Second): + return fmt.Errorf("ERROR: Scenario %s timeout after %d seconds", scenario, timeout) + } + + return nil +} + +func main() { + flag.Parse() + args := flag.Args() + scenario := args[0] + var timeout int + if len(args) < 2 { + timeout = 5 + } else { + timeout2, err := strconv.Atoi(args[1]) + if err != nil { + fmt.Printf("ERROR: Invalid timeout: %s\n", args[1]) + os.Exit(1) + } + timeout = timeout2 + } + + fmt.Println("Initializing API") + api := NewApi() + + fmt.Printf("Running scenario %s (timeout=%ds)\n", scenario, timeout) + + err := apply_scenario_timeout(scenario, timeout, api) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/tests/api/go.mod b/tests/api/go.mod new file mode 100644 index 000000000..1f53cb3c7 --- /dev/null +++ b/tests/api/go.mod @@ -0,0 +1,3 @@ +module github.com/selfhoster1312/matterbridge-tests/api + +go 1.25.1 diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 000000000..90dfa5718 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,199 @@ +#! /usr/bin/env bash + +# Don't stop on first error because we still want to terminate background PIDs +set -ux + +# ./test.sh PROTOCOL SCENARIO [TIMEOUT] +PROTOCOL="$1" +SCENARIO="$2" +TIMEOUT="${3:-20}" + +# Setup steps are performed in this order: +# +# - setup.sh comes online (mocking protocol server) +# - matterbridge comes online (connecting to the protocol server) +# - API client comes online (connecting to matterbridge) +# - $PROTOCOL client comes online (connecting to protocol server) +# - test.sh sends SIGUSR1 to API client to notify that $PROTOCOL client is ready +# so that it can start performing the test (read-only tests are not affected +# by this signal) +# +# What counts in the timeouts: +# +# - no timeout: compilation time for matterbridge, api client, and $PROTOCOL client +# - STARTUP_TIMEOUT: time for setup.sh to come online ; time for matterbridge to come online +# - TIMEOUT: time for api/$PROTOCOL client to become "READY" then perform the test + +# The number of second to wait for matterbridge and setup.sh each +# to be done with startup. Otherwise, tests are aborted. +STARTUP_TIMEOUT=30 +# Time allocated for the various clients to each finish shutting down. +TEARDOWN_TIMEOUT=30 + +################################################################################ + +# We start matterbridge and wait for the "Now relaying messages" output +start_matterbridge() { + echo "[$PROTOCOL] Starting matterbridge" + # Building the matterbridge binary in the foreground so build time doesn't count in the timeout + if go build .; then + # Start matterbridge instance in background + ./matterbridge -conf tests/$PROTOCOL/matterbridge.toml -debug 2>&1 >tests/$PROTOCOL/matterbridge.log & + MATTERBRIDGE_PID=$! + echo "MATTERBRIDGE_PID=$MATTERBRIDGE_PID" + + # Wait some magic seconds + for i in $(seq 1 $STARTUP_TIMEOUT); do + if grep "Now relaying messages" tests/$PROTOCOL/matterbridge.log 2>&1 >/dev/null; then + echo "[$PROTOCOL] Started matterbridge successfully" + return + fi + + sleep 1 + done + fi + + echo "[$PROTOCOL] Failed to start matterbridge" + return 1 +} + +# We start the additional setup.sh steps if they exist +# If setup_status.sh exists, its exit code is determines whether the setup is finished +# Otherwise, setup.sh is assumed to be instantly successful +start_setup() { + echo "[$PROTOCOL] Starting setup.sh" + if [ -f tests/$PROTOCOL/setup.sh ]; then + ./tests/$PROTOCOL/setup.sh 2>&1 >tests/$PROTOCOL/setup.log & + SETUP_PID=$! + fi + + if [ ! -f tests/$PROTOCOL/setup_status.sh ]; then + # Setup assumed successful because we have no way to test + return + fi + + # Wait some magic seconds + for i in $(seq 1 $STARTUP_TIMEOUT); do + if tests/$PROTOCOL/setup_status.sh; then + echo "[$PROTOCOL] Started setup.sh successfully" + return + fi + sleep 1 + done + + echo "[$PROTOCOL] Failed to start setup.sh" + return 1 +} + +wait_kill() { + if ps -p $1; then + kill $1 + fi + for i in $(seq 1 $TEARDOWN_TIMEOUT); do + if ! ps -p $1 > /dev/null; then + return + fi + done + + # Timeout. Start KILLing things for real. + echo "[$PROTOCOL] wait_kill timeout. Now sending SIGKILL." + kill -9 $1 +} + +teardown() { + if [[ "${MATTERBRIDGE_PID:-FOO}" != "FOO" ]]; then + echo "[$PROTOCOL] Shutting down matterbridge…" + wait_kill $MATTERBRIDGE_PID + fi + if [ -f tests/$PROTOCOL/setup.sh ] && [[ "${SETUP_PID:-FOO}" != "FOO" ]]; then + echo "[$PROTOCOL] Shutting down setup.sh…" + wait_kill $SETUP_PID + fi + if [[ "${PROTOCOL_PID:-FOO}" != "FOO" ]]; then + echo "[$PROTOCOL] Shutting down $PROTOCOL client…" + wait_kill $PROTOCOL_PID + fi + if [[ "${API_PID:-FOO}" != "FOO" ]]; then + echo "[$PROTOCOL] Shutting down API client…" + wait_kill $API_PID + fi +} + +go_build() { + echo "[$PROTOCOL] Building $1 client" + cd $1 + if ! go build .; then + echo "[$PROTOCOL] Building $1 failed… Aborting tests." + teardown + exit 1 + fi + cd ../.. +} + +echo "Current working dir: $(pwd)" + +# Wait for setup.sh/matterbridge to start and populate the SETUP_PID/MATTERBRIDGE_PID variables +if ! start_setup || ! start_matterbridge; then + echo "[$PROTOCOL] Failed test setup steps… Aborting." + teardown + exit 1 +fi + +echo "[$PROTOCOL] Successful setup steps… Continuing." +# Building the binaries does not count in the timeout +go_build tests/$PROTOCOL +go_build tests/api +echo "[$PROTOCOL] Successful client builds… Continuing." + +# Start PROTOCOL client in background +cd tests/$PROTOCOL +timeout -s KILL $TIMEOUT ./$PROTOCOL $SCENARIO $TIMEOUT &>$PROTOCOL.log & +PROTOCOL_PID=$! +# Wait for protocol client to be done with setup +PROTOCOL_READY=0 +for i in 1 2 3; do + if grep "Running scenario $SCENARIO" $PROTOCOL.log 2>&1 >/dev/null; then + echo "[$PROTOCOL] Protocol client ready to start test." + PROTOCOL_READY=1 + break + fi + sleep 1 +done +cd ../.. + +if [[ $PROTOCOL_READY == 0 ]]; then + echo "[$PROTOCOL] Protocol client never reported ready to start test." + teardown + exit 1 +fi + + +# Start API client in background +cd tests/api +timeout -s KILL $TIMEOUT ./api $SCENARIO $TIMEOUT &>../$PROTOCOL/api.log & +API_PID=$! +cd ../.. + +STATUS=0 + +# Now wait for tests to finish or timeout +if ! wait $API_PID; then + echo "[$PROTOCOL] API client failed. See tests/$PROTOCOL/api.log" + STATUS=1 +fi + +if ! wait $PROTOCOL_PID; then + echo "[$PROTOCOL] PROTOCOL client failed. See tests/$PROTOCOL/$PROTOCOL.log" + STATUS=1 +fi + +echo "[$PROTOCOL] Reported test status: $STATUS. Now performing teardown." +teardown + +if [[ $STATUS == 0 ]]; then + echo "OK $SCENARIO" +else + echo "FAIL $SCENARIO" +fi + +exit $STATUS diff --git a/tests/xmpp/.xmpp.mellium.go b/tests/xmpp/.xmpp.mellium.go new file mode 100644 index 000000000..214a23749 --- /dev/null +++ b/tests/xmpp/.xmpp.mellium.go @@ -0,0 +1,150 @@ +package main + +import ( + // "bytes" + "flag" + "fmt" + // "net/http" + // "io" + "context" + // "encoding/xml" + // "errors" + "time" + "strconv" + "os" + + "mellium.im/sasl" + "mellium.im/xmpp" + // "mellium.im/xmpp/dial" + "mellium.im/xmpp/jid" + "mellium.im/xmpp/stanza" + // "mellium.im/xmpp/stream" + +) + +func scenario_outgoing_message(client *xmpp.Session) error { + // err := api.SendMessage(newMessage("outgoing-message-test")) + // if err != nil { + // return err + // } + + return nil +} + +func apply_scenario(scenario string, client *xmpp.Session) error { + // switch scenario { + // case "outgoing-message": + // return scenario_outgoing_message(api, client) + // default: + // fmt.Printf("ERROR: Unknown test scenario: %s\n", scenario) + // os.Exit(1) + // } + + return nil +} + +func apply_scenario_timeout(scenario string, timeout int, client *xmpp.Session) error { + timeoutChan := make(chan error, 1) + + go func() { + timeoutChan <- apply_scenario(scenario, client) + }() + + select { + case err := <-timeoutChan: + if err != nil { + return fmt.Errorf("ERROR: Scenario %s failed because of error:\n%s", scenario, err) + } + + fmt.Printf("OK: Scenario %s", scenario) + case <-time.After(time.Duration(timeout) * time.Second): + return fmt.Errorf("ERROR: Scenario %s timeout after %d seconds", scenario, timeout) + } + + return nil +} + +func main() { + flag.Parse() + args := flag.Args() + scenario := args[0] + var timeout int + if len(args) < 2 { + timeout = 5 + } else { + timeout2, err := strconv.Atoi(args[1]) + if err != nil { + fmt.Printf("ERROR: Invalid timeout: %s\n", args[1]) + os.Exit(1) + } + timeout = timeout2 + } + + fmt.Println("Initializing client") + client, err := NewClient() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Printf("Running scenario %s (timeout=%ds)\n", scenario, timeout) + + err = apply_scenario_timeout(scenario, timeout, client) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +// MessageBody is a message stanza that contains a body. It is normally used for +// chat messages. +type MessageBody struct { + stanza.Message + Body string `xml:"body"` +} + +func NewClient() (*xmpp.Session, error) { + j, _ := jid.Parse("xmpp@matterbridge-test.localhost") + ctx := context.Background() + + s, err := xmpp.DialClientSession(ctx, j, xmpp.BindResource(), xmpp.SASL("", "password", sasl.ScramSha1Plus, sasl.Plain)) + if err != nil { + return nil, fmt.Errorf("error dialing session: %w", err) + } + + // s, err := xmpp.NewSession(ctx, j.Domain(), j, conn, 0, xmpp.NewNegotiator(func(*xmpp.Session, *xmpp.StreamConfig) xmpp.StreamConfig { + // return xmpp.StreamConfig{ + // Lang: "en", + // Features: []xmpp.StreamFeature{ + // xmpp.BindResource(), + // // xmpp.StartTLS(&tls.Config{ + // // ServerName: j.Domain().String(), + // // MinVersion: tls.VersionTLS12, + // // }), + // xmpp.SASL("", "password", sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain), + // }, + // // TeeIn: xmlIn, + // // TeeOut: xmlOut, + // } + // })) + + // if err != nil { + // return nil, return fmt.Errorf("Error connecting: %w", err) + // } + + defer func() { + fmt.Println("Closing conn…") + if err := s.Conn().Close(); err != nil { + fmt.Printf("Error closing connection: %q", err) + } + }() + + // Send initial presence to let the server know we want to receive messages. + err = s.Send(ctx, stanza.Presence{Type: stanza.AvailablePresence}.Wrap(nil)) + if err != nil { + return nil, fmt.Errorf("Error sending initial presence: %w", err) + } + + return s, err +} + diff --git a/tests/xmpp/go.mod b/tests/xmpp/go.mod new file mode 100644 index 000000000..bf10e442c --- /dev/null +++ b/tests/xmpp/go.mod @@ -0,0 +1,12 @@ +module github.com/selfhoster1312/matterbridge-test/xmpp + +go 1.25.1 + +replace github.com/xmppo/go-xmpp => github.com/selfhoster1312/go-xmpp v0.0.0-20251025163107-4fc2a287a5ee + +require github.com/xmppo/go-xmpp v0.2.18 + +require ( + github.com/google/uuid v1.6.0 // indirect + golang.org/x/net v0.46.0 // indirect +) diff --git a/tests/xmpp/go.sum b/tests/xmpp/go.sum new file mode 100644 index 000000000..b50c9d14f --- /dev/null +++ b/tests/xmpp/go.sum @@ -0,0 +1,6 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/selfhoster1312/go-xmpp v0.0.0-20251025163107-4fc2a287a5ee h1:O4oPVGk+/FUaZubagHHt/Ng5pe3gWVnk3dpfT+BFw00= +github.com/selfhoster1312/go-xmpp v0.0.0-20251025163107-4fc2a287a5ee/go.mod h1:hLa9WAf0VRpSVguhme06Df+7mIQ6enCEG8udUNYcqX4= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= diff --git a/tests/xmpp/matterbridge.toml b/tests/xmpp/matterbridge.toml new file mode 100644 index 000000000..e92ebfaf6 --- /dev/null +++ b/tests/xmpp/matterbridge.toml @@ -0,0 +1,31 @@ +[xmpp.test] +Server="matterbridge-test.localhost:52222" +Jid="matterbridge-xmpp@matterbridge-test.localhost" +Password="AnythingGoesHere" +Muc="muc.matterbridge-test.localhost" +Nick="matterbridge-xmpp" +Label="" +RemoteNickFormat="<{NICK}> " +ShowJoinPart=false +StripNick=false +ShowTopicChange=false +NoTLS=true +StartTLS=false +InsecureAllowUnencryptedAuth = true +SkipTLSVerify=true +# Mechanism = "PLAIN" + +[api.test] +BindAddress="127.0.0.1:4242" +Buffer=1000 +RemoteNickFormat="<{NICK}>" + +[[gateway]] +name="test" +enable=true +[[gateway.inout]] +account="xmpp.test" +channel="test" +[[gateway.inout]] +account = "api.test" +channel = "api" diff --git a/tests/xmpp/prosody.cfg.lua b/tests/xmpp/prosody.cfg.lua new file mode 100644 index 000000000..0dbddde6f --- /dev/null +++ b/tests/xmpp/prosody.cfg.lua @@ -0,0 +1,83 @@ +admins = { } + +-- Github action has 5222 reserved? +c2s_ports = { 52222 } + +modules_enabled = { + "disco"; -- Service discovery + "roster"; -- Allow users to have a roster. Recommended ;) + "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. + "tls"; -- Add support for secure TLS on c2s/s2s connections + "blocklist"; -- Allow users to block communications with other users + "bookmarks"; -- Synchronise the list of open rooms between clients + "carbons"; -- Keep multiple online clients in sync + "dialback"; -- Support for verifying remote servers using DNS + "limits"; -- Enable bandwidth limiting for XMPP connections + "pep"; -- Allow users to store public and private data in their account + "private"; -- Legacy account storage mechanism (XEP-0049) + "smacks"; -- Stream management and resumption (XEP-0198) + "vcard4"; -- User profiles (stored in PEP) + "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard + "account_activity"; -- Record time when an account was last used + --"cloud_notify"; -- Push notifications for mobile devices + "csi_simple"; -- Simple but effective traffic optimizations for mobile devices + "invites"; -- Create and manage invites + "invites_adhoc"; -- Allow admins/users to create invitations via their client + "invites_register"; -- Allows invited users to create accounts + "ping"; -- Replies to XMPP pings with pongs + "register"; -- Allow users to register on this server using a client and change passwords + "time"; -- Let others know the time here on this server + "uptime"; -- Report how long server has been running + "version"; -- Replies to server version requests + "mam"; -- Store recent messages to allow multi-device synchronization + --"turn_external"; -- Provide external STUN/TURN service for e.g. audio/video calls + + -- Admin interfaces + -- "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands + -- "admin_shell"; -- Allow secure administration via 'prosodyctl shell' + + -- HTTP modules + --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP" + --"http_openmetrics"; -- for exposing metrics to stats collectors + --"websocket"; -- XMPP over WebSockets +} + +c2s_require_encryption = false +allow_unencrypted_plain_auth = true + +-- Don't use this in prod! +-- See https://prosody.im/doc/modules/mod_muc +muc_room_locking = false +muc_room_lock_timeout = 0 + +modules_disabled = { + "s2s"; -- Handle server-to-server connections +} + +limits = { + c2s = { + rate = "10kb/s"; + }; + s2sin = { + rate = "30kb/s"; + }; +} + +-- Users are logged in with a random JID. +authentication = "any" +-- Database is kept in memory (this is only for tests) +storage = "memory" + +archive_expires_after = "1w" -- Remove archived messages after 1 week + +log = { + debug = "*console"; +} + + +VirtualHost "matterbridge-test.localhost" +Component "muc.matterbridge-test.localhost" "muc" + modules_enabled = { "muc_mam" } + +---Set up a file sharing component +--Component "share.example.com" "http_file_share" diff --git a/tests/xmpp/setup.sh b/tests/xmpp/setup.sh new file mode 100755 index 000000000..cf6e0c289 --- /dev/null +++ b/tests/xmpp/setup.sh @@ -0,0 +1,5 @@ +#! /usr/bin/env bash + +BASEDIR="$(dirname "$0")" + +prosody --config "$BASEDIR"/prosody.cfg.lua -F diff --git a/tests/xmpp/setup_status.sh b/tests/xmpp/setup_status.sh new file mode 100755 index 000000000..d948655d1 --- /dev/null +++ b/tests/xmpp/setup_status.sh @@ -0,0 +1,6 @@ +#! /usr/bin/env bash + +set -eu + +BASEDIR="$(dirname "$0")" +grep "Startup complete" "$BASEDIR"/setup.log 2>&1>/dev/null \ No newline at end of file diff --git a/tests/xmpp/xmpp.go b/tests/xmpp/xmpp.go new file mode 100644 index 000000000..d747e02aa --- /dev/null +++ b/tests/xmpp/xmpp.go @@ -0,0 +1,128 @@ +package main + +import ( + // "bytes" + "flag" + "fmt" + // "net/http" + // "io" + // "context" + // "encoding/xml" + // "errors" + "crypto/tls" + "os" + "strconv" + "time" + + "github.com/xmppo/go-xmpp" +) + +func scenario_outgoing_message(client *xmpp.Client) error { + for { + m, err := client.Recv() + if err != nil { + // TODO: Some errors should not be fatal + return err + } + + switch v := m.(type) { + case xmpp.Chat: + if v.Type == "groupchat" && v.Remote == "test@muc.matterbridge-test.localhost/matterbridge-xmpp" && v.Text == " outgoing-message-test" { + // Test successful + return nil + } else { + fmt.Printf("Received MUC message from %s:\n%s\n", v.Remote, v.Text) + } + default: + // Do nothing + } + } + + return nil +} + +func apply_scenario(scenario string, client *xmpp.Client) error { + switch scenario { + case "outgoing-message": + return scenario_outgoing_message(client) + default: + return fmt.Errorf("ERROR: Unknown test scenario: %s\n", scenario) + } + + return nil +} + +func apply_scenario_timeout(scenario string, timeout int, client *xmpp.Client) error { + timeoutChan := make(chan error, 1) + + go func() { + timeoutChan <- apply_scenario(scenario, client) + }() + + select { + case err := <-timeoutChan: + if err != nil { + return fmt.Errorf("ERROR: Scenario %s failed because of error:\n%s", scenario, err) + } + + fmt.Printf("OK: Scenario %s", scenario) + return nil + case <-time.After(time.Duration(timeout) * time.Second): + return fmt.Errorf("ERROR: Scenario %s timeout after %d seconds", scenario, timeout) + } + + return nil +} + +func main() { + flag.Parse() + args := flag.Args() + scenario := args[0] + var timeout int + if len(args) < 2 { + timeout = 5 + } else { + timeout2, err := strconv.Atoi(args[1]) + if err != nil { + fmt.Printf("ERROR: Invalid timeout: %s\n", args[1]) + os.Exit(1) + } + timeout = timeout2 + } + + fmt.Println("Initializing client") + client, err := NewClient() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // client.JoinMUCNoHistory("test@muc.matterbridge-test.localhost", "client-xmpp") + client.JoinOrCreateMUCNoHistoryDoNotUseOutsideTests("test@muc.matterbridge-test.localhost", "client-xmpp") + + fmt.Printf("Running scenario %s (timeout=%ds)\n", scenario, timeout) + + err = apply_scenario_timeout(scenario, timeout, client) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func NewClient() (*xmpp.Client, error) { + options := xmpp.Options{ + Host: "localhost:52222", + User: "client-xmpp@matterbridge-test.localhost", + Password: "testxmpp_password", + NoTLS: true, + StartTLS: false, + Debug: true, + Session: true, + InsecureAllowUnencryptedAuth: true, + TLSConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + return options.NewClient() +}