forked from 42wim/matterbridge
-
Notifications
You must be signed in to change notification settings - Fork 26
Add mastodon #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add mastodon #14
Changes from 3 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
b8a8b29
Add mastodon
lil5 d289026
Fix go mod
lil5 54c1413
Fix send from mastodon
lil5 f833bf9
Update code references to old repo
lil5 5800484
Add documentation
lil5 05d6928
more docs
lil5 94c9314
Golang ci now works for all code
lil5 0ca3b6f
Better set of channel name
lil5 032b52e
Update golangci-lint to v2.6
lil5 d58f996
Update go to 1.25
lil5 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| 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 { | ||
| var channelType string | ||
| var ch chan mastodon.Event | ||
| var err error | ||
| ctx, ctxCancel := context.WithCancel(context.Background()) | ||
| if channel.Name == "home" { | ||
| // 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" { | ||
| // 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" { | ||
| // 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) | ||
| } | ||
| 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) { | ||
| 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() | ||
| 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() | ||
| } | ||
| }() | ||
| 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) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| ) | ||
|
|
||
| func init() { | ||
| FullMap["mastodon"] = bmastodon.New | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.