Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions bridge/mastodon/mastodon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package mastodon

import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"regexp"
"strings"

"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"

mastodon "github.com/mattn/go-mastodon"
)

var (
htmlReplacementTag = regexp.MustCompile("<[^>]*>")
)

type Broom struct {
channel string
ctx context.Context

Check failure on line 24 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

found a struct that contains a context.Context field (containedctx)
ctxCancel context.CancelFunc
}

type Bmastodon struct {
*bridge.Config
c *mastodon.Client
account *mastodon.Account

rooms []Broom
}

func New(cfg *bridge.Config) bridge.Bridger {
b := &Bmastodon{Config: cfg}
return b
}

func (b *Bmastodon) Connect() error {
b.Log.Infof("Connecting %s", b.GetString("Server"))

config := mastodon.Config{
Server: b.GetString("Server"),
ClientID: b.GetString("ClientID"),
ClientSecret: b.GetString("ClientSecret"),
AccessToken: b.GetString("AccessToken"),
}
b.c = mastodon.NewClient(&config)
var err error
b.account, err =
b.c.GetAccountCurrentUser(context.Background())
if err != nil {
return nil
}

return nil
}

func (b *Bmastodon) Disconnect() error {
for _, r := range b.rooms {
r.ctxCancel()
}

return nil
}

func (b *Bmastodon) JoinChannel(channel config.ChannelInfo) error {

Check failure on line 69 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Function 'JoinChannel' is too long (67 > 60) (funlen)
var channelType string
var ch chan mastodon.Event
var err error
ctx, ctxCancel := context.WithCancel(context.Background())
if channel.Name == "home" {

Check failure on line 74 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

string `home` has 4 occurrences, make it a constant (goconst)
// You are talking to the home channel
b.rooms = append(b.rooms, Broom{
channel: "home",
ctx: ctx,
ctxCancel: ctxCancel,
})
channelType = "home"
ch, err = b.c.StreamingUser(ctx)
} else if channel.Name == "local" {

Check failure on line 83 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

string `local` has 4 occurrences, make it a constant (goconst)
// You are talking to the local channel
b.rooms = append(b.rooms, Broom{
channel: "local",
ctx: ctx,
ctxCancel: ctxCancel,
})
channelType = "local"
ch, err = b.c.StreamingPublic(ctx, true)
} else if channel.Name == "remote" {

Check failure on line 92 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

string `remote` has 4 occurrences, make it a constant (goconst)
// You are talking to the remote channel
b.rooms = append(b.rooms, Broom{
channel: "remote",
ctx: ctx,
ctxCancel: ctxCancel,
})
channelType = "remote"
ch, err = b.c.StreamingPublic(ctx, false)
} else if strings.HasPrefix(channel.Name, "@") {
// You are talking to a private user
b.rooms = append(b.rooms, Broom{
channel: channel.Name,
ctx: ctx,
ctxCancel: ctxCancel,
})
channelType = "direct"
ch, err = b.c.StreamingDirect(ctx)
} else {
ctxCancel()
return fmt.Errorf("invalid channel name: %s", channel.Name)

Check failure on line 112 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"invalid channel name: %s\", channel.Name)" (err113)
}
if err != nil {
return err
}

go func() {
b.Log.Debugf("run golang channel on streaming api call, channel name: %v", channel.Name)
for msg := range ch {
switch t := msg.(type) {
case *mastodon.UpdateEvent:
switch channelType {
case "local", "home", "remote":
b.handleSendRemoteStatus(t.Status, channel.Name)
default:
b.Log.Debugf("run UpdateEvent on unsupported channelType: %s", channelType)
}
case *mastodon.ConversationEvent:
switch channelType {
case "local", "home", "remote":
// Not a conversation
b.Log.Debugf("run ConversationEvent on unsupported channelType: %s", channelType)
default:
b.handleSendRemoteStatus(t.Conversation.LastStatus, channel.Name)
}
}

}
}()
return nil
}

func (b *Bmastodon) handleSendRemoteStatus(msg *mastodon.Status, channel string) {

Check failure on line 144 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unexported method "handleSendRemoteStatus" for struct "Bmastodon" should be placed after the exported method "Send" (funcorder)
if msg.Account.ID == b.account.ID {
// Ignore messages that are from the bot user
return
}
remoteMessage := config.Message{
Text: htmlReplacementTag.ReplaceAllString(msg.Content, ""),
Channel: channel,
Username: msg.Account.DisplayName,
UserID: string(msg.Account.ID),
Account: b.Account,
Avatar: msg.Account.Avatar,
ID: string(msg.ID),
Extra: map[string][]any{},
}
if len(msg.MediaAttachments) > 0 {
remoteMessage.Extra["file"] = []any{}
}
for _, media := range msg.MediaAttachments {
resp, err := http.Get(media.RemoteURL)
if err != nil {
continue
}
defer resp.Body.Close()

Check failure on line 167 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `resp.Body.Close` is not checked (errcheck)
if resp.StatusCode != http.StatusOK {
continue
}
b, err := io.ReadAll(resp.Body)
if err != nil {
continue
}
remoteMessage.Extra["file"] = append(remoteMessage.Extra["file"], config.FileInfo{
Name: media.Description,
Data: &b,
Size: int64(len(b)),
Avatar: false,
})
}
b.Log.Debugf("<= Message is %#v", remoteMessage)
b.Remote <- remoteMessage
}

