diff --git a/README.md b/README.md index 7848e8b..eb32131 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ freed add "https://feed.url" Lists all currently stored feed with their metadata. ```sh -freed ls +freed list # or `freed ls` ``` ### Show a feed entry for the day diff --git a/cmd/root.go b/cmd/root.go index 1ce4f3e..76a670f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -46,11 +46,11 @@ var ( Short: "A f[r]eed aggregator with an intentionally reduced discovery mechanism.", Long: `A f[r]eed aggregator with an intentionally reduced discovery mechanism. -f[r]eed aims to handle all sort of different inputs that emit recurring content. This includes: RSS feeds, youtube channels, and more. If there is a type of input that you wish to be handled (or you find a bug), open an issue at https://github.com/dennisschoepf/freed/issues. +f[r]eed aims to handle all sort of different inputs that emit recurring content. This includes: RSS feeds, youtube channels, and more. If there is a type of input that you wish to be handled (or you find a bug), file a issue at https://github.com/dennisschoepf/freed/issues. Where f[r]eed is different to other feed aggregators or bookmarking services is in its discovery mechanism. Due to the overwhelming amount of content created every day, the constant stream of input can be overwhelming. That is why only a set amount of new content from your input feeds is shown to you when discovering new items. The amount is going to be configurable in the future. -The application also includes a set of recommended "Small Web" and topic-specific curated feeds. These are also available through the configuration. +The application will also include a set of recommended "Small Web" and topic-specific curated feeds. `, PersistentPreRun: func(cmd *cobra.Command, args []string) { initDB() diff --git a/cmd/today.go b/cmd/today.go index a75e69a..6070d60 100644 --- a/cmd/today.go +++ b/cmd/today.go @@ -1,5 +1,5 @@ /* -Copyright © 2024 Dennis Schoepf +Copyright © 2025 Dennis Schoepf This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,26 +17,42 @@ along with this program. If not, see . package cmd import ( - "fmt" + "freed/internal" + "github.com/pterm/pterm" "github.com/spf13/cobra" ) // todayCmd represents the today command var todayCmd = &cobra.Command{ - Use: "today", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Use: "today", + Aliases: []string{"day", "t"}, + Short: "Shows an item for the day", + Long: `Shows an item from your stored feeds. Only allows for a set amount of items to be read in a day to discourage doom-scrolling.`, + Example: `freed today`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("today called") + url, err := internal.GetArticleUrlForToday() + + if err != nil { + pterm.Error.Printf("Could not get article for today: %v\n", err) + return + } + + title := pterm.LightCyan("Today's article") + pterm.DefaultBox.WithPadding(2).WithTitle(title).Println(url) }, } func init() { rootCmd.AddCommand(todayCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // todayCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // todayCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/internal/article.go b/internal/article.go new file mode 100644 index 0000000..8ed17f1 --- /dev/null +++ b/internal/article.go @@ -0,0 +1,30 @@ +package internal + +import ( + "fmt" + "freed/internal/database" +) + +func GetArticleUrlForToday() (string, error) { + count, err := database.CountArticlesReadToday() + + if err != nil { + return "", err + } + + if count > 0 { + return "", fmt.Errorf("Already reached maximum number of articles for the day. Come back tomorrow!") + } + + article, err := database.FindOneUnreadArticle() + + if err != nil { + return "", err + } + + if err := article.MarkAsRead(); err != nil { + return "", err + } + + return article.Url, nil +} diff --git a/internal/database/article.go b/internal/database/article.go index a679de8..4e3f9e2 100644 --- a/internal/database/article.go +++ b/internal/database/article.go @@ -5,6 +5,7 @@ import ( ) type Article struct { + ID string Name string Url string ReadAt *time.Time @@ -27,6 +28,20 @@ func (a Article) Insert() (int64, error) { return id, nil } +func (a Article) MarkAsRead() error { + result, err := db.Exec("UPDATE article SET readAt = datetime() WHERE id = ?;", a.ID) + + if err != nil { + return err + } + + if _, err := result.RowsAffected(); err != nil { + return err + } + + return nil +} + func InsertMultipleArticles(articles []Article) error { if len(articles) == 0 { return nil @@ -57,3 +72,29 @@ func InsertMultipleArticles(articles []Article) error { return tx.Commit() } + +func FindOneUnreadArticle() (*Article, error) { + var article Article + + row := db.QueryRow("SELECT * FROM article WHERE readAt IS NULL ORDER BY RANDOM() LIMIT 1") + err := row.Scan(&article.ID, &article.Name, &article.Url, &article.ReadAt, &article.FeedId) + + if err != nil { + return nil, err + } + + return &article, nil +} + +func CountArticlesReadToday() (int, error) { + var count int + + result := db.QueryRow("SELECT COUNT(*) FROM article WHERE date(readAt) = date()") + err := result.Scan(&count) + + if err != nil { + return -1, err + } + + return count, nil +}