Skip to content

Commit b4e0b56

Browse files
committed
feat: implemented replies
1 parent e2e3c76 commit b4e0b56

3 files changed

Lines changed: 98 additions & 40 deletions

File tree

bluesky/blueskyapi.go

Lines changed: 86 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ type User struct {
4747
}
4848

4949
type PostRecord struct {
50-
Type string `json:"$type"`
51-
CreatedAt time.Time `json:"createdAt"`
52-
Embed Embed `json:"embed"`
53-
Facets []Facet `json:"facets"`
54-
Langs []string `json:"langs"`
55-
Text string `json:"text"`
50+
Type string `json:"$type"`
51+
CreatedAt time.Time `json:"createdAt"`
52+
Embed Embed `json:"embed"`
53+
Facets []Facet `json:"facets"`
54+
Langs []string `json:"langs"`
55+
Text string `json:"text"`
56+
Reply *ReplySubject `json:"reply,omitempty"`
5657
}
5758

5859
// Specifically for reposts
@@ -176,16 +177,22 @@ type PostInteractionRecord struct {
176177
}
177178

178179
type CreatePostRecord struct {
179-
Type string `json:"$type"`
180-
Text string `json:"text"`
181-
CreatedAt time.Time `json:"createdAt"`
180+
Type string `json:"$type"`
181+
Text string `json:"text"`
182+
CreatedAt time.Time `json:"createdAt"`
183+
Reply *ReplySubject `json:"reply,omitempty"`
182184
}
183185

184186
type Subject struct {
185187
URI string `json:"uri"`
186188
CID string `json:"cid"`
187189
}
188190