func (b *Bmastodon) Send(msg config.Message) (string, error) {
ctx := context.Background()

// Standard Message Send
if msg.Event == "" {
sentMessage, err := b.handleSendingMessage(ctx, &msg)
if err != nil {
b.Log.Errorf("Could not send message to room %v from %v: %v", msg.Channel, msg.Username, err)

return "", nil
}
return string(sentMessage.ID), nil
}

// Message Deletion
if msg.Event == config.EventMsgDelete {
if msg.UserID != string(b.account.ID) {
b.Log.Errorf("Can not delete a status that is owned by a different account")
return "", nil
}
err := b.c.DeleteStatus(context.Background(), mastodon.ID(msg.ID))
return "", err
}

// Message is not a type that is currently supported
return "", nil
}

func (b *Bmastodon) handleSendingMessage(ctx context.Context, msg *config.Message) (*mastodon.Status, error) {
toot := mastodon.Toot{
Status: msg.Text,
InReplyToID: "",
MediaIDs: []mastodon.ID{},
Sensitive: false,
SpoilerText: "",
Visibility: "public",
Language: "",
}
if strings.HasPrefix(msg.Channel, "#") {
toot.Status += " " + msg.Channel
}
if strings.HasPrefix(msg.Channel, "@") {
toot.Visibility = "private"
}
if msg.ParentID != "" {
toot.InReplyToID = mastodon.ID(msg.ParentID)
if toot.Visibility == "public" {
toot.Visibility = "unlisted"
}
}

for _, file := range msg.Extra["file"] {
fileInfo, ok := file.(config.FileInfo)
if !ok {
continue
}
var r io.Reader
var err error
var resp *http.Response
defer func() {
if resp != nil {
resp.Body.Close()

Check failure on line 247 in bridge/mastodon/mastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `resp.Body.Close` is not checked (errcheck)
}
}()
if fileInfo.URL != "" {
resp, err = http.Get(fileInfo.URL)
if err != nil {
continue
}
if resp.StatusCode != http.StatusOK {
continue
}
r = resp.Body
} else if fileInfo.Data != nil {
r = bytes.NewReader(*fileInfo.Data)
}
attachment, err := b.c.UploadMediaFromMedia(ctx, &mastodon.Media{
File: r,
Description: fileInfo.Comment,
})
if err != nil {
continue
}
toot.MediaIDs = append(toot.MediaIDs, attachment.ID)
}

return b.c.PostStatus(ctx, &toot)
}
12 changes: 12 additions & 0 deletions gateway/bridgemap/bmastodon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !nomastodon
// +build !nomastodon

package bridgemap

import (
bmastodon "github.com/42wim/matterbridge/bridge/mastodon"
)
Comment thread
poVoq marked this conversation as resolved.

func init() {

Check failure on line 10 in gateway/bridgemap/bmastodon.go

View workflow job for this annotation

GitHub Actions / golangci-lint

don't use `init` function (gochecknoinits)
FullMap["mastodon"] = bmastodon.New
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/matterbridge/matterclient v0.0.0-20240817214420-3d4c3aef3dc1
github.com/matterbridge/telegram-bot-api/v6 v6.5.0
github.com/mattermost/mattermost/server/public v0.1.6
github.com/mattn/go-mastodon v0.0.10
github.com/mattn/godown v0.0.1
github.com/mdp/qrterminal v1.0.1
github.com/mitchellh/mapstructure v1.5.0
Expand Down Expand Up @@ -115,6 +116,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tinylib/msgp v1.2.0 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-mastodon v0.0.10 h1:wz1d/aCkJOIkz46iv4eAqXHVreUMxydY1xBWrPBdDeE=
github.com/mattn/go-mastodon v0.0.10/go.mod h1:YBofeqh7G6s787787NQR8erBYz6fKDu+KNMrn5RuD6Y=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
Expand Down Expand Up @@ -388,6 +390,8 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
Expand Down
27 changes: 27 additions & 0 deletions matterbridge.toml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,31 @@ Label=""
# REQUIRED
Team="myteam"

###################################################################
#
# Mastodon
#
###################################################################

[mastodon]
[mastodon.myaccount]

# Your mastodon instance url.
# REQUIRED
Server="https://mastodon.social"

# Application client key
# REQUIRED
ClientID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Application client secret
# REQUIRED
ClientSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Application access token
# REQUIRED
AccessToken="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

###################################################################
# Microsoft teams section
# See https://github.com/42wim/matterbridge/wiki/MS-Teams-setup
Expand Down Expand Up @@ -1757,6 +1782,8 @@ enable=true
# -------------------------------------------------------------------------------------------------------------------------------------
# irc | channel | #general | The # symbol is required and should be lowercase!
# -------------------------------------------------------------------------------------------------------------------------------------
# mastodon | channel | home | The channel can be home or local or @name@mastodon.social
# -------------------------------------------------------------------------------------------------------------------------------------
# | channel | general | This is the channel name as seen in the URL, not the display name
# mattermost | channel id | ID:oc4wifyuojgw5f3nsuweesmz8w | This is the channel ID (only use if you know what you're doing)
# -------------------------------------------------------------------------------------------------------------------------------------
Expand Down
Loading