this repo has no description
0
fork

Configure Feed

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

progress on client SDK

+200 -38
-35
atproto/client/api_client.go
··· 3 3 import ( 4 4 "context" 5 5 "encoding/json" 6 - "io" 7 6 "net/http" 8 7 9 8 "github.com/bluesky-social/indigo/atproto/syntax" ··· 29 28 // Returns the currently-authenticated account DID, or empty string if not available. 30 29 AuthDID() syntax.DID 31 30 } 32 - 33 - type APIRequest struct { 34 - HTTPVerb string // TODO: type? 35 - Endpoint syntax.NSID 36 - Body io.Reader 37 - QueryParams map[string]string // TODO: better type for this? 38 - Headers map[string]string 39 - } 40 - 41 - func (r *APIRequest) HTTPRequest(ctx context.Context, host string, headers map[string]string) (*http.Request, error) { 42 - // TODO: use 'url' to safely construct the request URL 43 - u := host + "/xrpc/" + r.Endpoint.String() 44 - // XXX: query params 45 - httpReq, err := http.NewRequestWithContext(ctx, r.HTTPVerb, u, r.Body) 46 - if err != nil { 47 - return nil, err 48 - } 49 - 50 - // first set default headers 51 - if headers != nil { 52 - for k, v := range headers { 53 - httpReq.Header.Set(k, v) 54 - } 55 - } 56 - 57 - // then request-specific take priority (overwrite) 58 - if r.Headers != nil { 59 - for k, v := range r.Headers { 60 - httpReq.Header.Set(k, v) 61 - } 62 - } 63 - 64 - return httpReq, nil 65 - }
+53
atproto/client/api_request.go
··· 1 + package client 2 + 3 + import ( 4 + "context" 5 + "io" 6 + "net/http" 7 + "net/url" 8 + 9 + "github.com/bluesky-social/indigo/atproto/syntax" 10 + ) 11 + 12 + type APIRequest struct { 13 + HTTPVerb string // TODO: type? 14 + Endpoint syntax.NSID 15 + Body io.Reader 16 + QueryParams map[string]string // TODO: better type for this? 17 + Headers map[string]string 18 + } 19 + 20 + func (r *APIRequest) HTTPRequest(ctx context.Context, host string, headers map[string]string) (*http.Request, error) { 21 + u, err := url.Parse(host) 22 + if err != nil { 23 + return nil, err 24 + } 25 + u.Path = "/xrpc/" + r.Endpoint.String() 26 + if r.QueryParams != nil { 27 + q := u.Query() 28 + for k, v := range r.QueryParams { 29 + q.Add(k, v) 30 + } 31 + u.RawQuery = q.Encode() 32 + } 33 + httpReq, err := http.NewRequestWithContext(ctx, r.HTTPVerb, u.String(), r.Body) 34 + if err != nil { 35 + return nil, err 36 + } 37 + 38 + // first set default headers... 39 + if headers != nil { 40 + for k, v := range headers { 41 + httpReq.Header.Set(k, v) 42 + } 43 + } 44 + 45 + // ... then request-specific take priority (overwrite) 46 + if r.Headers != nil { 47 + for k, v := range r.Headers { 48 + httpReq.Header.Set(k, v) 49 + } 50 + } 51 + 52 + return httpReq, nil 53 + }
+15 -2
atproto/client/base_api_client.go
··· 36 36 defer resp.Body.Close() 37 37 // TODO: duplicate error handling with Post()? 38 38 if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { 39 - return nil, fmt.Errorf("non-successful API request status: %d", resp.StatusCode) 39 + var eb ErrorBody 40 + if err := json.NewDecoder(resp.Body).Decode(&eb); err != nil { 41 + return nil, &APIError{StatusCode: resp.StatusCode} 42 + } 43 + return nil, eb.APIError(resp.StatusCode) 40 44 } 41 45 42 46 var ret json.RawMessage ··· 70 74 defer resp.Body.Close() 71 75 // TODO: duplicate error handling with Get()? 72 76 if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { 73 - return nil, fmt.Errorf("non-successful API request status: %d", resp.StatusCode) 77 + var eb ErrorBody 78 + if err := json.NewDecoder(resp.Body).Decode(&eb); err != nil { 79 + return nil, &APIError{StatusCode: resp.StatusCode} 80 + } 81 + return nil, eb.APIError(resp.StatusCode) 74 82 } 75 83 76 84 var ret json.RawMessage ··· 84 92 httpReq, err := req.HTTPRequest(ctx, c.Host, c.DefaultHeaders) 85 93 if err != nil { 86 94 return nil, err 95 + } 96 + 97 + // TODO: thread-safe? 98 + if c.HTTPClient == nil { 99 + c.HTTPClient = http.DefaultClient 87 100 } 88 101 89 102 var resp *http.Response
+55
atproto/client/cmd/atclient/main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "log/slog" 8 + "os" 9 + 10 + "github.com/bluesky-social/indigo/atproto/client" 11 + 12 + "github.com/urfave/cli/v2" 13 + ) 14 + 15 + func main() { 16 + app := cli.App{ 17 + Name: "atclient", 18 + Usage: "dev helper for atproto/client SDK", 19 + Commands: []*cli.Command{ 20 + &cli.Command{ 21 + Name: "get", 22 + Usage: "do a basic GET request", 23 + Action: runGet, 24 + }, 25 + }, 26 + } 27 + h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}) 28 + slog.SetDefault(slog.New(h)) 29 + app.RunAndExitOnError() 30 + } 31 + 32 + func runGet(cctx *cli.Context) error { 33 + ctx := context.Background() 34 + 35 + c := client.BaseAPIClient{ 36 + Host: "https://public.api.bsky.app", 37 + } 38 + 39 + params := map[string]string{ 40 + "actor": "atproto.com", 41 + "limit": "5", 42 + "includePins": "false", 43 + } 44 + b, err := c.Get(ctx, "app.bsky.feed.getAuthorFeed", params) 45 + if err != nil { 46 + return err 47 + } 48 + 49 + out, err := json.MarshalIndent(b, "", " ") 50 + if err != nil { 51 + return err 52 + } 53 + fmt.Println(string(out)) 54 + return nil 55 + }
+35
atproto/client/error.go
··· 1 + package client 2 + 3 + import ( 4 + "fmt" 5 + ) 6 + 7 + type APIError struct { 8 + StatusCode int 9 + Name string 10 + Message string 11 + } 12 + 13 + func (ae *APIError) Error() string { 14 + if ae.StatusCode > 0 && ae.Name != "" && ae.Message != "" { 15 + return fmt.Sprintf("API request failed (HTTP %d): %s: %s", ae.StatusCode, ae.Name, ae.Message) 16 + } else if ae.StatusCode > 0 && ae.Name != "" { 17 + return fmt.Sprintf("API request failed (HTTP %d): %s", ae.StatusCode, ae.Name) 18 + } else if ae.StatusCode > 0 { 19 + return fmt.Sprintf("API request failed (HTTP %d)", ae.StatusCode) 20 + } 21 + return "API request failed" 22 + } 23 + 24 + type ErrorBody struct { 25 + Name string `json:"error"` 26 + Message string `json:"message,omitempty"` 27 + } 28 + 29 + func (eb *ErrorBody) APIError(statusCode int) error { 30 + return &APIError{ 31 + StatusCode: statusCode, 32 + Name: eb.Name, 33 + Message: eb.Message, 34 + } 35 + }
+41
atproto/client/examples_test.go
··· 1 + package client 2 + 3 + import ( 4 + "fmt" 5 + 6 + atdata "github.com/bluesky-social/indigo/atproto/data" 7 + ) 8 + 9 + func ExampleGetRequest() { 10 + 11 + // First load Lexicon schema JSON files from local disk. 12 + cat := NewBaseCatalog() 13 + if err := cat.LoadDirectory("testdata/catalog"); err != nil { 14 + panic("failed to load lexicons") 15 + } 16 + 17 + // Parse record JSON data using atproto/data helper 18 + recordJSON := `{ 19 + "$type": "example.lexicon.record", 20 + "integer": 123, 21 + "formats": { 22 + "did": "did:web:example.com", 23 + "aturi": "at://handle.example.com/com.example.nsid/asdf123", 24 + "datetime": "2023-10-30T22:25:23Z", 25 + "language": "en", 26 + "tid": "3kznmn7xqxl22" 27 + } 28 + }` 29 + 30 + recordData, err := atdata.UnmarshalJSON([]byte(recordJSON)) 31 + if err != nil { 32 + panic("failed to parse record JSON") 33 + } 34 + 35 + if err := ValidateRecord(&cat, recordData, "example.lexicon.record", 0); err != nil { 36 + fmt.Printf("Schema validation failed: %v\n", err) 37 + } else { 38 + fmt.Println("Success!") 39 + } 40 + // Output: Success! 41 + }
+1 -1
atproto/client/refresh_auth.go
··· 12 12 RefreshToken string 13 13 DID syntax.DID 14 14 // The AuthHost might different from any APIClient host, if there is an entryway involved 15 - AuthHost string 15 + AuthHost string 16 16 } 17 17 18 18 // TODO: