mirror of
https://github.com/nkanaev/yarr.git
synced 2025-05-24 00:33:14 +00:00
add fever doc & fix fever issues
This commit is contained in:
parent
3278ba4eac
commit
5f82a9e339
21
readme.md
21
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 self-hosting, see `yarr -h` for auth, tls & server configuration flags.
|
||||||
For building from source code, see [build.md](build.md)
|
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<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.
|
||||||
|
|
||||||
## credits
|
## credits
|
||||||
|
|
||||||
[Feather](http://feathericons.com/) for icons.
|
[Feather](http://feathericons.com/) for icons.
|
||||||
|
@ -54,7 +54,7 @@ type FeverFavicon struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeFeverJSON(c *router.Context, data map[string]interface{}, lastRefreshed int64) {
|
func writeFeverJSON(c *router.Context, data map[string]interface{}, lastRefreshed int64) {
|
||||||
data["api_version"] = 1
|
data["api_version"] = 3
|
||||||
data["auth"] = 1
|
data["auth"] = 1
|
||||||
data["last_refreshed_on_time"] = lastRefreshed
|
data["last_refreshed_on_time"] = lastRefreshed
|
||||||
c.JSON(http.StatusOK, data)
|
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 {
|
func (s *Server) feverAuth(c *router.Context) bool {
|
||||||
if s.Username != "" && s.Password != "" {
|
if s.Username != "" && s.Password != "" {
|
||||||
apiKey := c.Req.FormValue("api_key")
|
apiKey := c.Req.FormValue("api_key")
|
||||||
|
apiKey = strings.ToLower(apiKey)
|
||||||
md5HashValue := md5.Sum([]byte(fmt.Sprintf("%s:%s", s.Username, s.Password)))
|
md5HashValue := md5.Sum([]byte(fmt.Sprintf("%s:%s", s.Username, s.Password)))
|
||||||
hexMD5HashValue := fmt.Sprintf("%x", md5HashValue[:])
|
hexMD5HashValue := fmt.Sprintf("%x", md5HashValue[:])
|
||||||
if auth.StringsEqual(apiKey, hexMD5HashValue) {
|
if !auth.StringsEqual(apiKey, hexMD5HashValue) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +98,7 @@ func (s *Server) handleFever(c *router.Context) {
|
|||||||
c.Req.ParseForm()
|
c.Req.ParseForm()
|
||||||
if !s.feverAuth(c) {
|
if !s.feverAuth(c) {
|
||||||
c.JSON(http.StatusOK, map[string]interface{}{
|
c.JSON(http.StatusOK, map[string]interface{}{
|
||||||
"api_version": 1,
|
"api_version": 3,
|
||||||
"auth": 0,
|
"auth": 0,
|
||||||
"last_refreshed_on_time": 0,
|
"last_refreshed_on_time": 0,
|
||||||
})
|
})
|
||||||
@ -123,7 +124,7 @@ func (s *Server) handleFever(c *router.Context) {
|
|||||||
s.feverMarkHandler(c)
|
s.feverMarkHandler(c)
|
||||||
default:
|
default:
|
||||||
c.JSON(http.StatusOK, map[string]interface{}{
|
c.JSON(http.StatusOK, map[string]interface{}{
|
||||||
"api_version": 1,
|
"api_version": 3,
|
||||||
"auth": 1,
|
"auth": 1,
|
||||||
"last_refreshed_on_time": getLastRefreshedOnTime(s.db.ListHTTPStates()),
|
"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{}{
|
writeFeverJSON(c, map[string]interface{}{
|
||||||
"items": feverItems,
|
"items": feverItems,
|
||||||
|
"total_items": totalItems,
|
||||||
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,4 +386,8 @@ func (s *Server) feverMarkHandler(c *router.Context) {
|
|||||||
c.Out.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.JSON(http.StatusOK, map[string]interface{}{
|
||||||
|
"api_version": 3,
|
||||||
|
"auth": 1,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,23 @@ func listQueryPredicate(filter ItemFilter, newestFirst bool) (string, []interfac
|
|||||||
return predicate, args
|
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 {
|
func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool, withContent bool) []Item {
|
||||||
predicate, args := listQueryPredicate(filter, newestFirst)
|
predicate, args := listQueryPredicate(filter, newestFirst)
|
||||||
result := make([]Item, 0, 0)
|
result := make([]Item, 0, 0)
|
||||||
@ -185,9 +202,12 @@ func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool, with
|
|||||||
if !newestFirst {
|
if !newestFirst {
|
||||||
order = "date asc, id asc"
|
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"
|
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"
|
selectCols := "i.id, i.guid, i.feed_id, i.title, i.link, i.date, i.status, i.image, i.podcast_url"
|
||||||
if withContent {
|
if withContent {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user