66 "errors"
77 "fmt"
88 "net/http"
9+ "net/url"
910 "strings"
11+ "sync"
1012
1113 "maunium.net/go/mautrix/bridgev2"
1214 "maunium.net/go/mautrix/bridgev2/networkid"
@@ -15,13 +17,20 @@ import (
1517 "github.com/highesttt/matrix-line-messenger/pkg/line"
1618)
1719
20+ const albumPreviewWorkerLimit = 4
21+
1822type postPreviewMedia struct {
1923 Service string `json:"svc"`
2024 SID string `json:"sid"`
2125 OID string `json:"mediaOid"`
2226 MediaType string `json:"mediaType"`
2327}
2428
29+ type albumPreviewContext struct {
30+ ChatID string
31+ AlbumID string
32+ }
33+
2534// ConvertPostNotification converts a LINE note, album, or unknown post
2635// notification into a readable Matrix notice, including album preview images.
2736func (h * Handler ) ConvertPostNotification (
@@ -86,63 +95,194 @@ func (h *Handler) ConvertPostNotification(
8695 if len (previewMedias ) == 0 {
8796 return converted , nil
8897 }
98+ previewContext := parseAlbumPreviewContext (data .ContentMetadata )
99+ if previewContext .ChatID == "" {
100+ h .Log .Warn ().
101+ Str ("msg_id" , data .ID ).
102+ Msg ("LINE album preview metadata is missing chatId" )
103+ return converted , nil
104+ }
89105 if h .NewClient == nil || intent == nil || portal == nil {
90106 return nil , errors .New ("album preview conversion requires LINE and Matrix media clients" )
91107 }
92108
93109 client := h .NewClient ()
94- for index , media := range previewMedias {
95- imageData , err := h .downloadOBSResource (ctx , client , media .Service , media .SID , media .OID )
96- if newClient , ok := h .tryRecoverClient (ctx , err ); ok {
97- client = newClient
98- imageData , err = h .downloadOBSResource (ctx , client , media .Service , media .SID , media .OID )
99- }
100- if errors .Is (err , line .ErrOBSObjectNotFound ) {
101- h .Log .Warn ().
102- Str ("msg_id" , data .ID ).
103- Str ("media_oid" , media .OID ).
104- Msg ("LINE album preview image expired before it could be bridged" )
105- continue
106- } else if err != nil {
107- return nil , fmt .Errorf (
108- "%w: failed to download LINE album preview %q: %w" ,
109- bridgev2 .ErrIgnoringRemoteEvent ,
110- media .OID ,
111- err ,
112- )
110+ parts , err := h .convertAlbumPreviews (
111+ ctx ,
112+ portal ,
113+ intent ,
114+ client ,
115+ data .ID ,
116+ previewContext ,
117+ previewMedias ,
118+ relatesTo ,
119+ )
120+ if err != nil {
121+ return nil , err
122+ }
123+ for _ , part := range parts {
124+ if part != nil {
125+ converted .Parts = append (converted .Parts , part )
113126 }
127+ }
128+ return converted , nil
129+ }
114130
115- mimeType , extension := albumPreviewImageType (imageData )
116- if mimeType == "" {
117- h .Log .Warn ().
118- Str ("msg_id" , data .ID ).
119- Str ("media_oid" , media .OID ).
120- Msg ("Ignoring LINE album preview with unsupported image data" )
121- continue
122- }
123- fileName := fmt .Sprintf ("album-image-%d.%s" , index + 1 , extension )
124- mxc , file , err := intent .UploadMedia (ctx , portal .MXID , imageData , fileName , mimeType )
125- if err != nil {
126- return nil , fmt .Errorf ("failed to upload LINE album preview to Matrix: %w" , err )
127- }
131+ func (h * Handler ) convertAlbumPreviews (
132+ ctx context.Context ,
133+ portal * bridgev2.Portal ,
134+ intent bridgev2.MatrixAPI ,
135+ client * line.Client ,
136+ messageID string ,
137+ previewContext albumPreviewContext ,
138+ previewMedias []postPreviewMedia ,
139+ relatesTo * event.RelatesTo ,
140+ ) ([]* bridgev2.ConvertedMessagePart , error ) {
141+ workCtx , cancel := context .WithCancel (ctx )
142+ defer cancel ()
143+
144+ jobs := make (chan int , len (previewMedias ))
145+ for index := range previewMedias {
146+ jobs <- index
147+ }
148+ close (jobs )
128149
129- converted .Parts = append (converted .Parts , & bridgev2.ConvertedMessagePart {
130- ID : networkid .PartID (fmt .Sprintf ("album-image-%d" , index + 1 )),
131- Type : event .EventMessage ,
132- Content : & event.MessageEventContent {
133- MsgType : event .MsgImage ,
134- Body : fileName ,
135- URL : mxc ,
136- File : file ,
137- Info : & event.FileInfo {
138- MimeType : mimeType ,
139- Size : len (imageData ),
140- },
141- RelatesTo : relatesTo ,
150+ parts := make ([]* bridgev2.ConvertedMessagePart , len (previewMedias ))
151+ var workers sync.WaitGroup
152+ var errOnce sync.Once
153+ var firstErr error
154+ workerCount := min (albumPreviewWorkerLimit , len (previewMedias ))
155+ workers .Add (workerCount )
156+ for range workerCount {
157+ go func () {
158+ defer workers .Done ()
159+ for index := range jobs {
160+ if workCtx .Err () != nil {
161+ continue
162+ }
163+ part , err := h .convertAlbumPreview (
164+ workCtx ,
165+ portal ,
166+ intent ,
167+ client ,
168+ messageID ,
169+ previewContext ,
170+ previewMedias [index ],
171+ index ,
172+ relatesTo ,
173+ )
174+ if err != nil {
175+ errOnce .Do (func () {
176+ firstErr = err
177+ cancel ()
178+ })
179+ continue
180+ }
181+ parts [index ] = part
182+ }
183+ }()
184+ }
185+ workers .Wait ()
186+
187+ if firstErr != nil {
188+ return nil , firstErr
189+ }
190+ if err := ctx .Err (); err != nil {
191+ return nil , err
192+ }
193+ return parts , nil
194+ }
195+
196+ func (h * Handler ) convertAlbumPreview (
197+ ctx context.Context ,
198+ portal * bridgev2.Portal ,
199+ intent bridgev2.MatrixAPI ,
200+ client * line.Client ,
201+ messageID string ,
202+ previewContext albumPreviewContext ,
203+ media postPreviewMedia ,
204+ index int ,
205+ relatesTo * event.RelatesTo ,
206+ ) (* bridgev2.ConvertedMessagePart , error ) {
207+ imageData , err := h .downloadAlbumPreview (
208+ ctx ,
209+ client ,
210+ media .OID ,
211+ previewContext .ChatID ,
212+ previewContext .AlbumID ,
213+ )
214+ if newClient , ok := h .tryRecoverClient (ctx , err ); ok {
215+ imageData , err = h .downloadAlbumPreview (
216+ ctx ,
217+ newClient ,
218+ media .OID ,
219+ previewContext .ChatID ,
220+ previewContext .AlbumID ,
221+ )
222+ }
223+ if errors .Is (err , line .ErrOBSObjectNotFound ) {
224+ h .Log .Warn ().
225+ Str ("msg_id" , messageID ).
226+ Str ("media_oid" , media .OID ).
227+ Msg ("LINE album preview image expired before it could be bridged" )
228+ return nil , nil
229+ } else if err != nil {
230+ return nil , fmt .Errorf (
231+ "%w: failed to download LINE album preview %q: %w" ,
232+ bridgev2 .ErrIgnoringRemoteEvent ,
233+ media .OID ,
234+ err ,
235+ )
236+ }
237+
238+ mimeType , extension := albumPreviewImageType (imageData )
239+ if mimeType == "" {
240+ h .Log .Warn ().
241+ Str ("msg_id" , messageID ).
242+ Str ("media_oid" , media .OID ).
243+ Msg ("Ignoring LINE album preview with unsupported image data" )
244+ return nil , nil
245+ }
246+ fileName := fmt .Sprintf ("album-image-%d.%s" , index + 1 , extension )
247+ mxc , file , err := intent .UploadMedia (ctx , portal .MXID , imageData , fileName , mimeType )
248+ if err != nil {
249+ return nil , fmt .Errorf ("failed to upload LINE album preview to Matrix: %w" , err )
250+ }
251+
252+ return & bridgev2.ConvertedMessagePart {
253+ ID : networkid .PartID (fmt .Sprintf ("album-image-%d" , index + 1 )),
254+ Type : event .EventMessage ,
255+ Content : & event.MessageEventContent {
256+ MsgType : event .MsgImage ,
257+ Body : fileName ,
258+ URL : mxc ,
259+ File : file ,
260+ Info : & event.FileInfo {
261+ MimeType : mimeType ,
262+ Size : len (imageData ),
142263 },
143- })
264+ RelatesTo : relatesTo ,
265+ },
266+ }, nil
267+ }
268+
269+ func parseAlbumPreviewContext (metadata map [string ]string ) albumPreviewContext {
270+ previewContext := albumPreviewContext {
271+ ChatID : strings .TrimSpace (metadata ["chatId" ]),
144272 }
145- return converted , nil
273+ postEndURL := strings .TrimSpace (metadata ["postEndUrl" ])
274+ if postEndURL == "" {
275+ return previewContext
276+ }
277+ parsedURL , err := url .Parse (postEndURL )
278+ if err != nil {
279+ return previewContext
280+ }
281+ previewContext .AlbumID = strings .TrimSpace (parsedURL .Query ().Get ("albumIdV2" ))
282+ if previewContext .AlbumID == "" {
283+ previewContext .AlbumID = strings .TrimSpace (parsedURL .Query ().Get ("albumId" ))
284+ }
285+ return previewContext
146286}
147287
148288func parseAlbumPreviewMedias (metadata map [string ]string ) ([]postPreviewMedia , error ) {
0 commit comments