Skip to content
This repository was archived by the owner on Feb 2, 2026. It is now read-only.

Commit ede6665

Browse files
committed
feat: link attachment
1 parent 84edf85 commit ede6665

File tree

4 files changed

+80
-29
lines changed

4 files changed

+80
-29
lines changed

DOCS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,9 @@ interface Attachment {
16071607
duration?: number
16081608
stickerId?: number
16091609
previewUrl?: string
1610+
// For link attachments
1611+
description?: string // Link description/subtitle
1612+
sourceText?: string // Source domain text
16101613
// For E2EE media download (only available in E2EE messages)
16111614
mediaKey?: string // Base64 encoded encryption key
16121615
mediaSha256?: string // Base64 encoded file SHA256

DOCS_VI.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,9 @@ interface Attachment {
16071607
duration?: number
16081608
stickerId?: number
16091609
previewUrl?: string
1610+
// Dành cho link attachments
1611+
description?: string // Mô tả/subtitle của link
1612+
sourceText?: string // Tên miền nguồn
16101613
// Dành cho tải media E2EE (chỉ có trong tin nhắn E2EE)
16111614
mediaKey?: string // Khóa mã hóa dạng Base64
16121615
mediaSha256?: string // SHA256 file gốc dạng Base64

bridge-go/bridge/events.go

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bridge
33
import (
44
"context"
55
"fmt"
6+
"net/url"
67
"strconv"
78
"strings"
89
"time"
@@ -69,18 +70,20 @@ type Thread struct {
6970

7071
// Attachment represents a media attachment
7172
type Attachment struct {
72-
Type string `json:"type"` // "image", "video", "audio", "file", "sticker", "gif", "voice", "location"
73-
URL string `json:"url,omitempty"`
74-
FileName string `json:"fileName,omitempty"`
75-
MimeType string `json:"mimeType,omitempty"`
76-
FileSize int64 `json:"fileSize,omitempty"`
77-
Width int `json:"width,omitempty"`
78-
Height int `json:"height,omitempty"`
79-
Duration int `json:"duration,omitempty"` // in seconds for audio/video
80-
StickerID int64 `json:"stickerId,omitempty"`
81-
Latitude float64 `json:"latitude,omitempty"`
82-
Longitude float64 `json:"longitude,omitempty"`
83-
PreviewURL string `json:"previewUrl,omitempty"`
73+
Type string `json:"type"` // "image", "video", "audio", "file", "sticker", "gif", "voice", "location", "link"
74+
URL string `json:"url,omitempty"`
75+
FileName string `json:"fileName,omitempty"`
76+
MimeType string `json:"mimeType,omitempty"`
77+
FileSize int64 `json:"fileSize,omitempty"`
78+
Width int `json:"width,omitempty"`
79+
Height int `json:"height,omitempty"`
80+
Duration int `json:"duration,omitempty"` // in seconds for audio/video
81+
StickerID int64 `json:"stickerId,omitempty"`
82+
Latitude float64 `json:"latitude,omitempty"`
83+
Longitude float64 `json:"longitude,omitempty"`
84+
PreviewURL string `json:"previewUrl,omitempty"`
85+
Description string `json:"description,omitempty"` // For link attachments
86+
SourceText string `json:"sourceText,omitempty"` // Domain/source for link attachments
8487
// For E2EE media download
8588
MediaKey []byte `json:"mediaKey,omitempty"`
8689
MediaSHA256 []byte `json:"mediaSha256,omitempty"`
@@ -442,12 +445,25 @@ func (c *Client) convertWrappedMessage(msg *table.WrappedMessage) *Message {
442445

443446
// Handle XMA attachments (links, shares, etc.)
444447
for _, xma := range msg.XMAAttachments {
445-
if xma.PreviewUrl != "" {
448+
// Get the actual URL from CTA ActionUrl or fallback to xma.ActionUrl
449+
var linkURL string
450+
if xma.CTA != nil && xma.CTA.ActionUrl != "" {
451+
linkURL = extractURLFromLPHP(xma.CTA.ActionUrl)
452+
} else if xma.ActionUrl != "" {
453+
linkURL = extractURLFromLPHP(xma.ActionUrl)
454+
}
455+
456+
// Only add as link attachment if we have a URL or preview
457+
if linkURL != "" || xma.PreviewUrl != "" {
446458
m.Attachments = append(m.Attachments, &Attachment{
447-
Type: "link",
448-
URL: xma.ActionUrl,
449-
PreviewURL: xma.PreviewUrl,
450-
FileName: xma.TitleText,
459+
Type: "link",
460+
URL: linkURL,
461+
PreviewURL: xma.PreviewUrl,
462+
FileName: xma.TitleText,
463+
Description: xma.SubtitleText,
464+
SourceText: xma.SourceText,
465+
Width: int(xma.PreviewWidth),
466+
Height: int(xma.PreviewHeight),
451467
})
452468
}
453469
}
@@ -949,11 +965,23 @@ func (c *Client) extractE2EEMessage(e *events.FBMessage, senderID int64) *E2EEMe
949965
msg.Mentions = mentions
950966
}
951967
}
952-
if extMsg.GetCanonicalURL() != "" {
968+
if extMsg.GetCanonicalURL() != "" || extMsg.GetMatchedText() != "" {
953969
att := &Attachment{
954-
Type: "link",
955-
URL: extMsg.GetCanonicalURL(),
956-
FileName: extMsg.GetTitle(),
970+
Type: "link",
971+
URL: extMsg.GetCanonicalURL(),
972+
FileName: extMsg.GetTitle(),
973+
Description: extMsg.GetDescription(),
974+
}
975+
// If no canonical URL, use matched text as the URL
976+
if att.URL == "" && extMsg.GetMatchedText() != "" {
977+
att.URL = extMsg.GetMatchedText()
978+
}
979+
// Try to decode thumbnail for preview
980+
if thumb, err := extMsg.DecodeThumbnail(); err == nil && thumb != nil {
981+
if ancillary := thumb.GetAncillary(); ancillary != nil {
982+
att.Width = int(ancillary.GetWidth())
983+
att.Height = int(ancillary.GetHeight())
984+
}
957985
}
958986
msg.Attachments = append(msg.Attachments, att)
959987
}
@@ -1137,3 +1165,22 @@ func (c *Client) extractE2EEStickerAttachment(sticker *waConsumerApplication.Con
11371165

11381166
return att
11391167
}
1168+
1169+
// extractURLFromLPHP extracts the actual URL from Facebook's l.php redirect URL
1170+
// e.g., "https://l.facebook.com/l.php?u=https%3A%2F%2Fexample.com&h=..." -> "https://example.com"
1171+
func extractURLFromLPHP(addr string) string {
1172+
if addr == "" {
1173+
return ""
1174+
}
1175+
parsed, err := url.Parse(addr)
1176+
if err != nil {
1177+
return addr
1178+
}
1179+
// Check if this is a Facebook l.php redirect
1180+
if parsed.Path == "/l.php" || strings.HasSuffix(parsed.Path, "/l.php") {
1181+
if u := parsed.Query().Get("u"); u != "" {
1182+
return u
1183+
}
1184+
}
1185+
return addr
1186+
}

src/types.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,9 @@ export type AttachmentType = "image" | "video" | "audio" | "file" | "sticker" |
255255
export interface Attachment {
256256
/** Attachment type */
257257
type: AttachmentType;
258-
/** URL to download media (for regular messages) */
258+
/** URL to download media (for regular messages) or the link URL (for link attachments) */
259259
url?: string;
260-
/** Original filename */
260+
/** Original filename or link title */
261261
fileName?: string;
262262
/** MIME type (e.g., 'image/jpeg') */
263263
mimeType?: string;
@@ -277,12 +277,10 @@ export interface Attachment {
277277
longitude?: number;
278278
/** Preview/thumbnail URL */
279279
previewUrl?: string;
280-
281-
// === E2EE Media Download Fields ===
282-
// These fields are only available for E2EE (end-to-end encrypted) messages.
283-
// Use client.downloadE2EEMedia() to download and decrypt the media.
284-
285-
/** Base64 encoded media encryption key (E2EE only) */
280+
/** Link description/subtitle (for link attachments) */
281+
description?: string;
282+
/** Source domain text (for link attachments) */
283+
sourceText?: string;
286284
mediaKey?: string;
287285
/** Base64 encoded SHA256 hash of the decrypted file (E2EE only) */
288286
mediaSha256?: string;

0 commit comments

Comments
 (0)