first commit (and successful migration!) 🎉
This commit is contained in:
commit
24db172462
4 changed files with 541 additions and 0 deletions
250
atproto.go
Normal file
250
atproto.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type (
|
||||
AtprotoCreateSession struct {
|
||||
Identifier string `json:"identifier"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
AtprotoSession struct {
|
||||
AccessJwt string `json:"accessJwt"`
|
||||
RefreshJwt string `json:"refreshJwt"`
|
||||
Handle string `json:"handle"`
|
||||
Did string `json:"did"`
|
||||
}
|
||||
|
||||
AtprotoIdentity struct {
|
||||
Did string `json:"did"`
|
||||
Handle string `json:"handle"`
|
||||
DidDoc string `json:"didDoc"`
|
||||
}
|
||||
AtprotoGetRecordResponse struct {
|
||||
Uri string `json:"uri"`
|
||||
Cid string `json:"cid"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
}
|
||||
AtprotoCreateRecord struct {
|
||||
Repo string `json:"repo"`
|
||||
Collection string `json:"collection"`
|
||||
Record json.RawMessage `json:"record"`
|
||||
}
|
||||
AtprotoPutRecord struct {
|
||||
Repo string `json:"repo"`
|
||||
Collection string `json:"collection"`
|
||||
Rkey string `json:"rkey"`
|
||||
Record any `json:"record"`
|
||||
}
|
||||
AtprotoUidCid struct {
|
||||
Uri string `json:"uri"`
|
||||
Cid string `json:"cid"`
|
||||
}
|
||||
AtprotoRecord struct {
|
||||
Type string `json:"$type"`
|
||||
}
|
||||
AtprotoRef struct {
|
||||
Link string `json:"$link"`
|
||||
}
|
||||
AtprotoBlob struct {
|
||||
LexiconTypeID string `json:"$type,const=blob"`
|
||||
Ref AtprotoRef `json:"ref"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
BskyActorProfileView struct {
|
||||
Did string `json:"did"`
|
||||
Handle string `json:"handle"`
|
||||
DisplayName *string `json:"displayName"`
|
||||
Description *string `json:"description"`
|
||||
Avatar *string `json:"avatar"`
|
||||
Associated *json.RawMessage `json:"associated"`
|
||||
IndexedAt *string `json:"indexedAt"`
|
||||
CreatedAt *string `json:"createdAt"`
|
||||
Viewer *json.RawMessage `json:"viewer"`
|
||||
Labels *json.RawMessage `json:"labels"`
|
||||
Verification *json.RawMessage `json:"verification"`
|
||||
Status *json.RawMessage `json:"status"`
|
||||
}
|
||||
BskyGetFollowsResponse struct {
|
||||
Subject BskyActorProfileView `json:"subject"`
|
||||
Cursor *string `json:"cursor"`
|
||||
Follows []BskyActorProfileView `json:"follows"`
|
||||
}
|
||||
|
||||
BskyActorProfile struct {
|
||||
LexiconTypeID string `json:"$type,const=app.bsky.actor.profile"`
|
||||
DisplayName *string `json:"displayName,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Avatar *AtprotoBlob `json:"avatar,omitempty"`
|
||||
Banner *AtprotoBlob `json:"banner,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
PinnedPost *AtprotoUidCid `json:"pinnedPost,omitempty"`
|
||||
}
|
||||
BskyGraphFollow struct {
|
||||
LexiconTypeID string `json:"$type,const=app.bsky.graph.follow"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
|
||||
ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
)
|
||||
|
||||
func CreateSession (pdsUrl string, identifier string, password string) (*AtprotoSession, error) {
|
||||
reqBody, reqBodyBytes := AtprotoCreateSession{
|
||||
Identifier: identifier,
|
||||
Password: password,
|
||||
}, new(bytes.Buffer)
|
||||
json.NewEncoder(reqBodyBytes).Encode(reqBody)
|
||||
res, err := http.Post(pdsUrl + "/xrpc/com.atproto.server.createSession", "application/json", reqBodyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to log into @%s: %v\n", identifier, err))
|
||||
}
|
||||
|
||||
session := AtprotoSession{}
|
||||
err = json.NewDecoder(res.Body).Decode(&session)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to parse PDS response: %v\n", err))
|
||||
}
|
||||
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func ResolveAtprotoHandle(session *AtprotoSession, pdsUrl string, handle string) (string, error) {
|
||||
reqUrl, _ := url.Parse(pdsUrl + "/xrpc/com.atproto.identity.resolveHandle")
|
||||
reqUrl.RawQuery = url.Values{
|
||||
"handle": { handle },
|
||||
}.Encode()
|
||||
req, _ := http.NewRequest(http.MethodGet, reqUrl.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errBody := ErrorResponse{}
|
||||
json.NewDecoder(res.Body).Decode(&errBody)
|
||||
return "", errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
||||
}
|
||||
|
||||
type DidResponse struct {
|
||||
Did string `json:"did"`
|
||||
}
|
||||
identity := DidResponse{}
|
||||
json.NewDecoder(res.Body).Decode(&identity)
|
||||
|
||||
return identity.Did, nil
|
||||
}
|
||||
|
||||
func CreateAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record json.RawMessage) (*AtprotoUidCid, error) {
|
||||
reqBody, reqBodyBytes := AtprotoCreateRecord{
|
||||
Repo: session.Did,
|
||||
Collection: collection,
|
||||
Record: record,
|
||||
}, new(bytes.Buffer)
|
||||
err := json.NewEncoder(reqBodyBytes).Encode(reqBody)
|
||||
if err != nil { return nil, err }
|
||||
req, _ := http.NewRequest(http.MethodPost, pdsUrl + "/xrpc/com.atproto.repo.createRecord", reqBodyBytes)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil { return nil, err }
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errBody := ErrorResponse{}
|
||||
json.NewDecoder(res.Body).Decode(&errBody)
|
||||
return nil, errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
||||
}
|
||||
createdRecord := AtprotoUidCid{}
|
||||
err = json.NewDecoder(res.Body).Decode(&createdRecord)
|
||||
return &createdRecord, err
|
||||
}
|
||||
|
||||
func PutAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record any) (*AtprotoUidCid, error) {
|
||||
reqBody, reqBodyBytes := AtprotoPutRecord{
|
||||
Repo: session.Did,
|
||||
Collection: collection,
|
||||
Record: record,
|
||||
Rkey: "self",
|
||||
}, new(bytes.Buffer)
|
||||
err := json.NewEncoder(reqBodyBytes).Encode(reqBody)
|
||||
|
||||
if err != nil { return nil, err }
|
||||
req, _ := http.NewRequest(http.MethodPost, pdsUrl + "/xrpc/com.atproto.repo.putRecord", reqBodyBytes)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil { return nil, err }
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errBody := ErrorResponse{}
|
||||
json.NewDecoder(res.Body).Decode(&errBody)
|
||||
return nil, errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
||||
}
|
||||
updatedRecord := AtprotoUidCid{}
|
||||
err = json.NewDecoder(res.Body).Decode(&updatedRecord)
|
||||
return &updatedRecord, err
|
||||
}
|
||||
|
||||
func GetAtprotoBlob(pdsUrl string, did string, cid string) ([]byte, error) {
|
||||
reqUrl, _ := url.Parse(pdsUrl + "/xrpc/com.atproto.sync.getBlob")
|
||||
reqUrl.RawQuery = url.Values{
|
||||
"did": { did },
|
||||
"cid": { cid },
|
||||
}.Encode()
|
||||
res, err := http.Get(reqUrl.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errBody := ErrorResponse{}
|
||||
json.NewDecoder(res.Body).Decode(&errBody)
|
||||
return nil, errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to read bytes: %v", err))
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func UploadAtprotoBlob(session *AtprotoSession, pdsUrl string, data []byte, mimeType string) (*AtprotoBlob, error) {
|
||||
req, _ := http.NewRequest(http.MethodPost, pdsUrl + "/xrpc/com.atproto.repo.uploadBlob", bytes.NewReader(data))
|
||||
req.Header.Set("Content-Type", mimeType)
|
||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errBody := ErrorResponse{}
|
||||
json.NewDecoder(res.Body).Decode(&errBody)
|
||||
return nil, errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Blob AtprotoBlob `json:"blob"`
|
||||
}
|
||||
blob := Response{}
|
||||
err = json.NewDecoder(res.Body).Decode(&blob)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to read response body: %v", err))
|
||||
}
|
||||
blob.Blob.LexiconTypeID = "blob"
|
||||
return &blob.Blob, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue