add support for links as posts

This commit is contained in:
Astra 2025-07-01 07:08:46 +01:00
parent 2bb3946237
commit bfa829d8c7
4 changed files with 240 additions and 33 deletions

View file

@ -263,3 +263,17 @@ type Records struct {
Cid string `json:"cid"`
Value Value `json:"value"`
}
func (bluesky *Bluesky) FetchPost(did string, rkey string) FetchedPost {
resp := &struct {
Posts []FetchedPost `json:"posts"`
}{}
params := struct {
URIs string `url:"uris"`
}{
URIs: fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey),
}
bluesky.sling.New().Base("https://public.api.bsky.app").
Get("/xrpc/app.bsky.feed.getPosts").QueryStruct(&params).Receive(resp, resp)
return resp.Posts[0]
}

View file

@ -28,7 +28,7 @@ func NewBSky() *BSky {
}
}
func (b *BSky) getPDS() error {
func (b *BSky) ResolveHandle(handle string) (string, error) {
httpClient := &http.Client{Timeout: 3 * time.Second}
resp := new(BSkySessionResponse)
errResp := &struct {
@ -38,23 +38,29 @@ func (b *BSky) getPDS() error {
params := struct {
Handle string `url:"handle"`
}{
Handle: b.Bluesky.Cfg.Handle,
Handle: handle,
}
sling.New().Base("https://public.api.bsky.app/").Client(httpClient).
Get("/xrpc/com.atproto.identity.resolveHandle").QueryStruct(params).
Receive(resp, errResp)
if errResp.Error != "" {
return errors.New(errResp.Message)
return "", errors.New(errResp.Message)
}
return resp.DID, nil
}
func (b *BSky) getPDS() error {
did, _ := b.ResolveHandle(b.Bluesky.Cfg.Handle)
var didURL url.URL
if strings.HasPrefix(resp.DID, "did:web:") {
didURL.Host = "https://" + resp.DID[8:]
if strings.HasPrefix(did, "did:web:") {
didURL.Host = "https://" + did[8:]
didURL.Path = "/.well-known/did.json"
} else if strings.HasPrefix(resp.DID, "did:plc:") {
} else if strings.HasPrefix(did, "did:plc:") {
didURL.Host = "https://plc.directory"
didURL.Path = "/" + resp.DID
didURL.Path = "/" + did
} else {
return errors.New("DID is not supported")
}
@ -104,7 +110,7 @@ func (b *BSky) Auth(authData []string) error {
b.Bluesky.Cfg.AppPassword = authData[1]
err = b.Bluesky.CreateSession(b.Bluesky.Cfg)
if err != nil {
return errors.New(fmt.Sprintf("unable to auth: %s", err))
return fmt.Errorf("unable to auth: %s", err)
}
b.Bluesky.Cfg.AppPassword = "" // we don't need to save this
PersistAuthSession(b.Bluesky.Cfg)

View file

@ -123,6 +123,160 @@ type ParsedEmbeds struct {
Height int64
}
type FetchedPost struct {
URI string `json:"uri"`
Cid string `json:"cid"`
Author struct {
Did string `json:"did"`
Handle string `json:"handle"`
DisplayName string `json:"displayName"`
Avatar string `json:"avatar"`
Associated struct {
Chat struct {
AllowIncoming string `json:"allowIncoming"`
} `json:"chat"`
} `json:"associated"`
Labels []interface{} `json:"labels"`
CreatedAt time.Time `json:"createdAt"`
} `json:"author"`
Record *Post `json:"record"`
// Record struct {
// Type string `json:"$type"`
// CreatedAt time.Time `json:"createdAt"`
// Embed struct {
// Type string `json:"$type"`
// Media struct {
// Type string `json:"$type"`
// Images []struct {
// Alt string `json:"alt"`
// AspectRatio struct {
// Height int `json:"height"`
// Width int `json:"width"`
// } `json:"aspectRatio"`
// Image struct {
// Type string `json:"$type"`
// Ref struct {
// Link string `json:"$link"`
// } `json:"ref"`
// MimeType string `json:"mimeType"`
// Size int `json:"size"`
// } `json:"image"`
// } `json:"images"`
// } `json:"media"`
// Record struct {
// Type string `json:"$type"`
// Record struct {
// Cid string `json:"cid"`
// URI string `json:"uri"`
// } `json:"record"`
// } `json:"record"`
// } `json:"embed"`
// Labels struct {
// Type string `json:"$type"`
// Values []struct {
// Val string `json:"val"`
// } `json:"values"`
// } `json:"labels"`
// Langs []string `json:"langs"`
// Text string `json:"text"`
// } `json:"record"`
Embed struct {
Type string `json:"$type"`
Media struct {
Type string `json:"$type"`
Images []struct {
Thumb string `json:"thumb"`
Fullsize string `json:"fullsize"`
Alt string `json:"alt"`
AspectRatio struct {
Height int `json:"height"`
Width int `json:"width"`
} `json:"aspectRatio"`
} `json:"images"`
} `json:"media"`
Record struct {
Record struct {
Type string `json:"$type"`
URI string `json:"uri"`
Cid string `json:"cid"`
Author struct {
Did string `json:"did"`
Handle string `json:"handle"`
DisplayName string `json:"displayName"`
Avatar string `json:"avatar"`
Associated struct {
Chat struct {
AllowIncoming string `json:"allowIncoming"`
} `json:"chat"`
} `json:"associated"`
Labels []interface{} `json:"labels"`
CreatedAt time.Time `json:"createdAt"`
} `json:"author"`
Value struct {
Type string `json:"$type"`
CreatedAt time.Time `json:"createdAt"`
Embed struct {
Type string `json:"$type"`
AspectRatio struct {
Height int `json:"height"`
Width int `json:"width"`
} `json:"aspectRatio"`
Video struct {
Type string `json:"$type"`
Ref struct {
Link string `json:"$link"`
} `json:"ref"`
MimeType string `json:"mimeType"`
Size int `json:"size"`
} `json:"video"`
} `json:"embed"`
Facets []struct {
Type string `json:"$type"`
Features []struct {
Type string `json:"$type"`
Did string `json:"did"`
} `json:"features"`
Index struct {
ByteEnd int `json:"byteEnd"`
ByteStart int `json:"byteStart"`
} `json:"index"`
} `json:"facets"`
Langs []string `json:"langs"`
Text string `json:"text"`
} `json:"value"`
Labels []interface{} `json:"labels"`
LikeCount int `json:"likeCount"`
ReplyCount int `json:"replyCount"`
RepostCount int `json:"repostCount"`
QuoteCount int `json:"quoteCount"`
IndexedAt time.Time `json:"indexedAt"`
Embeds []struct {
Type string `json:"$type"`
Cid string `json:"cid"`
Playlist string `json:"playlist"`
Thumbnail string `json:"thumbnail"`
AspectRatio struct {
Height int `json:"height"`
Width int `json:"width"`
} `json:"aspectRatio"`
} `json:"embeds"`
} `json:"record"`
} `json:"record"`
} `json:"embed,omitempty"`
ReplyCount int `json:"replyCount"`
RepostCount int `json:"repostCount"`
LikeCount int `json:"likeCount"`
QuoteCount int `json:"quoteCount"`
IndexedAt time.Time `json:"indexedAt"`
Labels []struct {
Src string `json:"src"`
URI string `json:"uri"`
Cid string `json:"cid"`
Val string `json:"val"`
Cts time.Time `json:"cts"`
} `json:"labels"`
}
func (b *BSky) ParsePost(post []byte) (*Post, error) {
var p = &Post{}
err := json.Unmarshal(post, &p)
@ -160,12 +314,10 @@ func (post *Post) ProcessFacets(aliases []Records) string {
switch feature.Type {
case "app.bsky.richtext.facet#mention":
link := fmt.Sprintf(`<a href="https://bsky.app/profile/%s">%s</a>`, feature.Did, post.Text[start:end])
if aliases != nil {
for _, alias := range aliases {
if alias.Value.Subject == feature.Did {
link = fmt.Sprintf(`<a href="%s">%s</a>`,
strings.SplitN(alias.Value.Target, "#", 2)[0], strings.SplitN(alias.Value.Target, "#", 2)[1])
}
for _, alias := range aliases {
if alias.Value.Subject == feature.Did {
link = fmt.Sprintf(`<a href="%s">%s</a>`,
strings.SplitN(alias.Value.Target, "#", 2)[0], strings.SplitN(alias.Value.Target, "#", 2)[1])
}
}
result.WriteString(link)