From 02f7863227ea28e93636e6d491fb1d315de61cfb Mon Sep 17 00:00:00 2001 From: Alex Wilkerson Date: Tue, 5 Nov 2024 13:39:15 -0700 Subject: [PATCH] refactor --- bot/cmd/main.go | 31 ++++++++--------- bot/pkg/bsky/bsky.go | 33 +++++++++--------- bot/pkg/environment/environment.go | 6 ++-- bot/pkg/gsheets/gsheets.go | 56 ++++++++++++++---------------- bot/pkg/mastodon/mastodon.go | 25 +++++++++---- 5 files changed, 78 insertions(+), 73 deletions(-) diff --git a/bot/cmd/main.go b/bot/cmd/main.go index 28add62..5760d4b 100644 --- a/bot/cmd/main.go +++ b/bot/cmd/main.go @@ -6,38 +6,28 @@ import ( "log" "os" "os/signal" + "time" "github.com/togdon/reply-bot/bot/pkg/environment" - "github.com/togdon/reply-bot/bot/pkg/mastodon" "github.com/togdon/reply-bot/bot/pkg/gsheets" + "github.com/togdon/reply-bot/bot/pkg/mastodon" ) - - func main() { cfg, err := environment.New() - if err != nil { log.Fatalf("Error loading .env or ENV: %v", err) } + log.Printf("Successfully read the env\n") ctx, cancel := context.WithCancel(context.Background()) defer cancel() - writeChan := make(chan interface{}) - gsheetClient, err := gsheets.NewGSheetsClient(gsheets.CREDS_FILE, gsheets.SHEET_ID, gsheets.SHEET_NAME) + gsheetClient, err := gsheets.NewClient(ctx) if err != nil { log.Fatalf("Unable to create gsheets client: %v", err) } - mastodonClient, err := mastodon.NewClient( - writeChan, - gsheetClient, - mastodon.WithConfig(*cfg), - ) - if err != nil { - log.Fatal(err) - } errs := make(chan error, 1) @@ -49,15 +39,24 @@ func main() { cancel() }() - go mastodonClient.Run(ctx, cancel, errs) - go mastodonClient.Write(ctx) + mastodonClient, err := mastodon.NewClient(gsheetClient, mastodon.WithConfig(*cfg)) + if err != nil { + log.Fatal(err) + } + + go mastodonClient.Run(ctx, errs) for { select { case err := <-errs: fmt.Println(err) case <-ctx.Done(): + fmt.Println("Context cancelled, waiting 5 seconds for services to shut down...") + + time.Sleep(5 * time.Second) + fmt.Println("Shutting down...") + return } } diff --git a/bot/pkg/bsky/bsky.go b/bot/pkg/bsky/bsky.go index 3483a56..0799fa9 100644 --- a/bot/pkg/bsky/bsky.go +++ b/bot/pkg/bsky/bsky.go @@ -11,7 +11,7 @@ import ( ) const ( - bskyFeedUrl = "https://public.api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=at://did:plc:ltradugkwaw6yfotr7boceaj/app.bsky.feed.generator/aaapztniwbk46" + bskyFeedURL = "https://public.api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=at://did:plc:ltradugkwaw6yfotr7boceaj/app.bsky.feed.generator/aaapztniwbk46" ) type Post struct { @@ -23,6 +23,7 @@ type Post struct { RepostCount int `json:"reposeCount"` QuoteCount int `json:"quoteCount"` } + type FeedItem struct { Post Post `json:"post"` } @@ -31,61 +32,59 @@ type SearchResponse struct { Feed []FeedItem `json:"feed"` } -func FetchPosts(client *gsheets.Client) { - - resp, err := http.Get(bskyFeedUrl) - +func FetchPosts(client *gsheets.Client) error { + resp, err := http.Get(bskyFeedURL) if err != nil { - panic(err) + return err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) - if err != nil { - panic(err) + return err } - var search_hits SearchResponse + var searchHits SearchResponse - if err := json.Unmarshal(body, &search_hits); err != nil { - panic(err) + if err := json.Unmarshal(body, &searchHits); err != nil { + return err } - for _, item := range search_hits.Feed { + for _, item := range searchHits.Feed { //TODO more specific logic to filter bots / low engagement posts / low follower authors? - url, err := generateBskyUrl(item.Post) if err != nil { fmt.Printf("error generating bsky url for uri %v", err) + + continue } fmt.Printf("Associated URL: %s\n", url) - } //TODO write to the google sheet where responses can be generated? + + return nil } func generateBskyUrl(post Post) (string, error) { - uri := post.URI handle, ok := post.Author["handle"] if !ok { return "", fmt.Errorf("author handle invalid") } - rkey, err := extractRKey(uri) + rkey, err := extractRKey(post.URI) if err != nil { return "", fmt.Errorf("failed to extract rkey for post: %w", err) } return fmt.Sprintf("https://bsky.app/profile/%s/post/%s", handle, rkey), nil - } func extractRKey(uri string) (string, error) { parts := strings.Split(uri, "/") + if len(parts) < 2 { return "", fmt.Errorf("invalid uri format") } diff --git a/bot/pkg/environment/environment.go b/bot/pkg/environment/environment.go index c358cfe..edc42eb 100644 --- a/bot/pkg/environment/environment.go +++ b/bot/pkg/environment/environment.go @@ -23,10 +23,10 @@ type BlueSky struct { func New() (*Config, error) { var cfg Config - err := env.Parse(&cfg) - if err != nil { + + if err := env.Parse(&cfg); err != nil { return nil, err } - return &cfg, nil + return &cfg, nil } diff --git a/bot/pkg/gsheets/gsheets.go b/bot/pkg/gsheets/gsheets.go index 25e940e..f924ffe 100644 --- a/bot/pkg/gsheets/gsheets.go +++ b/bot/pkg/gsheets/gsheets.go @@ -12,55 +12,51 @@ import ( ) const ( - SHEET_ID = "1wD8zsIcn9vUPmL749MFAreXx8cfaYeqRfFoGuSnJ2Lk" - SHEET_NAME = "replies" - CREDS_FILE = "credentials.json" + sheetID = "1wD8zsIcn9vUPmL749MFAreXx8cfaYeqRfFoGuSnJ2Lk" + sheetName = "replies" + credsFile = "credentials.json" ) -// GSheetsClient encapsulates the Sheets service and sheet configuration. +// Client encapsulates the Sheets service and sheet configuration. type Client struct { - Service *sheets.Service - SheetID string - SheetName string + service *sheets.Service + sheetID string + sheetName string } -// NewGSheetsClient initializes a Google Sheets API client and returns a GSheetsClient instance. -func NewGSheetsClient(credentialsFile, sheetID, sheetName string) (*Client, error) { - ctx := context.Background() - service, err := sheets.NewService(ctx, option.WithCredentialsFile(credentialsFile)) +// NewClient initializes a Google Sheets API client and returns a Client instance. +func NewClient(ctx context.Context) (*Client, error) { + service, err := sheets.NewService(ctx, option.WithCredentialsFile(credsFile)) if err != nil { return nil, fmt.Errorf("unable to create Sheets client: %v", err) } return &Client{ - Service: service, - SheetID: sheetID, - SheetName: sheetName, + service: service, + sheetID: sheetID, + sheetName: sheetName, }, nil } // AppendRow adds a new entry to the Google Sheet, formatted with URL, Post Type, and Responded checkbox. func (c *Client) AppendRow(post post.Post) error { - rowData := []interface{}{ - post.ID, - post.URI, - post.Type, - post.Content, - post.Source, - false, - } - - writeRange := fmt.Sprintf("%s!A:F", c.SheetName) // Columns A to F - // Append data to the specified range in the sheet - _, err := c.Service.Spreadsheets.Values.Append(c.SheetID, writeRange, &sheets.ValueRange{ - Values: [][]interface{}{rowData}, - }).ValueInputOption("USER_ENTERED").Do() - - if err != nil { + if _, err := c.service.Spreadsheets.Values.Append(c.sheetID, fmt.Sprintf("%s!A:F", c.sheetName), &sheets.ValueRange{ + Values: [][]interface{}{ + { + post.ID, + post.URI, + post.Type, + post.Content, + post.Source, + false, + }, + }, + }).ValueInputOption("USER_ENTERED").Do(); err != nil { return fmt.Errorf("unable to append data to sheet: %v", err) } log.Println("Row successfully appended.") + return nil } diff --git a/bot/pkg/mastodon/mastodon.go b/bot/pkg/mastodon/mastodon.go index ca9857a..7ab705e 100644 --- a/bot/pkg/mastodon/mastodon.go +++ b/bot/pkg/mastodon/mastodon.go @@ -43,7 +43,7 @@ func WithConfig(cfg environment.Config) Option { } } -func NewClient(ch chan interface{}, gsheetsClient *gsheets.Client, options ...Option) (*Client, error) { +func NewClient(gsheetsClient *gsheets.Client, options ...Option) (*Client, error) { var cfg config for _, opt := range options { @@ -61,11 +61,20 @@ func NewClient(ch chan interface{}, gsheetsClient *gsheets.Client, options ...Op AccessToken: cfg.accessToken, }), gsheetsClient: gsheetsClient, - writeChannel: ch, + writeChannel: make(chan interface{}), }, nil } -func (c *Client) Run(ctx context.Context, cancel context.CancelFunc, errs chan error) { +func (c *Client) Run(ctx context.Context, errs chan error) { + go c.read(ctx, errs) + go c.write(ctx, errs) + + <-ctx.Done() + + fmt.Println("Context cancelled, shutting down Mastodon client...") +} + +func (c *Client) read(ctx context.Context, errs chan<- error) { events, err := c.mastodonClient.StreamingPublic(ctx, false) if err != nil { errs <- err @@ -103,14 +112,12 @@ func (c *Client) Run(ctx context.Context, cancel context.CancelFunc, errs chan e // How should we handle this? } case <-ctx.Done(): - fmt.Println("Context cancelled, shutting down Mastodon client...") return } } } -func (c *Client) Write(ctx context.Context) { - +func (c *Client) write(ctx context.Context, errs chan<- error) { for { select { case event := <-c.writeChannel: @@ -125,7 +132,6 @@ func (c *Client) Write(ctx context.Context) { // How should we handle this? } case <-ctx.Done(): - fmt.Println("Context cancelled, shutting down Mastodon client...") return } } @@ -135,6 +141,7 @@ func createPost(URI string, content string, postType post.NYTContentType) (*post if URI == "" || content == "" { return nil, fmt.Errorf("empty content or uri. Content: %s, URI: %s", URI, content) } + post := post.Post{ ID: URI, URI: URI, @@ -142,6 +149,7 @@ func createPost(URI string, content string, postType post.NYTContentType) (*post Type: postType, Source: post.Mastodon, } + return &post, nil } @@ -190,6 +198,7 @@ func findURLs(s string) string { if a.Key == "href" { url = a.Val } + if a.Key == "class" { class = a.Val } @@ -210,6 +219,7 @@ func findURLs(s string) string { } extractURL(doc, &buf) + return buf.String() } @@ -226,6 +236,7 @@ func parseURLs(urls string) bool { } newsRE := regexp.MustCompile(`(?i)nytimes\.com`) + if newsRE.MatchString(u) { return true }