Compare commits

...

3 Commits

Author SHA1 Message Date
nkanaev
c76ff26bd6
Update readme.md 2023-09-14 13:44:35 +01:00
icefed
50f8648f64 update readme 2023-09-14 13:42:57 +01:00
icefed
5f82a9e339 add fever doc & fix fever issues 2023-09-14 13:42:57 +01:00
4 changed files with 55 additions and 6 deletions

19
fever.md Normal file
View File

@ -0,0 +1,19 @@
# 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<br>iOS | 127.0.0.1:7070/fever<br>http://127.0.0.1:7070/fever |
| [ReadKit](https://readkit.app/) | MacOS<br>iOS | http://127.0.0.1:7070/fever |
| [Fluent Reader](https://github.com/yang991178/fluent-reader) | MacOS<br>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<br>iOS | http://127.0.0.1:7070/fever |
If you are having trouble using Fever, please open an issue and @icefed, thanks.

View File

@ -30,6 +30,8 @@ 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)
For Fever API support, see [fever.md](fever.md).
## credits
[Feather](http://feathericons.com/) for icons.

View File

@ -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,
})
}

View File

@ -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 {