|
|
hace 1 semana | |
|---|---|---|
| .. | ||
| README.md | hace 1 semana | |
| handler.go | hace 1 semana | |
| item.go | hace 5 meses | |
| item_test.go | hace 11 meses | |
| middleware.go | hace 1 semana | |
| parameters.go | hace 8 meses | |
| prefix_suffix.go | hace 8 meses | |
| request_modifier.go | hace 1 mes | |
| response.go | hace 2 semanas | |
| stream.go | hace 8 meses | |
This document describes the Google Reader compatible API implemented by the internal/googlereader package in this repository.
Miniflux implements a compatibility subset intended for existing Google Reader clients. It is not a full reimplementation of the historical Google Reader API, and several behaviors are intentionally narrower or implementation-specific.
BASE_URL/accounts/ClientLoginBASE_URL/reader/api/0BASE_URL includes the Miniflux root URL and any configured BasePathClientLogin: plain text by default, JSON when output=jsonOKGoogle Reader compatibility is configured per user from the Miniflux integrations page.
Google Reader API must be enabledGoogle Reader Username must be unique across all Miniflux usersGoogle Reader Password is stored as a bcrypt hashThe Google Reader username and password are separate integration credentials. They are not the Miniflux account password.
POST /accounts/ClientLoginThis endpoint exchanges the configured Google Reader username and password for an auth token.
Form parameters:
Email: Google Reader usernamePasswd: Google Reader passwordoutput: optional, set to json for a JSON responseSuccessful responses:
output=json: JSONExample plain-text response:
SID=readeruser/0123456789abcdef...
LSID=readeruser/0123456789abcdef...
Auth=readeruser/0123456789abcdef...
Example JSON response:
{
"SID": "readeruser/0123456789abcdef...",
"LSID": "readeruser/0123456789abcdef...",
"Auth": "readeruser/0123456789abcdef..."
}
On authentication failure, ClientLogin returns HTTP 401 with the normal JSON error body:
{
"error_message": "access unauthorized"
}
The token format is:
<googlereader_username>/<hex_digest>
The digest is generated server-side from:
Specifically, the code computes an HMAC-SHA256 digest of an empty message using the key:
googlereader_username + bcrypt_hash
Because the bcrypt hash is only known to the server, clients should not try to precompute the token. Use ClientLogin or GET /reader/api/0/token.
Miniflux uses different auth mechanisms for GET and POST requests:
GET requests must send the header Authorization: GoogleLogin auth=<token>POST requests are authenticated with T=<token> read from the parsed form valuesNotes:
GoogleLoginauthPOST, T may come from the URL query or the form body because the server reads merged form valuesPOST requests do not accept the token from the Authorization headerGET requests do not accept the token from the query stringGET /reader/api/0/tokenThis endpoint requires normal GET authentication and returns the same token as plain text.
Many Google Reader clients use this as the edit token for subsequent write requests. In Miniflux, the edit token and auth token are the same value.
/reader/api/0/*When API authentication fails under /reader/api/0, Miniflux returns:
401X-Reader-Google-Bad-Token: truetext/plain; charset=utf-8UnauthorizedThis is different from ClientLogin, which returns a JSON 401.
The implementation recognizes these stream forms:
user/-/state/com.google/readuser/-/state/com.google/starreduser/-/state/com.google/reading-listuser/-/state/com.google/kept-unreaduser/-/state/com.google/broadcastuser/-/state/com.google/broadcast-friendsuser/-/state/com.google/likeuser/<user_id>/state/com.google/...user/-/label/<name>user/<user_id>/label/<name>feed/<value>Important feed stream difference:
feed/<numeric_feed_id>subscription/edit with ac=subscribe expects feed/<absolute_feed_url>subscription/edit with ac=edit or ac=unsubscribe expects feed/<numeric_feed_id>So feed/<...> is not a single stable identifier format across all endpoints.
edit-tag and stream/items/contents accept repeated i parameters in all of these formats:
tag:google.com,2005:reader/item/00000000148b9369tag:google.com,2005:reader/item/2f2000000000000048c12345Responses use different forms depending on endpoint:
stream/items/ids returns decimal IDs as stringsstream/items/contents returns long-form Google Reader item IDsJSON errors use this shape:
{
"error_message": "..."
}
Plain-text success responses from write endpoints are usually:
OK
Most POST handlers call ParseForm() and read from r.Form, so parameters may be supplied either in the query string or in a standard form body.
Important exception:
POST /reader/api/0/edit-tag reads a and r from r.PostForm, so those tag lists must come from the request bodyBecause GET auth comes only from the Authorization header, query parameters never authenticate GET requests even when other parameters are read from the query string.
GET /reader/api/0/user-infoReturns JSON only. No output=json parameter is required.
Response fields:
userId: Miniflux user ID as a stringuserName: Miniflux usernameuserProfileId: same value as userIduserEmail: same value as userNameExample:
{
"userId": "1",
"userName": "demo",
"userProfileId": "1",
"userEmail": "demo"
}
GET /reader/api/0/tag/list?output=jsonReturns the starred state and user labels.
Notes:
output=json is requiredread and reading-list are not listed hereResponse shape:
{
"tags": [
{
"id": "user/1/state/com.google/starred"
},
{
"id": "user/1/label/Tech",
"label": "Tech",
"type": "folder"
}
]
}
GET /reader/api/0/subscription/list?output=jsonReturns the user's feeds.
Notes:
output=json is requiredfeed/42categories always contains the Miniflux category as a Google Reader folderResponse shape:
{
"subscriptions": [
{
"id": "feed/42",
"title": "Example Feed",
"categories": [
{
"id": "user/1/label/Tech",
"label": "Tech",
"type": "folder"
}
],
"url": "https://example.org/feed.xml",
"htmlUrl": "https://example.org/",
"iconUrl": "https://miniflux.example.com/icon/..."
}
]
}
POST /reader/api/0/subscription/quickaddSubscribes to the first discovered feed for the given absolute URL.
Form parameters:
T: auth tokenquickadd: absolute URLResponse shape when a feed is found:
{
"numResults": 1,
"query": "https://example.org/feed.xml",
"streamId": "feed/42",
"streamName": "Example Feed"
}
Response shape when no feed is found:
{
"numResults": 0
}
Notes:
POST /reader/api/0/subscription/editEdits subscriptions. Successful requests return plain text OK.
Form parameters:
T: auth tokenac: actions: repeated stream IDa: optional destination label streamt: optional titleSupported actions:
ac=subscribeac=unsubscribeac=editBehavior by action:
subscribe
s value is useds must be feed/<absolute_feed_url>a, when present, must be a label streamt, when present, becomes the feed title after creationunsubscribe
s must be feed/<numeric_feed_id>edit
s value is useds must be feed/<numeric_feed_id>t renames the feeda moves the feed to a label, and must be a label streamNotable limitations:
subscribe, edit, and unsubscribe do not share the same feed ID formatPOST /reader/api/0/rename-tagRenames a label. Successful requests return plain text OK.
Form parameters:
T: auth tokens: source label streamdest: destination label streamRules:
s and dest must be label streams404POST /reader/api/0/disable-tagDeletes one or more labels and reassigns affected feeds to the user's first remaining category.
Form parameters:
T: auth tokens: repeated label streamRules:
Successful requests return plain text OK.
POST /reader/api/0/edit-tagMarks entries read or unread and starred or unstarred.
Form parameters:
T: auth tokeni: repeated item IDa: repeated tag stream to addr: repeated tag stream to removeSupported tag semantics:
user/.../state/com.google/read: mark readuser/.../state/com.google/read: mark unreaduser/.../state/com.google/kept-unread: mark unreaduser/.../state/com.google/kept-unread: mark readuser/.../state/com.google/starred: staruser/.../state/com.google/starred: unstarSpecial cases:
read and kept-unread cannot be combined in conflicting ways in the same requeststarred cannot be present in both add and removebroadcast and like are recognized but ignoredSuccessful requests return plain text OK.
GET /reader/api/0/stream/items/ids?output=jsonReturns item IDs for one stream.
Required query parameters:
output=jsons=<stream_id>Optional query parameters:
n: maximum number of items to returnc: numeric offset continuation tokenr: sort direction, o for ascending, anything else for descendingot: only items published after this Unix timestamp in secondsnt: only items published before this Unix timestamp in secondsxt: repeated exclude target streamit: repeated filter target stream, parsed but currently ignoredSupported s values:
user/.../state/com.google/reading-listuser/.../state/com.google/starreduser/.../state/com.google/readfeed/<numeric_feed_id>Notes:
s value is expectedxt contains the read stream, reading-list and feed/<id> behave as unread-only queriesn is omitted, the query is effectively unboundedcontinuation is a numeric offset encoded as a JSON string, not an opaque tokenResponse shape:
{
"itemRefs": [
{
"id": "12345"
},
{
"id": "12344"
}
],
"continuation": "2"
}
POST /reader/api/0/stream/items/contentsReturns content for specific items.
Required parameters:
T: auth tokenoutput=jsoni: repeated item IDOptional query parameters:
r: sort direction, o for ascending, anything else for descendingImplementation notes:
POST onlyT, output, and i are read from merged form values, so they may be supplied in the query string or the form bodyResponse shape:
{
"direction": "ltr",
"id": "user/-/state/com.google/reading-list",
"title": "Reading List",
"self": [
{
"href": "https://miniflux.example.com/reader/api/0/stream/items/contents"
}
],
"updated": 1710000000,
"author": "demo",
"items": [
{
"id": "tag:google.com,2005:reader/item/00000000148b9369",
"categories": [
"user/1/state/com.google/reading-list",
"user/1/label/Tech",
"user/1/state/com.google/starred"
],
"title": "Example entry",
"crawlTimeMsec": "1710000000123",
"timestampUsec": "1710000000123456",
"published": 1710000000,
"updated": 1710000300,
"author": "Author",
"alternate": [
{
"href": "https://example.org/post",
"type": "text/html"
}
],
"summary": {
"direction": "ltr",
"content": "<p>Content</p>"
},
"content": {
"direction": "ltr",
"content": "<p>Content</p>"
},
"origin": {
"streamId": "feed/42",
"title": "Example Feed",
"htmlUrl": "https://example.org/"
},
"enclosure": [],
"canonical": [
{
"href": "https://example.org/post"
}
]
}
]
}
Notes:
id and title are hard-coded as the reading listsummary.content and content.content both contain the rewritten entry contentPOST /reader/api/0/mark-all-as-readMarks items as read before a timestamp. Successful requests return plain text OK.
Form parameters:
T: auth tokens: stream IDts: optional timestampSupported s values:
feed/<numeric_feed_id>user/.../label/<name>user/.../state/com.google/reading-listTimestamp handling:
ts has at least 16 digits, it is interpreted as microseconds since the Unix epochts is omitted, Miniflux uses the current server timeNotes:
ts are marked as readOKAny other GET or POST path under /reader/api/0/ is caught by the fallback handler and returns:
[]
with HTTP 200.
These differences are important for client authors:
ac=subscribe expects feed/<absolute_feed_url>stream/items/ids returns decimal entry IDs, while stream/items/contents returns long-form Google Reader item IDsc as a numeric SQL offset, not an opaque continuation tokenit filter targets are parsed but currently ignoredtag/list returns only starred and user labels/reader/api/0/* return plain text 401 Unauthorized, not JSON/reader/api/0/* endpoints return [] with 200, not 404