this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Factor out ListRepos, add Collection handling, and reuse with relay Remote

+100 -80
+1 -76
cmd/butterfly/remote/pds.go
··· 3 3 4 4 import ( 5 5 "context" 6 - "encoding/json" 7 6 "fmt" 8 - "io" 9 7 "net/http" 10 - "net/url" 11 - "strconv" 12 8 "time" 13 9 14 10 "github.com/bluesky-social/indigo/atproto/data" ··· 24 20 25 21 // ListRepos returns the DIDs hosted by the PDS 26 22 func (p *PdsRemote) ListRepos(ctx context.Context, params ListReposParams) (*ListReposResult, error) { 27 - // Build URL with query parameters 28 - u, err := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.listRepos", p.Service)) 29 - if err != nil { 30 - return nil, fmt.Errorf("failed to parse PDS URL: %w", err) 31 - } 32 - 33 - q := u.Query() 34 - if params.Cursor != "" { 35 - q.Set("cursor", params.Cursor) 36 - } 37 - if params.Limit > 0 { 38 - q.Set("limit", strconv.Itoa(params.Limit)) 39 - } 40 - u.RawQuery = q.Encode() 41 - 42 - // Create request 43 - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) 44 - if err != nil { 45 - return nil, fmt.Errorf("failed to create request: %w", err) 46 - } 47 - 48 - req.Header.Set("Accept", "application/json") 49 - req.Header.Set("User-Agent", "butterfly/0.0.1") 50 - 51 - // Make request 52 - client := &http.Client{ 53 - Timeout: 30 * time.Second, 54 - } 55 - resp, err := client.Do(req) 56 - if err != nil { 57 - return nil, fmt.Errorf("failed to list repos: %w", err) 58 - } 59 - defer resp.Body.Close() 60 - 61 - if resp.StatusCode != http.StatusOK { 62 - body, _ := io.ReadAll(resp.Body) 63 - return nil, fmt.Errorf("failed to list repos: %s - %s", resp.Status, string(body)) 64 - } 65 - 66 - // Parse response 67 - var apiResp struct { 68 - Cursor *string `json:"cursor,omitempty"` 69 - Repos []struct { 70 - Did string `json:"did"` 71 - Head string `json:"head"` 72 - Rev string `json:"rev"` 73 - Active *bool `json:"active,omitempty"` 74 - Status *string `json:"status,omitempty"` 75 - } `json:"repos"` 76 - } 77 - 78 - if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { 79 - return nil, fmt.Errorf("failed to decode response: %w", err) 80 - } 81 - 82 - // Extract DIDs from response 83 - dids := make([]string, 0, len(apiResp.Repos)) 84 - for _, repo := range apiResp.Repos { 85 - // Only include active repos by default 86 - if repo.Active == nil || *repo.Active { 87 - dids = append(dids, repo.Did) 88 - } 89 - } 90 - 91 - result := &ListReposResult{ 92 - Dids: dids, 93 - } 94 - if apiResp.Cursor != nil { 95 - result.Cursor = *apiResp.Cursor 96 - } 97 - 98 - return result, nil 23 + return XrpcListRepos(ctx, p.Service, params) 99 24 } 100 25 101 26 // FetchRepo streams the contents of a repository from the PDS
+4 -4
cmd/butterfly/remote/relay.go
··· 12 12 Service string 13 13 } 14 14 15 - func (self RelayRemote) ListRepos(ctx context.Context, params ListReposParams) (*ListReposResult, error) { 16 - return nil, fmt.Errorf("Not yet implemented") 15 + func (r *RelayRemote) ListRepos(ctx context.Context, params ListReposParams) (*ListReposResult, error) { 16 + return XrpcListRepos(ctx, r.Service, params) 17 17 } 18 18 19 - func (self RelayRemote) FetchRepo(ctx context.Context, params FetchRepoParams) (*RemoteStream, error) { 19 + func (r *RelayRemote) FetchRepo(ctx context.Context, params FetchRepoParams) (*RemoteStream, error) { 20 20 return nil, fmt.Errorf("Not yet implemented") 21 21 } 22 22 23 - func (self RelayRemote) SubscribeRecords(ctx context.Context, params SubscribeRecordsParams) (*RemoteStream, error) { 23 + func (r *RelayRemote) SubscribeRecords(ctx context.Context, params SubscribeRecordsParams) (*RemoteStream, error) { 24 24 return nil, fmt.Errorf("Not yet implemented") 25 25 }
+95
cmd/butterfly/remote/util.go
··· 1 + package remote 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "io" 8 + "net/http" 9 + "net/url" 10 + "strconv" 11 + "time" 12 + ) 13 + 14 + func XrpcListRepos(ctx context.Context, service string, params ListReposParams) (*ListReposResult, error) { 15 + var xrpcMethod = "com.atproto.sync.listRepos" 16 + if params.Collection != "" { 17 + xrpcMethod = "com.atproto.sync.listReposByCollection" 18 + } 19 + 20 + // Build URL with query parameters 21 + u, err := url.Parse(fmt.Sprintf("%s/xrpc/%s", service, xrpcMethod)) 22 + if err != nil { 23 + return nil, fmt.Errorf("failed to parse PDS URL: %w", err) 24 + } 25 + 26 + q := u.Query() 27 + if params.Collection != "" { 28 + q.Set("collection", params.Collection) 29 + } 30 + if params.Cursor != "" { 31 + q.Set("cursor", params.Cursor) 32 + } 33 + if params.Limit > 0 { 34 + q.Set("limit", strconv.Itoa(params.Limit)) 35 + } 36 + u.RawQuery = q.Encode() 37 + 38 + // Create request 39 + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) 40 + if err != nil { 41 + return nil, fmt.Errorf("failed to create request: %w", err) 42 + } 43 + 44 + req.Header.Set("Accept", "application/json") 45 + req.Header.Set("User-Agent", "butterfly/0.0.1") 46 + 47 + // Make request 48 + client := &http.Client{ 49 + Timeout: 30 * time.Second, 50 + } 51 + resp, err := client.Do(req) 52 + if err != nil { 53 + return nil, fmt.Errorf("failed to list repos: %w", err) 54 + } 55 + defer resp.Body.Close() 56 + 57 + if resp.StatusCode != http.StatusOK { 58 + body, _ := io.ReadAll(resp.Body) 59 + return nil, fmt.Errorf("failed to list repos: %s - %s", resp.Status, string(body)) 60 + } 61 + 62 + // Parse response 63 + var apiResp struct { 64 + Cursor *string `json:"cursor,omitempty"` 65 + Repos []struct { 66 + Did string `json:"did"` 67 + Head string `json:"head"` 68 + Rev string `json:"rev"` 69 + Active *bool `json:"active,omitempty"` 70 + Status *string `json:"status,omitempty"` 71 + } `json:"repos"` 72 + } 73 + 74 + if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { 75 + return nil, fmt.Errorf("failed to decode response: %w", err) 76 + } 77 + 78 + // Extract DIDs from response 79 + dids := make([]string, 0, len(apiResp.Repos)) 80 + for _, repo := range apiResp.Repos { 81 + // Only include active repos by default 82 + if repo.Active == nil || *repo.Active { 83 + dids = append(dids, repo.Did) 84 + } 85 + } 86 + 87 + result := &ListReposResult{ 88 + Dids: dids, 89 + } 90 + if apiResp.Cursor != nil { 91 + result.Cursor = *apiResp.Cursor 92 + } 93 + 94 + return result, nil 95 + }