Skip to content

Commit 8f00015

Browse files
authored
Merge pull request #50 from Preloading/lists
feat: Lists
2 parents ad1fe1c + ca6cf27 commit 8f00015

5 files changed

Lines changed: 470 additions & 10 deletions

File tree

bluesky/auth.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,26 @@ func RefreshToken(pds string, refreshToken string) (*AuthResponse, error) {
9292
func GetUserAuthData(handle string) (*string, *string, error) {
9393
// thank you https://discord.com/channels/1097580399187738645/1097580399187738648/1318477650485973004 (ducky.ws) on https://discord.gg/zYvmrHAr8M for explaining this to me
9494

95+
// Get the user's DID
96+
userDID, err := ResolveDIDFromHandle(handle)
97+
if err != nil {
98+
return nil, nil, err
99+
}
100+
101+
// Get the user's PDS
102+
userPDS, err := ResolvePDSFromDID(*userDID)
103+
if err != nil {
104+
return nil, nil, err
105+
}
106+
107+
// and finally, return our data
108+
return userDID, userPDS, nil
109+
}
110+
111+
func ResolveDIDFromHandle(handle string) (*string, error) {
95112
// Validate our handle
96113
if !regexp.MustCompile(`^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$`).MatchString(handle) {
97-
return nil, nil, errors.New("invalid handle")
114+
return nil, errors.New("invalid handle")
98115
}
99116
userDID := ""
100117

@@ -129,11 +146,13 @@ func GetUserAuthData(handle string) (*string, *string, error) {
129146
}
130147

131148
if userDID == "" {
132-
return nil, nil, errors.New("user does not exist")
149+
return nil, errors.New("user does not exist")
133150
}
134151

135-
// Get the user's PDS
152+
return &userDID, nil
153+
}
136154

155+
func ResolvePDSFromDID(userDID string) (*string, error) {
137156
// we must do different things depending on the DID type.
138157
didDocReqUrl := ""
139158
switch strings.Split(userDID, ":")[1] {
@@ -147,17 +166,17 @@ func GetUserAuthData(handle string) (*string, *string, error) {
147166
// get the DID doc
148167
didDocReq, err := http.Get(didDocReqUrl)
149168
if err != nil {
150-
return nil, nil, errors.New("could not find PDS")
169+
return nil, errors.New("could not find PDS")
151170
}
152171
bodyBytes, err := io.ReadAll(didDocReq.Body)
153172
if err != nil {
154-
return nil, nil, err
173+
return nil, err
155174
}
156175
var userDIDDoc DIDDoc
157176
err = json.Unmarshal(bodyBytes, &userDIDDoc)
158177
didDocReq.Body.Close()
159178
if err != nil {
160-
return nil, nil, errors.New("could not find PDS")
179+
return nil, errors.New("could not find PDS")
161180
}
162181

163182
// get the user's PDS
@@ -169,9 +188,8 @@ func GetUserAuthData(handle string) (*string, *string, error) {
169188
}
170189
}
171190
if userPDS == "" {
172-
return nil, nil, errors.New("could not find PDS")
191+
return nil, errors.New("could not find PDS")
173192
}
174193

175-
// and finally, return our data
176-
return &userDID, &userPDS, nil
194+
return &userPDS, nil
177195
}

bluesky/blueskyapi.go

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,37 @@ type Notifications struct {
329329
SeenAt time.Time `json:"seenAt"`
330330
}
331331

332+
type ListInfo struct {
333+
URI string `json:"uri"`
334+
CID string `json:"cid"`
335+
Creator User `json:"creator"`
336+
Name string `json:"name"`
337+
// ignoring purpose
338+
339+
Description string `json:"description"`
340+
DescriptionFacets []Facet `json:"descriptionFacets"`
341+
Avatar string `json:"avatar"`
342+
ListItemCount int `json:"listItemCount"`
343+
IndexedAt time.Time `json:"indexedAt"`
344+
Viewer PostViewer `json:"viewer"`
345+
}
346+
347+
type ListItem struct {
348+
URI string `json:"uri"`
349+
Subject User `json:"subject"`
350+
}
351+
352+
type Lists struct {
353+
Lists []ListInfo `json:"lists"`
354+
Cursor string `json:"cursor"`
355+
}
356+
357+
type ListDetailed struct {
358+
List ListInfo `json:"list"`
359+
Cursor string `json:"cursor"`
360+
Items []ListItem `json:"items"`
361+
}
362+
332363
var (
333364
configData *config.Config
334365
)
@@ -750,6 +781,40 @@ func GetMediaTimeline(pds string, token string, context string, actor string, li
750781
return nil, &feeds
751782
}
752783

784+
// https://docs.bsky.app/docs/api/app-bsky-graph-get-list
785+
func GetListTimeline(pds string, token string, context string, listURI string, limit int) (error, *Timeline) {
786+
apiURL := pds + "/xrpc/app.bsky.feed.getListFeed?list=" + url.QueryEscape(listURI) + "&limit=" + fmt.Sprintf("%d", limit)
787+
if context != "" {
788+
apiURL = pds + "/xrpc/app.bsky.feed.getListFeed?list=" + url.QueryEscape(listURI) + "&cursor=" + context + "&limit=" + fmt.Sprintf("%d", limit)
789+
}
790+
791+
resp, err := SendRequest(&token, http.MethodGet, apiURL, nil)
792+
if err != nil {
793+
return err, nil
794+
}
795+
defer resp.Body.Close()
796+
797+
// // Print the response body for debugging
798+
// bodyBytes, _ := io.ReadAll(resp.Body)
799+
// bodyString := string(bodyBytes)
800+
// fmt.Println("Response Body:", bodyString)
801+
802+
if resp.StatusCode != http.StatusOK {
803+
bodyBytes, _ := io.ReadAll(resp.Body)
804+
bodyString := string(bodyBytes)
805+
fmt.Println("Response Status:", resp.StatusCode)
806+
fmt.Println("Response Body:", bodyString)
807+
return errors.New("failed to fetch timeline"), nil
808+
}
809+
810+
feeds := Timeline{}
811+
if err := json.NewDecoder(resp.Body).Decode(&feeds); err != nil {
812+
return err, nil
813+
}
814+
815+
return nil, &feeds
816+
}
817+
753818
func GetPost(pds string, token string, uri string, depth int, parentHeight int) (error, *ThreadRoot) {
754819
// Example URL at://did:plc:dqibjxtqfn6hydazpetzr2w4/app.bsky.feed.post/3lchbospvbc2j
755820

@@ -1367,7 +1432,7 @@ func GetPostLikes(pds string, token string, uri string, limit int) (*Likes, erro
13671432
func GetActorLikes(pds string, token string, context string, actor string, limit int) (error, *Timeline) {
13681433
url := fmt.Sprintf(pds+"/xrpc/app.bsky.feed.getActorLikes?limit=%d&actor=%s", limit, actor)
13691434
if context != "" {
1370-
url = fmt.Sprintf(pds+"/xrpc/app.bsky.feed.getActorLikes?limit=%d&actor=%s&context=%s", limit, actor, context)
1435+
url = fmt.Sprintf(pds+"/xrpc/app.bsky.feed.getActorLikes?limit=%d&actor=%s&cursor=%s", limit, actor, context)
13711436
}
13721437

13731438
resp, err := SendRequest(&token, http.MethodGet, url, nil)
@@ -1630,6 +1695,72 @@ func GetTrends(pds string, token string) (*TrendingTopics, error) {
16301695
return &trends, nil
16311696
}
16321697

1698+
func GetUsersLists(pds string, token string, actor string, limit int, cursor string) (*Lists, error) {
1699+
url := fmt.Sprintf(pds+"/xrpc/app.bsky.graph.getLists?limit=%d&actor=%s", limit, actor)
1700+
if cursor != "" {
1701+
url = fmt.Sprintf(pds+"/xrpc/app.bsky.graph.getLists?limit=%d&actor=%s&cursor=%s", limit, actor, cursor)
1702+
}
1703+
1704+
resp, err := SendRequest(&token, http.MethodGet, url, nil)
1705+
if err != nil {
1706+
return nil, err
1707+
}
1708+
defer resp.Body.Close()
1709+
1710+
// // Print the response body
1711+
// bodyBytes, _ := io.ReadAll(resp.Body)
1712+
// bodyString := string(bodyBytes)
1713+
// fmt.Println("Response Body:", bodyString)
1714+
1715+
if resp.StatusCode != http.StatusOK {
1716+
bodyBytes, _ := io.ReadAll(resp.Body)
1717+
bodyString := string(bodyBytes)
1718+
fmt.Println("Response Status:", resp.StatusCode)
1719+
fmt.Println("Response Body:", bodyString)
1720+
return nil, errors.New("failed to fetch a user's lists")
1721+
}
1722+
1723+
lists := Lists{}
1724+
if err := json.NewDecoder(resp.Body).Decode(&lists); err != nil {
1725+
return nil, err
1726+
}
1727+
1728+
return &lists, nil
1729+
}
1730+
1731+
func GetList(pds string, token string, listURI string, limit int, cursor string) (*ListDetailed, error) {
1732+
url := fmt.Sprintf(pds+"/xrpc/app.bsky.graph.getList?limit=%d&list=%s", limit, listURI)
1733+
if cursor != "" {
1734+
url = fmt.Sprintf(pds+"/xrpc/app.bsky.graph.getList?limit=%d&list=%s&cursor=%s", limit, listURI, cursor)
1735+
}
1736+
1737+
resp, err := SendRequest(&token, http.MethodGet, url, nil)
1738+
if err != nil {
1739+
return nil, err
1740+
}
1741+
defer resp.Body.Close()
1742+
1743+
// // Print the response body
1744+
// bodyBytes, _ := io.ReadAll(resp.Body)
1745+
// bodyString := string(bodyBytes)
1746+
// fmt.Println("Response Body:", bodyString)
1747+
1748+
if resp.StatusCode != http.StatusOK {
1749+
bodyBytes, _ := io.ReadAll(resp.Body)
1750+
bodyString := string(bodyBytes)
1751+
fmt.Println("Response Status:", resp.StatusCode)
1752+
fmt.Println("Response Body:", bodyString)
1753+
return nil, errors.New("failed to fetch a user's lists")
1754+
}
1755+
1756+
list := ListDetailed{}
1757+
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
1758+
return nil, err
1759+
}
1760+
1761+
return &list, nil
1762+
}
1763+
16331764
func GetMySuggestedUsers(pds string, token string, limit int) ([]User, error) {
16341765
url := pds + "/xrpc/app.bsky.actor.getSuggestions?limit=" + fmt.Sprintf("%d", limit)
16351766

bridge/bridge.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,41 @@ type AuthToken struct {
365365
ServerURLs []string `json:"server_urls"` // URLs to access that server
366366
}
367367

368+
type TwitterLists struct {
369+
XMLName xml.Name `xml:"lists" json:"-"`
370+
Lists []TwitterList `json:"lists" xml:"list"`
371+
Cursors
372+
}
373+
374+
type TwitterListMembers struct {
375+
XMLName xml.Name `xml:"users" json:"-"`
376+
Users []*TwitterUser `json:"users" xml:"users"`
377+
Cursors
378+
}
379+
380+
type Cursors struct {
381+
PreviousCursor int64 `json:"previous_cursor" xml:"previous_cursor"`
382+
PreviousCursorStr string `json:"previous_cursor_str" xml:"previous_cursor_str"`
383+
NextCursor uint64 `json:"next_cursor" xml:"next_cursor"`
384+
NextCursorStr string `json:"next_cursor_str" xml:"next_cursor_str"`
385+
}
386+
387+
type TwitterList struct {
388+
XMLName xml.Name `xml:"list" json:"-"`
389+
Slug string `json:"slug" xml:"slug"`
390+
Name string `json:"name" xml:"name"`
391+
URI string `json:"uri" xml:"uri"`
392+
IDStr string `json:"id_str" xml:"id_str"`
393+
SubscriberCount int `json:"subscriber_count" xml:"subscriber_count"`
394+
MemberCount int `json:"member_count" xml:"member_count"`
395+
Mode string `json:"mode" xml:"mode"`
396+
ID int64 `json:"id" xml:"id"`
397+
FullName string `json:"full_name" xml:"full_name"`
398+
Description string `json:"description" xml:"description"`
399+
User TwitterUser `json:"user" xml:"user"`
400+
Following bool `json:"following" xml:"following"`
401+
}
402+
368403
func encodeToUint63(input string) *int64 {
369404
hasher := fnv.New64a() // Create a new FNV-1a 64-bit hash
370405
hasher.Write([]byte(input)) // Write the input string as bytes

0 commit comments

Comments
 (0)