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 // Requires an invite code if the PDS has invites enabled 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") // PDS admin APIs use Basic Auth with "admin" as username 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 { // Create follow record 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 == "" { // Silently skip if directory account not configured return nil } session, err := p.CreateSession(dirHandle, dirPassword) if err != nil { return fmt.Errorf("directory login failed: %w", err) } return p.FollowAccount(session, targetDID) }