From 5f82a9e33985c5459ce5a53119cfb43e8dea3be7 Mon Sep 17 00:00:00 2001 From: icefed Date: Wed, 13 Sep 2023 22:39:04 +0800 Subject: [PATCH] add fever doc & fix fever issues --- readme.md | 21 +++++++++++++++++++++ src/server/fever.go | 18 +++++++++++++----- src/storage/item.go | 22 +++++++++++++++++++++- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index a731180..7e204b1 100644 --- a/readme.md +++ b/readme.md @@ -30,6 +30,27 @@ and run [the script](etc/install-linux.sh). For self-hosting, see `yarr -h` for auth, tls & server configuration flags. For building from source code, see [build.md](build.md) +## Fever API support + +Fever API is a kind of RSS HTTP API interface, because the Fever API definition is not very clear, so the implementation of Fever server and Client may have some compatibility problems. + +The Fever API implemented by Yarr is based on the Fever API spec: https://github.com/DigitalDJ/tinytinyrss-fever-plugin/blob/master/fever-api.md. + +Here are some Apps that have been tested to work with yarr. Feel free to test other Clients/Apps and update the list here. + +> Different apps support different URL/Address formats. Please note whether the URL entered has `http://` scheme and `/` suffix. + +| App | Platforms | Config Server URL | +|:------------------------------------------------------------------------- | ---------------- |:--------------------------------------------------- | +| [Reeder](https://reederapp.com/) | MacOS
iOS | 127.0.0.1:7070/fever
http://127.0.0.1:7070/fever | +| [ReadKit](https://readkit.app/) | MacOS
iOS | http://127.0.0.1:7070/fever | +| [Fluent Reader](https://github.com/yang991178/fluent-reader) | MacOS
Windows | http://127.0.0.1:7070/fever/ | +| [Unread](https://apps.apple.com/us/app/unread-an-rss-reader/id1363637349) | iOS | http://127.0.0.1:7070/fever | +| [Fiery Feeds](https://voidstern.net/fiery-feeds) | MacOS
iOS | http://127.0.0.1:7070/fever | + + +If you are having trouble using Fever, please open an issue and @icefed, thanks. + ## credits [Feather](http://feathericons.com/) for icons. diff --git a/src/server/fever.go b/src/server/fever.go index 0f17a1a..78ffb9b 100644 --- a/src/server/fever.go +++ b/src/server/fever.go @@ -54,7 +54,7 @@ type FeverFavicon struct { } func writeFeverJSON(c *router.Context, data map[string]interface{}, lastRefreshed int64) { - data["api_version"] = 1 + data["api_version"] = 3 data["auth"] = 1 data["last_refreshed_on_time"] = lastRefreshed c.JSON(http.StatusOK, data) @@ -77,9 +77,10 @@ func getLastRefreshedOnTime(httpStates map[int64]storage.HTTPState) int64 { func (s *Server) feverAuth(c *router.Context) bool { if s.Username != "" && s.Password != "" { apiKey := c.Req.FormValue("api_key") + apiKey = strings.ToLower(apiKey) md5HashValue := md5.Sum([]byte(fmt.Sprintf("%s:%s", s.Username, s.Password))) hexMD5HashValue := fmt.Sprintf("%x", md5HashValue[:]) - if auth.StringsEqual(apiKey, hexMD5HashValue) { + if !auth.StringsEqual(apiKey, hexMD5HashValue) { return false } } @@ -97,7 +98,7 @@ func (s *Server) handleFever(c *router.Context) { c.Req.ParseForm() if !s.feverAuth(c) { c.JSON(http.StatusOK, map[string]interface{}{ - "api_version": 1, + "api_version": 3, "auth": 0, "last_refreshed_on_time": 0, }) @@ -123,7 +124,7 @@ func (s *Server) handleFever(c *router.Context) { s.feverMarkHandler(c) default: c.JSON(http.StatusOK, map[string]interface{}{ - "api_version": 1, + "api_version": 3, "auth": 1, "last_refreshed_on_time": getLastRefreshedOnTime(s.db.ListHTTPStates()), }) @@ -277,8 +278,11 @@ func (s *Server) feverItemsHandler(c *router.Context) { } } + totalItems := s.db.CountItems(storage.ItemFilter{}) + writeFeverJSON(c, map[string]interface{}{ - "items": feverItems, + "items": feverItems, + "total_items": totalItems, }, getLastRefreshedOnTime(s.db.ListHTTPStates())) } @@ -382,4 +386,8 @@ func (s *Server) feverMarkHandler(c *router.Context) { c.Out.WriteHeader(http.StatusBadRequest) return } + c.JSON(http.StatusOK, map[string]interface{}{ + "api_version": 3, + "auth": 1, + }) } diff --git a/src/storage/item.go b/src/storage/item.go index 033878a..d1b8c8f 100644 --- a/src/storage/item.go +++ b/src/storage/item.go @@ -177,6 +177,23 @@ func listQueryPredicate(filter ItemFilter, newestFirst bool) (string, []interfac return predicate, args } +func (s *Storage) CountItems(filter ItemFilter) int { + predicate, args := listQueryPredicate(filter, false) + + var count int + query := fmt.Sprintf(` + select count(*) + from items + where %s + `, predicate) + err := s.db.QueryRow(query, args...).Scan(&count) + if err != nil { + log.Print(err) + return 0 + } + return count +} + func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool, withContent bool) []Item { predicate, args := listQueryPredicate(filter, newestFirst) result := make([]Item, 0, 0) @@ -185,9 +202,12 @@ func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool, with if !newestFirst { order = "date asc, id asc" } - if filter.IDs != nil || filter.SinceID != nil || filter.MaxID != nil { + if filter.IDs != nil || filter.SinceID != nil { order = "i.id asc" } + if filter.MaxID != nil { + order = "i.id desc" + } selectCols := "i.id, i.guid, i.feed_id, i.title, i.link, i.date, i.status, i.image, i.podcast_url" if withContent {