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 {