Thumbnail

rani/matterbridge.git

Clone URL: https://git.buni.party/rani/matterbridge.git

Viewing file on branch master

1package mastodon
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "fmt"
8 "regexp"
9 "strings"
10
11 "github.com/matterbridge-org/matterbridge/bridge"
12 "github.com/matterbridge-org/matterbridge/bridge/config"
13 "github.com/matterbridge-org/matterbridge/bridge/helper"
14
15 mastodon "github.com/mattn/go-mastodon"
16)
17
18var (
19 htmlReplacementTag = regexp.MustCompile("<[^>]*>")
20 channelTypeHome = "home"
21 channelTypeLocal = "local"
22 channelTypeRemote = "remote"
23 channelTypeDirect = "direct"
24)
25
26var errInvalidChannel = errors.New("invalid channel name")
27
28func InvalidChannelError(name string) error {
29 return fmt.Errorf("%w: %s", errInvalidChannel, name)
30}
31
32type Bmastodon struct {
33 *bridge.Config
34
35 c *mastodon.Client
36 account *mastodon.Account
37
38 rooms []string
39 handles []context.CancelFunc
40}
41
42func New(cfg *bridge.Config) bridge.Bridger {
43 b := &Bmastodon{Config: cfg}
44 return b
45}
46
47func (b *Bmastodon) Connect() error {
48 b.Log.Infof("Connecting %s", b.GetString("Server"))
49
50 cfg := mastodon.Config{
51 Server: b.GetString("Server"),
52 ClientID: b.GetString("ClientID"),
53 ClientSecret: b.GetString("ClientSecret"),
54 AccessToken: b.GetString("AccessToken"),
55 }
56 b.c = mastodon.NewClient(&cfg)
57
58 var err error
59
60 b.account, err =
61 b.c.GetAccountCurrentUser(context.Background())
62 if err != nil {
63 return err
64 }
65
66 return nil
67}
68
69func (b *Bmastodon) Disconnect() error {
70 for _, ctxCancel := range b.handles {
71 ctxCancel()
72 }
73
74 return nil
75}
76
77func (b *Bmastodon) JoinChannel(channel config.ChannelInfo) error {
78 var (
79 channelType string
80 ch chan mastodon.Event
81 err error
82 )
83
84 ctx, ctxCancel := context.WithCancel(context.Background())
85
86 switch channel.Name {
87 case "home":
88 channelType = channelTypeHome
89 ch, err = b.c.StreamingUser(ctx)
90 case "local":
91 channelType = channelTypeLocal
92 ch, err = b.c.StreamingPublic(ctx, true)
93 case "remote":
94 channelType = channelTypeRemote
95 ch, err = b.c.StreamingPublic(ctx, false)
96 default:
97 if !strings.HasPrefix(channel.Name, "@") {
98 ctxCancel()
99 return InvalidChannelError(channel.Name)
100 }
101
102 channelType = channelTypeDirect
103 ch, err = b.c.StreamingDirect(ctx)
104 }
105
106 if err != nil {
107 ctxCancel()
108 return err
109 }
110
111 b.rooms = append(b.rooms, channel.Name)
112 b.handles = append(b.handles, ctxCancel)
113
114 go func() {
115 b.Log.Debugf("run golang channel on streaming api call, channel name: %v", channel.Name)
116
117 for msg := range ch {
118 switch t := msg.(type) {
119 case *mastodon.UpdateEvent:
120 switch channelType {
121 case channelTypeHome, channelTypeLocal, channelTypeRemote:
122 b.handleSendRemoteStatus(t.Status, channel.Name)
123 default:
124 b.Log.Debugf("run UpdateEvent on unsupported channelType: %s", channelType)
125 }
126 case *mastodon.ConversationEvent:
127 switch channelType {
128 case channelTypeHome, channelTypeLocal, channelTypeRemote:
129 // Not a conversation
130 b.Log.Debugf("run ConversationEvent on unsupported channelType: %s", channelType)
131 default:
132 b.handleSendRemoteStatus(t.Conversation.LastStatus, channel.Name)
133 }
134 }
135 }
136 }()
137
138 return nil
139}
140
141func (b *Bmastodon) Send(msg config.Message) (string, error) {
142 ctx := context.Background()
143
144 // Standard Message Send
145 if msg.Event == "" {
146 sentMessage, err := b.handleSendingMessage(ctx, &msg)
147 if err != nil {
148 b.Log.Errorf("Could not send message to room %v from %v: %v", msg.Channel, msg.Username, err)
149
150 return "", nil
151 }
152
153 return string(sentMessage.ID), nil
154 }
155
156 // Message Deletion
157 if msg.Event == config.EventMsgDelete {
158 if msg.UserID != string(b.account.ID) {
159 b.Log.Errorf("Can not delete a status that is owned by a different account")
160 return "", nil
161 }
162
163 err := b.c.DeleteStatus(context.Background(), mastodon.ID(msg.ID))
164
165 return "", err
166 }
167
168 // Message is not a type that is currently supported
169 return "", nil
170}
171
172func (b *Bmastodon) handleSendRemoteStatus(msg *mastodon.Status, channel string) {
173 if msg.Account.ID == b.account.ID {
174 // Ignore messages that are from the bot user
175 return
176 }
177
178 remoteMessage := config.Message{
179 Text: htmlReplacementTag.ReplaceAllString(msg.Content, ""),
180 Channel: channel,
181 Username: msg.Account.DisplayName,
182 UserID: string(msg.Account.ID),
183 Account: b.Account,
184 Avatar: msg.Account.Avatar,
185 ID: string(msg.ID),
186 Extra: map[string][]any{},
187 }
188 if len(msg.MediaAttachments) > 0 {
189 remoteMessage.Extra["file"] = []any{}
190 }
191
192 for _, media := range msg.MediaAttachments {
193 b, err2 := helper.DownloadFile(media.RemoteURL)
194 if err2 != nil {
195 // TODO: log
196 continue
197 }
198
199 remoteMessage.Extra["file"] = append(remoteMessage.Extra["file"], config.FileInfo{
200 Name: media.Description,
201 Data: b,
202 Size: int64(len(*b)),
203 Avatar: false,
204 })
205 }
206
207 b.Log.Debugf("<= Message is %#v", remoteMessage)
208
209 b.Remote <- remoteMessage
210}
211
212func (b *Bmastodon) handleSendingMessage(ctx context.Context, msg *config.Message) (*mastodon.Status, error) {
213 toot := mastodon.Toot{
214 Status: msg.Text,
215 InReplyToID: "",
216 MediaIDs: []mastodon.ID{},
217 Sensitive: false,
218 SpoilerText: "",
219 Visibility: "public",
220 Language: "",
221 }
222 if strings.HasPrefix(msg.Channel, "#") {
223 toot.Status += " " + msg.Channel
224 }
225
226 if strings.HasPrefix(msg.Channel, "@") {
227 toot.Visibility = "private"
228 }
229
230 if msg.ParentID != "" {
231 toot.InReplyToID = mastodon.ID(msg.ParentID)
232 if toot.Visibility == "public" {
233 toot.Visibility = "unlisted"
234 }
235 }
236
237 for _, file := range *msg.GetFileInfos(b.Log) {
238 attachment, err := b.c.UploadMediaFromMedia(ctx, &mastodon.Media{
239 File: bytes.NewReader(*file.Data),
240 Description: file.Comment,
241 })
242 if err != nil {
243 b.Log.Error(err)
244 continue
245 }
246
247 toot.MediaIDs = append(toot.MediaIDs, attachment.ID)
248 }
249
250 return b.c.PostStatus(ctx, &toot)
251}
252