Files
2026-02-02 15:30:15 -05:00

184 lines
4.2 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
// CreateSession authenticates with the PDS and returns a session
func (p *Publisher) CreateSession(handle, password string) (*PDSSession, error) {
payload := map[string]string{
"identifier": handle,
"password": password,
}
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}
resp, err := p.httpClient.Post(
p.pdsHost+"/xrpc/com.atproto.server.createSession",
"application/json",
bytes.NewReader(body),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("auth failed: %s - %s", resp.Status, string(respBody))
}
var session PDSSession
if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
return nil, err
}
return &session, nil
}
// CreateAccount creates a new account on the PDS
func (p *Publisher) CreateAccount(handle, email, password, inviteCode string) (*PDSSession, error) {
payload := map[string]interface{}{
"handle": handle,
"email": email,
"password": password,
}
if inviteCode != "" {
payload["inviteCode"] = inviteCode
}
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}
resp, err := p.httpClient.Post(
p.pdsHost+"/xrpc/com.atproto.server.createAccount",
"application/json",
bytes.NewReader(body),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("create account failed: %s - %s", resp.Status, string(respBody))
}
var session PDSSession
if err := json.Unmarshal(respBody, &session); err != nil {
return nil, err
}
return &session, nil
}
// CreateInviteCode creates an invite code using PDS admin password (Basic Auth)
func (p *Publisher) CreateInviteCode(adminPassword string, useCount int) (string, error) {
payload := map[string]interface{}{
"useCount": useCount,
}
body, err := json.Marshal(payload)
if err != nil {
return "", err
}
req, err := http.NewRequest("POST", p.pdsHost+"/xrpc/com.atproto.server.createInviteCode", bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("admin", adminPassword)
resp, err := p.httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("create invite failed: %s - %s", resp.Status, string(respBody))
}
var result struct {
Code string `json:"code"`
}
if err := json.Unmarshal(respBody, &result); err != nil {
return "", err
}
return result.Code, nil
}
// FollowAccount creates a follow record from the authenticated session to the target DID
func (p *Publisher) FollowAccount(session *PDSSession, targetDID string) error {
now := time.Now().UTC().Format(time.RFC3339)
record := map[string]interface{}{
"$type": "app.bsky.graph.follow",
"subject": targetDID,
"createdAt": now,
}
payload := map[string]interface{}{
"repo": session.DID,
"collection": "app.bsky.graph.follow",
"record": record,
}
body, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest("POST", p.pdsHost+"/xrpc/com.atproto.repo.createRecord", bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+session.AccessJwt)
resp, err := p.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("follow failed: %s - %s", resp.Status, string(respBody))
}
return nil
}
// FollowAsDirectory logs in as the directory account and follows the target DID
func (p *Publisher) FollowAsDirectory(targetDID string) error {
dirHandle := os.Getenv("DIRECTORY_HANDLE")
dirPassword := os.Getenv("DIRECTORY_PASSWORD")
if dirHandle == "" || dirPassword == "" {
return nil
}
session, err := p.CreateSession(dirHandle, dirPassword)
if err != nil {
return fmt.Errorf("directory login failed: %w", err)
}
return p.FollowAccount(session, targetDID)
}