181 lines
4.8 KiB
Go
181 lines
4.8 KiB
Go
package blueskyclient
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/log"
|
|
"github.com/dghubble/sling"
|
|
)
|
|
|
|
type BlueskyConfig struct {
|
|
PDSURL string `json:"pds-url"`
|
|
Repo string `json:"repo"`
|
|
Handle string `json:"handle"`
|
|
DID string `json:"did"`
|
|
AppPassword string `json:"app-password"`
|
|
AccessJWT string `json:"access-jwt"`
|
|
RefreshJWT string `json:"refresh-jwt"`
|
|
Cursor int64 `json:"cursor"`
|
|
}
|
|
|
|
type DIDResponse struct {
|
|
Context []string `json:"@context"`
|
|
ID string `json:"id"`
|
|
AlsoKnownAs []string `json:"alsoKnownAs"`
|
|
VerificationMethod []struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Controller string `json:"controller"`
|
|
PublicKeyMultibase string `json:"publicKeyMultibase"`
|
|
} `json:"verificationMethod"`
|
|
Service []struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
ServiceEndpoint string `json:"serviceEndpoint"`
|
|
} `json:"service"`
|
|
}
|
|
|
|
type BSkySessionResponse struct {
|
|
AccessJWT string `json:"accessJwt,omitempty"`
|
|
RefreshJWT string `json:"refreshJwt,omitempty"`
|
|
Handle string `json:"handle"`
|
|
DID string `json:"did"`
|
|
Error string `json:"error,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
type CommitResponse struct {
|
|
URI string `json:"uri"`
|
|
Cid string `json:"cid"`
|
|
Commit struct {
|
|
Cid string `json:"cid"`
|
|
Rev string `json:"rev"`
|
|
} `json:"commit"`
|
|
Error string `json:"error"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func (c *CommitResponse) GetRKey() string {
|
|
s := strings.SplitN(c.URI, "/", 5)
|
|
if len(s) == 5 {
|
|
return s[4]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Link struct {
|
|
Cid string `json:"cid"`
|
|
URI string `json:"uri"`
|
|
}
|
|
|
|
type Bluesky struct {
|
|
Cfg *BlueskyConfig
|
|
HttpClient *http.Client
|
|
Logger *log.Logger
|
|
sling *sling.Sling
|
|
}
|
|
|
|
func (bluesky *Bluesky) CreateSession(cfg *BlueskyConfig) error {
|
|
body := struct {
|
|
Identifier string `json:"identifier"`
|
|
Password string `json:"password"`
|
|
}{
|
|
Identifier: cfg.Handle,
|
|
Password: cfg.AppPassword,
|
|
}
|
|
resp := new(BSkySessionResponse)
|
|
|
|
bluesky.sling.New().Post("/xrpc/com.atproto.server.createSession").BodyJSON(body).ReceiveSuccess(resp)
|
|
if resp.AccessJWT != "" {
|
|
cfg.AccessJWT = resp.AccessJWT
|
|
cfg.RefreshJWT = resp.RefreshJWT
|
|
return nil
|
|
}
|
|
|
|
bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT))
|
|
return errors.New("unable to authenticate, check handle/password")
|
|
}
|
|
|
|
func (bluesky *Bluesky) RefreshSession() error {
|
|
resp := new(BSkySessionResponse)
|
|
|
|
bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.RefreshJWT)).
|
|
Post("/xrpc/com.atproto.server.refreshSession").Receive(resp, resp)
|
|
if resp.AccessJWT != "" {
|
|
bluesky.Cfg.AccessJWT = resp.AccessJWT
|
|
bluesky.Cfg.RefreshJWT = resp.RefreshJWT
|
|
PersistAuthSession(bluesky.Cfg)
|
|
bluesky.sling.Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT))
|
|
return nil
|
|
}
|
|
|
|
return bluesky.CreateSession(bluesky.Cfg)
|
|
}
|
|
|
|
func (bluesky *Bluesky) CheckSessionValid() {
|
|
resp := new(BSkySessionResponse)
|
|
|
|
bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT)).
|
|
Get("/xrpc/app.bsky.actor.getProfile").Receive(resp, resp)
|
|
if resp.Error == "ExpiredToken" {
|
|
bluesky.RefreshSession()
|
|
}
|
|
}
|
|
|
|
func (bluesky *Bluesky) GetPost(uri string) *Post {
|
|
bluesky.CheckSessionValid()
|
|
var post = struct {
|
|
URI string `json:"uri"`
|
|
CID string `json:"cid"`
|
|
Value *Post `json:"value"`
|
|
}{}
|
|
|
|
args := strings.SplitN(uri, "/", 5)
|
|
params := struct {
|
|
RKey string `url:"rkey"`
|
|
Repo string `url:"repo"`
|
|
Collection string `url:"collection"`
|
|
}{
|
|
RKey: args[4],
|
|
Repo: args[2],
|
|
Collection: args[3],
|
|
}
|
|
bluesky.sling.New().Get("/xrpc/com.atproto.repo.getRecord").QueryStruct(params).ReceiveSuccess(&post)
|
|
|
|
return post.Value
|
|
}
|
|
|
|
func (bluesky *Bluesky) DeleteRecord(args []string) *CommitResponse {
|
|
bluesky.CheckSessionValid()
|
|
|
|
resp := new(CommitResponse)
|
|
params := struct {
|
|
RKey string `json:"rkey"`
|
|
Repo string `json:"repo"`
|
|
Collection string `json:"collection"`
|
|
}{
|
|
RKey: args[0],
|
|
Repo: args[1],
|
|
Collection: args[2],
|
|
}
|
|
bluesky.sling.New().Set("Authorization", fmt.Sprintf("Bearer %s", bluesky.Cfg.AccessJWT)).
|
|
Post("/xrpc/com.atproto.repo.deleteRecord").BodyJSON(params).Receive(resp, resp)
|
|
return resp
|
|
}
|
|
|
|
func (bluesky *Bluesky) FetchPost(did string, rkey string) *[]FetchedPost {
|
|
resp := &struct {
|
|
Posts []FetchedPost `json:"posts"`
|
|
}{}
|
|
params := struct {
|
|
URIs string `url:"uris"`
|
|
}{
|
|
URIs: fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey),
|
|
}
|
|
bluesky.sling.New().Base("https://public.api.bsky.app").
|
|
Get("/xrpc/app.bsky.feed.getPosts").QueryStruct(¶ms).Receive(resp, resp)
|
|
return &resp.Posts
|
|
}
|