diff --git a/app/screen_home.go b/app/screen_home.go index 6a8a658..40d8f12 100644 --- a/app/screen_home.go +++ b/app/screen_home.go @@ -22,7 +22,6 @@ THE SOFTWARE. package app import ( - "context" "fmt" "net/url" "strings" @@ -311,6 +310,7 @@ func (s *ScreenHome) initCli() { s.cli.AddCommand(w.NewCliCommand("getpds", s.cliGetPds)) s.cli.AddCommand(w.NewCliCommand("authpds", s.cliAuthPds)) s.cli.AddCommand(w.NewCliCommand("backuppds", s.cliBackupPds)) + s.cli.AddCommand(w.NewCliCommand("sendstatus", s.cliSendStatus)) } func (s *ScreenHome) toggleCli() { @@ -373,19 +373,18 @@ func (s *ScreenHome) cliAuthPds(args ...string) bool { return true } s.isLoading = true - ctx := context.Background() go func() { defer func() { s.isLoading = false }() atid := s.activePds.AtId.String() callbackRes := make(chan url.Values, 1) - listenPort, err := s.r.Auth.ListenForCallback(ctx, callbackRes) + listenPort, err := s.r.Auth.ListenForCallback(callbackRes) if err != nil { s.Log("Error Instantiating HTTP Server for Callback: %w", err) return } s.Log("Listening on %d", listenPort) var authUrl string - authUrl, err = s.r.Auth.StartAuthFlow(listenPort, ctx, atid, callbackRes) + authUrl, err = s.r.Auth.StartAuthFlow(listenPort, atid, callbackRes) if err != nil { s.Log("Error starting auth flow: %w", err) } @@ -414,6 +413,23 @@ func (s *ScreenHome) cliBackupPds(args ...string) bool { return true } +func (s *ScreenHome) cliSendStatus(args ...string) bool { + if s.activePds == nil { + s.Log("No active PDS.") + return true + } + s.isLoading = true + go func() { + defer func() { s.isLoading = false }() + if !s.r.Auth.HasAuth() { + s.Log("Not authorized. Run `authpds`") + return + } + s.r.SendToPDS() + }() + return true +} + func (s *ScreenHome) updatePdsListing() { s.pdsListing.SetTitle(fmt.Sprintf("─ %s (%s)", s.activePds.AtId.String(), s.activePds.Did.String())) s.pdsListing.Clear() diff --git a/data/repo.go b/data/repo.go index 73f91e2..d3a2f87 100644 --- a/data/repo.go +++ b/data/repo.go @@ -22,10 +22,13 @@ THE SOFTWARE. package data import ( + "context" + "fmt" "log/slog" "time" "git.bullercodeworks.com/brian/expds/data/models" + "github.com/bluesky-social/indigo/atproto/syntax" "github.com/spf13/viper" ) @@ -37,6 +40,8 @@ type Repo struct { handler *AppLogHandler logFunc func(string, ...any) + + context context.Context } func NewRepo() (*Repo, error) { @@ -44,6 +49,7 @@ func NewRepo() (*Repo, error) { LoadedPDSs: make(map[string]*models.Pds), BestBy: time.Minute * 15, handler: NewAppLogHandler(nil), + context: context.Background(), } if viper.GetBool(KeyDebug) { r.handler.SetLevel(slog.LevelDebug) @@ -70,6 +76,32 @@ func (r *Repo) GetPDS(atId string) (*models.Pds, error) { return p, nil } +func (r *Repo) SendToPDS() error { + session, err := r.Auth.GetSession() + if err != nil { + return err + } + c := session.APIClient() + body := map[string]any{ + "repo": c.AccountDID.String(), + "collection": "com.bullercodeworks.expds.status", + "record": map[string]any{ + "$type": "com.bullercodeworks.expds.status", + "text": "writeable", + "createdAt": syntax.DatetimeNow(), + }, + } + var resp struct { + Uri syntax.ATURI `json:"uri"` + } + slog.Debug("posting expds status...") + if err := c.Post(r.context, "com.atproto.repo.CreateRecord", body, &resp); err != nil { + return err + } + slog.Debug(fmt.Sprintf("posted: %s :: %s", resp.Uri.Authority(), resp.Uri.RecordKey())) + return nil +} + func (r *Repo) SetLogFunc(l func(string, ...any)) { r.logFunc = l r.handler = NewAppLogHandler(r.logFunc) diff --git a/data/repo_auth.go b/data/repo_auth.go index a0f9a2b..4a51d1b 100644 --- a/data/repo_auth.go +++ b/data/repo_auth.go @@ -21,12 +21,16 @@ type AuthRepo struct { oauthClient *oauth.ClientApp oauthConfig *oauth.ClientConfig store *SqliteStore + context context.Context session *oauth.ClientSessionData } func NewAuthRepo(r *Repo) (*AuthRepo, error) { - a := &AuthRepo{r: r} + a := &AuthRepo{ + r: r, + context: r.context, + } var err error a.oauthConfig, a.oauthClient, a.store, err = a.buildOAuthClient() if err != nil { @@ -39,7 +43,7 @@ func NewAuthRepo(r *Repo) (*AuthRepo, error) { func (r *AuthRepo) buildOAuthClient() (*oauth.ClientConfig, *oauth.ClientApp, *SqliteStore, error) { config := oauth.ClientConfig{ ClientID: "https://expds.bullercodeworks.com/oauth-client-metadata.json", - Scopes: []string{"atproto", "repo:*"}, + Scopes: []string{"atproto", "repo:*", "blob:*/*"}, UserAgent: "expds", } @@ -57,9 +61,9 @@ func (r *AuthRepo) buildOAuthClient() (*oauth.ClientConfig, *oauth.ClientApp, *S return &config, oauthClient, store, nil } -func (r *AuthRepo) StartAuthFlow(port int, ctx context.Context, identifier string, callbackRes chan url.Values) (string, error) { +func (r *AuthRepo) StartAuthFlow(port int, identifier string, callbackRes chan url.Values) (string, error) { r.oauthConfig.CallbackURL = fmt.Sprintf("http://127.0.0.1:%d/callback", port) - authUrl, err := r.oauthClient.StartAuthFlow(ctx, identifier) + authUrl, err := r.oauthClient.StartAuthFlow(r.context, identifier) if err != nil { return "", fmt.Errorf("error logging in: %w", err) } @@ -68,7 +72,7 @@ func (r *AuthRepo) StartAuthFlow(port int, ctx context.Context, identifier strin } exec.Command("xdg-open", authUrl).Run() - r.session, err = r.oauthClient.ProcessCallback(ctx, <-callbackRes) + r.session, err = r.oauthClient.ProcessCallback(r.context, <-callbackRes) if err != nil { return "", err } @@ -82,7 +86,7 @@ func (r *AuthRepo) prepareDbPath() string { } // HTTP Server listening for OAuth Response -func (r *AuthRepo) ListenForCallback(ctx context.Context, res chan url.Values) (int, error) { +func (r *AuthRepo) ListenForCallback(res chan url.Values) (int, error) { listener, err := net.Listen("tcp", ":0") if err != nil { return 0, err @@ -93,12 +97,12 @@ func (r *AuthRepo) ListenForCallback(ctx context.Context, res chan url.Values) ( Handler: mux, } - mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { - res <- r.URL.Query() + mux.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) { + res <- req.URL.Query() w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(200) w.Write([]byte("

expds

You can safely close this window and return to your application.

\n")) - go server.Shutdown(ctx) + go server.Shutdown(r.context) }) go func() { @@ -110,3 +114,16 @@ func (r *AuthRepo) ListenForCallback(ctx context.Context, res chan url.Values) ( return listener.Addr().(*net.TCPAddr).Port, nil } + +func (r *AuthRepo) HasAuth() bool { + sess, err := r.store.GetMostRecentSession(r.context) + return err != nil || sess == nil +} + +func (r *AuthRepo) GetSession() (*oauth.ClientSession, error) { + sess, err := r.store.GetMostRecentSession(r.context) + if err != nil { + return nil, fmt.Errorf("error getting most recent session: %w", err) + } + return r.oauthClient.ResumeSession(r.context, sess.AccountDID, sess.SessionID) +} diff --git a/static/oauth-client-metadata.json b/static/oauth-client-metadata.json index f854ba3..b978a01 100644 --- a/static/oauth-client-metadata.json +++ b/static/oauth-client-metadata.json @@ -5,7 +5,7 @@ "authorization_code", "refresh_token" ], - "scope": "atproto repo:*", + "scope": "atproto repo:* blob:*/*", "response_types": [ "code" ],