A message containing a YouTube link now carries a preview card. Discord and X build their own card from the URL in the text, so the work is on the bluesky side: the new preview.go asks YouTube's oEmbed endpoint for the title, channel and thumbnail and posts them as an app.bsky.embed.external. Links are found by pulling candidates out of the text and parsing them with net/url rather than by matching a URL shaped regexp, so a host like youtube.com.example.invalid is not mistaken for the real thing. Bluesky allows one embed per post, so a message with both attachments and a link keeps the attachments. A failed oEmbed lookup only costs the card, not the post. Downloading the thumbnail wants the attachment download path, so it is split into fetch and downloadImage, and both now go through a client with a timeout: the event loop is single threaded and a hung request would stall every later message. The Dockerfile built main.go by name, which stops working now that package main spans two files; it builds the package instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
48 lines
1.2 KiB
Go
48 lines
1.2 KiB
Go
package output
|
|
|
|
type Image struct {
|
|
Data []byte
|
|
ContentType string
|
|
Filename string
|
|
}
|
|
|
|
// Preview is a link preview card ("プレビュー窓") to attach to a post.
|
|
type Preview struct {
|
|
URL string
|
|
Title string
|
|
Description string
|
|
Thumb *Image
|
|
}
|
|
|
|
// Ref identifies a post an output has already published so it can later be
|
|
// replied to or deleted. The fields are output specific; only the ones the
|
|
// publishing output filled in are meaningful to it.
|
|
type Ref struct {
|
|
ID string `json:"id,omitempty"` // twitter: tweet ID
|
|
URI string `json:"uri,omitempty"` // bluesky: at:// URI of the record
|
|
CID string `json:"cid,omitempty"` // bluesky: record CID
|
|
RootURI string `json:"rooturi,omitempty"`
|
|
RootCID string `json:"rootcid,omitempty"`
|
|
}
|
|
|
|
// IsZero reports whether the ref points at nothing.
|
|
func (r Ref) IsZero() bool {
|
|
return r == Ref{}
|
|
}
|
|
|
|
// Post is a single message to distribute.
|
|
type Post struct {
|
|
Text string
|
|
Images []Image
|
|
Preview *Preview
|
|
// ReplyTo is the Ref this same output returned for the post being
|
|
// replied to, or nil for a top level post.
|
|
ReplyTo *Ref
|
|
}
|
|
|
|
type OutputInterface interface {
|
|
Write(Post) (Ref, error)
|
|
Delete(Ref) error
|
|
GetName() string
|
|
}
|