1package xrpcclient
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "io"
8 "net/http"
9
10 "github.com/bluesky-social/indigo/api/atproto"
11 "github.com/bluesky-social/indigo/xrpc"
12 indigoxrpc "github.com/bluesky-social/indigo/xrpc"
13 oauth "tangled.sh/icyphox.sh/atproto-oauth"
14)
15
16var (
17 ErrXrpcUnsupported = errors.New("xrpc not supported on this knot")
18 ErrXrpcUnauthorized = errors.New("unauthorized xrpc request")
19 ErrXrpcFailed = errors.New("xrpc request failed")
20 ErrXrpcInvalid = errors.New("invalid xrpc request")
21)
22
23type Client struct {
24 *oauth.XrpcClient
25 authArgs *oauth.XrpcAuthedRequestArgs
26}
27
28func NewClient(client *oauth.XrpcClient, authArgs *oauth.XrpcAuthedRequestArgs) *Client {
29 return &Client{
30 XrpcClient: client,
31 authArgs: authArgs,
32 }
33}
34
35func (c *Client) RepoPutRecord(ctx context.Context, input *atproto.RepoPutRecord_Input) (*atproto.RepoPutRecord_Output, error) {
36 var out atproto.RepoPutRecord_Output
37 if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.putRecord", nil, input, &out); err != nil {
38 return nil, err
39 }
40
41 return &out, nil
42}
43
44func (c *Client) RepoApplyWrites(ctx context.Context, input *atproto.RepoApplyWrites_Input) (*atproto.RepoApplyWrites_Output, error) {
45 var out atproto.RepoApplyWrites_Output
46 if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.applyWrites", nil, input, &out); err != nil {
47 return nil, err
48 }
49
50 return &out, nil
51}
52
53func (c *Client) RepoGetRecord(ctx context.Context, cid string, collection string, repo string, rkey string) (*atproto.RepoGetRecord_Output, error) {
54 var out atproto.RepoGetRecord_Output
55
56 params := map[string]interface{}{
57 "cid": cid,
58 "collection": collection,
59 "repo": repo,
60 "rkey": rkey,
61 }
62 if err := c.Do(ctx, c.authArgs, xrpc.Query, "", "com.atproto.repo.getRecord", params, nil, &out); err != nil {
63 return nil, err
64 }
65
66 return &out, nil
67}
68
69func (c *Client) RepoUploadBlob(ctx context.Context, input io.Reader) (*atproto.RepoUploadBlob_Output, error) {
70 var out atproto.RepoUploadBlob_Output
71 if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "*/*", "com.atproto.repo.uploadBlob", nil, input, &out); err != nil {
72 return nil, err
73 }
74
75 return &out, nil
76}
77
78func (c *Client) SyncGetBlob(ctx context.Context, cid string, did string) ([]byte, error) {
79 buf := new(bytes.Buffer)
80
81 params := map[string]interface{}{
82 "cid": cid,
83 "did": did,
84 }
85 if err := c.Do(ctx, c.authArgs, xrpc.Query, "", "com.atproto.sync.getBlob", params, nil, buf); err != nil {
86 return nil, err
87 }
88
89 return buf.Bytes(), nil
90}
91
92func (c *Client) RepoDeleteRecord(ctx context.Context, input *atproto.RepoDeleteRecord_Input) (*atproto.RepoDeleteRecord_Output, error) {
93 var out atproto.RepoDeleteRecord_Output
94 if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.deleteRecord", nil, input, &out); err != nil {
95 return nil, err
96 }
97
98 return &out, nil
99}
100
101func (c *Client) ServerGetServiceAuth(ctx context.Context, aud string, exp int64, lxm string) (*atproto.ServerGetServiceAuth_Output, error) {
102 var out atproto.ServerGetServiceAuth_Output
103
104 params := map[string]interface{}{
105 "aud": aud,
106 "exp": exp,
107 "lxm": lxm,
108 }
109 if err := c.Do(ctx, c.authArgs, xrpc.Query, "", "com.atproto.server.getServiceAuth", params, nil, &out); err != nil {
110 return nil, err
111 }
112
113 return &out, nil
114}
115
116// produces a more manageable error
117func HandleXrpcErr(err error) error {
118 if err == nil {
119 return nil
120 }
121
122 var xrpcerr *indigoxrpc.Error
123 if ok := errors.As(err, &xrpcerr); !ok {
124 return ErrXrpcInvalid
125 }
126
127 switch xrpcerr.StatusCode {
128 case http.StatusNotFound:
129 return ErrXrpcUnsupported
130 case http.StatusUnauthorized:
131 return ErrXrpcUnauthorized
132 default:
133 return ErrXrpcFailed
134 }
135}