191+
type ReplySubject struct {
192+
Root Subject `json:"root"`
193+
Parent Subject `json:"parent"`
194+
}
195+
189196
type Commit struct {
190197
CID string `json:"cid"`
191198
Rev string `json:"rev"`
@@ -218,6 +225,16 @@ type UserSearchResult struct {
218225
Actors []User `json:"actors"`
219226
}
220227

228+
type RecordResponse struct {
229+
URI string `json:"uri"`
230+
CID string `json:"cid"`
231+
Value RecordValue `json:"value"`
232+
}
233+
234+
type RecordValue struct {
235+
Reply *ReplySubject `json:"reply,omitempty"`
236+
}
237+
221238
func SendRequest(token *string, method string, url string, body io.Reader) (*http.Response, error) {
222239
client := &http.Client{}
223240
req, err := http.NewRequest(method, url, body)
@@ -459,32 +476,34 @@ func GetPost(token string, uri string, depth int, parentHeight int) (error, *Thr
459476

460477
// This handles both normal & replys
461478
func UpdateStatus(token string, my_did string, status string, in_reply_to *string) (*ThreadRoot, error) {
462-
url := "https://public.bsky.social/xrpc/com.atproto.repo.createRecord"
479+
url := "https://bsky.social/xrpc/com.atproto.repo.createRecord"
463480

464-
reqBody := []byte{}
481+
var replySubject *ReplySubject
465482
var err error
466483

467-
if in_reply_to == nil || *in_reply_to == "" {
468-
469-
payload := CreateRecordPayload{
470-
Collection: "app.bsky.feed.post",
471-
Repo: my_did,
472-
Record: CreatePostRecord{
473-
Type: "app.bsky.feed.post",
474-
Text: status,
475-
CreatedAt: time.Now().UTC(),
476-
},
477-
}
478-
479-
reqBody, err = json.Marshal(payload)
484+
// Replying
485+
if in_reply_to != nil && *in_reply_to != "" {
486+
replySubject, err = GetReplyRefs(token, *in_reply_to)
480487
if err != nil {
481-
return nil, errors.New("failed to marshal payload")
488+
return nil, errors.New("failed to fetch reply refs")
482489
}
490+
}
483491

484-
} else {
485-
return nil, errors.New("in_reply_to not implemented")
492+
payload := CreateRecordPayload{
493+
Collection: "app.bsky.feed.post",
494+
Repo: my_did,
495+
Record: CreatePostRecord{
496+
Type: "app.bsky.feed.post",
497+
Text: status,
498+
CreatedAt: time.Now().UTC(),
499+
Reply: replySubject,
500+
},
486501
}
487502

503+
reqBody, err := json.Marshal(payload)
504+
if err != nil {
505+
return nil, errors.New("failed to marshal payload")
506+
}
488507
resp, err := SendRequest(&token, http.MethodPost, url, bytes.NewReader(reqBody))
489508
if err != nil {
490509
return nil, errors.New("failed to post")
@@ -505,6 +524,8 @@ func UpdateStatus(token string, my_did string, status string, in_reply_to *strin
505524
return nil, err
506525
}
507526

527+
time.Sleep(100 * time.Millisecond) // Bluesky doesn't update instantly, so we wait a bit before fetching the post
528+
508529
err, thread := GetPost(token, postData.URI, 0, 1)
509530
if err != nil {
510531
return nil, errors.New("failed to fetch made post")
@@ -742,3 +763,41 @@ func UserSearch(token string, query string) ([]User, error) {
742763
}
743764
return users.Actors, nil
744765
}
766+
767+
// thank you https://docs.bsky.app/blog/create-post#replies
768+
func GetReplyRefs(token string, parentURI string) (*ReplySubject, error) {
769+
// Get the parent post
770+
err, parentThread := GetPost(token, parentURI, 0, 1)
771+
if err != nil {
772+
return nil, fmt.Errorf("failed to fetch parent post: %w", err)
773+
}
774+
775+
// If parent has a reply reference, fetch the root post
776+
var rootURI string
777+
var rootCID string
778+
779+
if parentThread.Thread.Post.Record.Reply != nil {
780+
// Get the root post
781+
rootURI = parentThread.Thread.Post.Record.Reply.Root.URI
782+
err, rootThread := GetPost(token, rootURI, 0, 1)
783+
if err != nil {
784+
return nil, fmt.Errorf("failed to fetch root post: %w", err)
785+
}
786+
rootCID = rootThread.Thread.Post.CID
787+
} else {
788+
// If parent has no reply reference, it's a top-level post, so it's also the root
789+
rootURI = parentThread.Thread.Post.URI
790+
rootCID = parentThread.Thread.Post.CID
791+
}
792+
793+
return &ReplySubject{
794+
Root: Subject{
795+
URI: rootURI,
796+
CID: rootCID,
797+
},
798+
Parent: Subject{
799+
URI: parentThread.Thread.Post.URI,
800+
CID: parentThread.Thread.Post.CID,
801+
},
802+
}, nil
803+
}

twitterv1/interaction.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@ func status_update(c *fiber.Ctx) error {
2020

2121
status := c.FormValue("status")
2222
trim_user := c.FormValue("trim_user")
23-
in_reply_to_status_id := c.FormValue("in_reply_to_status_id")
23+
encoded_in_reply_to_status_id_str := c.FormValue("in_reply_to_status_id")
24+
encoded_in_reply_to_status_id_int := new(big.Int)
25+
encoded_in_reply_to_status_id_int, ok := encoded_in_reply_to_status_id_int.SetString(encoded_in_reply_to_status_id_str, 10)
26+
if !ok {
27+
return c.Status(fiber.StatusBadRequest).SendString("Invalid in_reply_to_status_id format")
28+
}
29+
in_reply_to_status_id, _, _, err := bridge.TwitterMsgIdToBluesky(encoded_in_reply_to_status_id_int)
30+
if err != nil {
31+
return c.Status(fiber.StatusBadRequest).SendString("Invalid in_reply_to_status_id format")
32+
}
2433

2534
fmt.Println("Status:", status)
2635
fmt.Println("TrimUser:", trim_user)
27-
fmt.Println("InReplyToStatusID:", in_reply_to_status_id)
36+
fmt.Println("InReplyToStatusID:", encoded_in_reply_to_status_id_int)
2837

29-
thread, err := blueskyapi.UpdateStatus(*oauthToken, *my_did, status, &in_reply_to_status_id)
38+
thread, err := blueskyapi.UpdateStatus(*oauthToken, *my_did, status, in_reply_to_status_id)
3039

3140
if err != nil {
3241
fmt.Println("Error:", err)
@@ -60,10 +69,6 @@ func retweet(c *fiber.Ctx) error {
6069
}
6170
postId = *postIdPtr
6271

63-
if err != nil {
64-
return c.Status(fiber.StatusBadRequest).SendString("Invalid ID format")
65-
}
66-
6772
err, originalPost, retweetPostURI := blueskyapi.ReTweet(*oauthToken, postId, *user_did)
6873

6974
if err != nil {
@@ -102,9 +107,6 @@ func favourite(c *fiber.Ctx) error {
102107
return c.Status(fiber.StatusBadRequest).SendString("Invalid ID format")
103108
}
104109
postId = *postIdPtr
105-
if err != nil {
106-
return c.Status(fiber.StatusBadRequest).SendString("Invalid ID format")
107-
}
108110

109111
fmt.Println("Post ID:", postId)
110112

twitterv1/post_viewing.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,6 @@ func TweetInfo(c *fiber.Ctx) error {
391391
return c.Status(fiber.StatusBadRequest).SendString("Invalid ID format")
392392
}
393393
id := *idPtr
394-
if err != nil {
395-
return c.Status(fiber.StatusBadRequest).SendString("Invalid ID format")
396-
}
397394

398395
err, thread := blueskyapi.GetPost(*oauthToken, id, 1, 0)
399396

0 commit comments

Comments
 (0)