Skip to content

Commit ce1c2d2

Browse files
committed
feat: followers timeline
1 parent 5ac1e10 commit ce1c2d2

3 files changed

Lines changed: 156 additions & 3 deletions

File tree

bridge/tidconversions.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package bridge
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
var tidChars = "234567abcdefghijklmnopqrstuvwxyz"
9+
10+
// numToTid converts a 64-bit number into a Bluesky TID.
11+
func NumToTid(number uint64) (string, error) {
12+
// Ensure the first bit is 0
13+
if (number & 0x8000000000000000) != 0 {
14+
return "", fmt.Errorf("first bit must be 0")
15+
}
16+
17+
// Convert to base32 manually
18+
// the other one kept losing precision
19+
var result strings.Builder
20+
for i := 0; i < 13; i++ {
21+
index := number & 0x1F // Take 5 bits
22+
result.WriteByte(tidChars[index])
23+
number >>= 5
24+
}
25+
26+
// Reverse the string since we built it backwards
27+
runes := []rune(result.String())
28+
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
29+
runes[i], runes[j] = runes[j], runes[i]
30+
}
31+
32+
return string(runes), nil
33+
}
34+
35+
// tidToNum converts a Bluesky TID into a 64-bit number.
36+
func TidToNum(tid string) (uint64, error) {
37+
if len(tid) != 13 {
38+
return 0, fmt.Errorf("TID must be 13 characters")
39+
}
40+
41+
var num uint64
42+
for _, c := range tid {
43+
index := strings.IndexRune(tidChars, c)
44+
if index == -1 {
45+
return 0, fmt.Errorf("invalid character in TID: %c", c)
46+
}
47+
num = (num << 5) | uint64(index)
48+
}
49+
50+
// Verify the first bit is 0
51+
if (num & 0x8000000000000000) != 0 {
52+
return 0, fmt.Errorf("first bit must be 0")
53+
}
54+
55+
return num, nil
56+
}

twitterv1/twitterv1.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ func InitServer(config *config.Config) {
8383
app.Post("/1/friendships/create.:filetype", FollowUser)
8484
app.Post("/1/friendships/destroy.:filetype", UnfollowUserForm)
8585
app.Post("/1/friendships/destroy/:id.:filetype", UnfollowUserParams)
86-
87-
app.Get("/1/statuses/followers.:filetype", GetFollowers)
86+
app.Get("/1/followers.:filetype", GetFollowers)
87+
app.Get("/1/statuses/followers.:filetype", GetStatusesFollowers)
8888
app.Get("/1/statuses/friends.:filetype", GetFollows)
8989

9090
app.Get("/1/users/recommendations.:filetype", GetSuggestedUsers)

twitterv1/user.go

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ func UnfollowUserParams(c *fiber.Ctx) error {
422422

423423
// https://web.archive.org/web/20101115102530/http://apiwiki.twitter.com/w/page/22554748/Twitter-REST-API-Method%3a-statuses%C2%A0followers
424424
// At the moment we are not doing pagination, so this will only return the first ~50 followers.
425-
func GetFollowers(c *fiber.Ctx) error {
425+
func GetStatusesFollowers(c *fiber.Ctx) error {
426426
// auth
427427
_, pds, _, oauthToken, err := GetAuthFromReq(c)
428428
if err != nil {
@@ -483,6 +483,103 @@ func GetFollowers(c *fiber.Ctx) error {
483483
})
484484
}
485485

486+
func GetFollowers(c *fiber.Ctx) error {
487+
// auth
488+
_, pds, _, oauthToken, err := GetAuthFromReq(c)
489+
if err != nil {
490+
return c.Status(fiber.StatusUnauthorized).SendString("OAuth token not found in Authorization header")
491+
}
492+
493+
// lets go get our user data
494+
fmt.Println(c.Body())
495+
fmt.Println(c.OriginalURL())
496+
actor := c.FormValue("user_id")
497+
if actor == "" {
498+
actor = c.FormValue("screen_name")
499+
if actor == "" {
500+
c.Status(fiber.StatusBadRequest).SendString("No user provided")
501+
}
502+
} else {
503+
id, err := strconv.ParseInt(actor, 10, 64)
504+
if err != nil {
505+
return c.Status(fiber.StatusBadRequest).SendString("Invalid user_id provided")
506+
}
507+
actorPtr, err := bridge.TwitterIDToBlueSky(&id)
508+
if err != nil {
509+
return c.Status(fiber.StatusBadRequest).SendString("Failed to convert user_id to screen_name")
510+
}
511+
if actorPtr == nil {
512+
return c.Status(fiber.StatusBadRequest).SendString("Failed to convert user_id to screen_name")
513+
}
514+
actor = *actorPtr
515+
}
516+
517+
cursor := ""
518+
var cursorInt int64
519+
520+
cursorStr := c.FormValue("cursor")
521+
if cursorStr != "" {
522+
cursorInt, err = strconv.ParseInt(cursorStr, 10, 64)
523+
if err != nil || cursorInt != -1 {
524+
cursor, err = bridge.NumToTid(uint64(cursorInt))
525+
if err != nil {
526+
fmt.Println("Error when converting Followers Cursor:", err)
527+
cursor = ""
528+
}
529+
} else {
530+
cursor = ""
531+
}
532+
} else {
533+
cursor = ""
534+
}
535+
536+
// fetch followers
537+
followers, err := blueskyapi.GetFollowers(*pds, *oauthToken, cursor, actor)
538+
if err != nil {
539+
fmt.Println("Error:", err)
540+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to fetch followers")
541+
}
542+
543+
// convert users into twitter format
544+
// This right now doesn't act on pagination, i'll figure that out later
545+
var actorsToLookUp []string
546+
for _, user := range followers.Followers {
547+
actorsToLookUp = append(actorsToLookUp, user.DID)
548+
}
549+
550+
twitterUsers, err := blueskyapi.GetUsersInfo(*pds, *oauthToken, actorsToLookUp, false)
551+
if err != nil {
552+
fmt.Println("Error:", err)
553+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to fetch user info")
554+
}
555+
556+
// Convert []*bridge.TwitterUser to []bridge.TwitterUser
557+
var twitterUsersConverted []bridge.TwitterUser
558+
for _, user := range twitterUsers {
559+
twitterUsersConverted = append(twitterUsersConverted, *user)
560+
}
561+
562+
next_cursor, err := bridge.TidToNum(followers.Cursor)
563+
if err != nil {
564+
fmt.Println("Error when converting Followers Cursor:", err)
565+
next_cursor = 0
566+
}
567+
568+
return EncodeAndSend(c, struct {
569+
Users []bridge.TwitterUser `json:"users" xml:"users"`
570+
NextCursor uint64 `json:"next_cursor" xml:"next_cursor"`
571+
PreviousCursor uint64 `json:"previous_cursor" xml:"previous_cursor"`
572+
NextCursorStr string `json:"next_cursor_str" xml:"-"`
573+
PreviousCursorStr string `json:"previous_cursor_str" xml:"-"`
574+
}{
575+
Users: twitterUsersConverted,
576+
NextCursor: next_cursor,
577+
PreviousCursor: 0, // Unimplemented. This could probably be figured out if i could figure out what the TID corrisponds to, if it corrisponds to anything at all.
578+
NextCursorStr: strconv.FormatUint(next_cursor, 10),
579+
PreviousCursorStr: "0",
580+
})
581+
}
582+
486583
// https://web.archive.org/web/20120407214017/https://dev.twitter.com/docs/api/1/get/statuses/friends
487584
func GetFollows(c *fiber.Ctx) error {
488585
// auth

0 commit comments

Comments
 (0)