handler.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package googlereader // import "miniflux.app/v2/internal/googlereader"
  4. import (
  5. "errors"
  6. "fmt"
  7. "log/slog"
  8. "net/http"
  9. "strconv"
  10. "time"
  11. "miniflux.app/v2/internal/config"
  12. "miniflux.app/v2/internal/http/request"
  13. "miniflux.app/v2/internal/http/response"
  14. "miniflux.app/v2/internal/integration"
  15. "miniflux.app/v2/internal/mediaproxy"
  16. "miniflux.app/v2/internal/model"
  17. "miniflux.app/v2/internal/proxyrotator"
  18. "miniflux.app/v2/internal/reader/fetcher"
  19. mff "miniflux.app/v2/internal/reader/handler"
  20. mfs "miniflux.app/v2/internal/reader/subscription"
  21. "miniflux.app/v2/internal/storage"
  22. "miniflux.app/v2/internal/urllib"
  23. "miniflux.app/v2/internal/validator"
  24. )
  25. var (
  26. errEmptyFeedTitle = errors.New("googlereader: empty feed title")
  27. errFeedNotFound = errors.New("googlereader: feed not found")
  28. errCategoryNotFound = errors.New("googlereader: category not found")
  29. errSimultaneously = fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", keptUnreadStreamSuffix, readStreamSuffix)
  30. )
  31. // NewHandler returns an http.Handler that handles Google Reader API calls.
  32. // The returned handler expects the base path to be stripped from the request URL.
  33. func NewHandler(store *storage.Storage) http.Handler {
  34. h := &greaderHandler{
  35. store: store,
  36. }
  37. authMiddleware := newAuthMiddleware(store)
  38. withApiKeyAuth := func(fn http.HandlerFunc) http.Handler {
  39. return authMiddleware.validateApiKey(fn)
  40. }
  41. mux := http.NewServeMux()
  42. mux.HandleFunc("POST /accounts/ClientLogin", h.clientLoginHandler)
  43. mux.Handle("GET /reader/api/0/token", withApiKeyAuth(h.tokenHandler))
  44. mux.Handle("POST /reader/api/0/edit-tag", withApiKeyAuth(h.editTagHandler))
  45. mux.Handle("POST /reader/api/0/rename-tag", withApiKeyAuth(h.renameTagHandler))
  46. mux.Handle("POST /reader/api/0/disable-tag", withApiKeyAuth(h.disableTagHandler))
  47. mux.Handle("GET /reader/api/0/tag/list", withApiKeyAuth(h.tagListHandler))
  48. mux.Handle("GET /reader/api/0/user-info", withApiKeyAuth(h.userInfoHandler))
  49. mux.Handle("GET /reader/api/0/subscription/list", withApiKeyAuth(h.subscriptionListHandler))
  50. mux.Handle("POST /reader/api/0/subscription/edit", withApiKeyAuth(h.editSubscriptionHandler))
  51. mux.Handle("POST /reader/api/0/subscription/quickadd", withApiKeyAuth(h.quickAddHandler))
  52. mux.Handle("GET /reader/api/0/stream/items/ids", withApiKeyAuth(h.streamItemIDsHandler))
  53. mux.Handle("POST /reader/api/0/stream/items/contents", withApiKeyAuth(h.streamItemContentsHandler))
  54. mux.Handle("POST /reader/api/0/mark-all-as-read", withApiKeyAuth(h.markAllAsReadHandler))
  55. mux.Handle("GET /reader/api/0/", withApiKeyAuth(h.fallbackHandler))
  56. mux.Handle("POST /reader/api/0/", withApiKeyAuth(h.fallbackHandler))
  57. return mux
  58. }
  59. type greaderHandler struct {
  60. store *storage.Storage
  61. }
  62. func (h *greaderHandler) clientLoginHandler(w http.ResponseWriter, r *http.Request) {
  63. clientIP := request.ClientIP(r)
  64. slog.Debug("[GoogleReader] Handle /accounts/ClientLogin",
  65. slog.String("handler", "clientLoginHandler"),
  66. slog.String("client_ip", clientIP),
  67. slog.String("user_agent", r.UserAgent()),
  68. )
  69. if err := r.ParseForm(); err != nil {
  70. slog.Warn("[GoogleReader] Could not parse request form data",
  71. slog.Bool("authentication_failed", true),
  72. slog.String("client_ip", clientIP),
  73. slog.String("user_agent", r.UserAgent()),
  74. slog.Any("error", err),
  75. )
  76. response.JSONUnauthorized(w, r)
  77. return
  78. }
  79. username := r.Form.Get("Email")
  80. password := r.Form.Get("Passwd")
  81. output := r.Form.Get("output")
  82. if username == "" || password == "" {
  83. slog.Warn("[GoogleReader] Empty username or password",
  84. slog.Bool("authentication_failed", true),
  85. slog.String("client_ip", clientIP),
  86. slog.String("user_agent", r.UserAgent()),
  87. )
  88. response.JSONUnauthorized(w, r)
  89. return
  90. }
  91. if err := h.store.GoogleReaderUserCheckPassword(username, password); err != nil {
  92. slog.Warn("[GoogleReader] Invalid username or password",
  93. slog.Bool("authentication_failed", true),
  94. slog.String("client_ip", clientIP),
  95. slog.String("user_agent", r.UserAgent()),
  96. slog.String("username", username),
  97. slog.Any("error", err),
  98. )
  99. response.JSONUnauthorized(w, r)
  100. return
  101. }
  102. slog.Info("[GoogleReader] User authenticated successfully",
  103. slog.Bool("authentication_successful", true),
  104. slog.String("client_ip", clientIP),
  105. slog.String("user_agent", r.UserAgent()),
  106. slog.String("username", username),
  107. )
  108. integration, err := h.store.GoogleReaderUserGetIntegration(username)
  109. if err != nil {
  110. response.JSONServerError(w, r, err)
  111. return
  112. }
  113. h.store.SetLastLogin(integration.UserID)
  114. token := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
  115. slog.Debug("[GoogleReader] Created token",
  116. slog.String("client_ip", clientIP),
  117. slog.String("user_agent", r.UserAgent()),
  118. slog.String("username", username),
  119. )
  120. result := loginResponse{SID: token, LSID: token, Auth: token}
  121. if output == "json" {
  122. response.JSON(w, r, result)
  123. return
  124. }
  125. response.Text(w, r, result.String())
  126. }
  127. func (h *greaderHandler) tokenHandler(w http.ResponseWriter, r *http.Request) {
  128. clientIP := request.ClientIP(r)
  129. slog.Debug("[GoogleReader] Handle /token",
  130. slog.String("handler", "tokenHandler"),
  131. slog.String("client_ip", clientIP),
  132. slog.String("user_agent", r.UserAgent()),
  133. )
  134. if !request.IsAuthenticated(r) {
  135. slog.Warn("[GoogleReader] User is not authenticated",
  136. slog.String("client_ip", clientIP),
  137. slog.String("user_agent", r.UserAgent()),
  138. )
  139. response.JSONUnauthorized(w, r)
  140. return
  141. }
  142. token := request.GoogleReaderToken(r)
  143. if token == "" {
  144. slog.Warn("[GoogleReader] User does not have token",
  145. slog.String("client_ip", clientIP),
  146. slog.String("user_agent", r.UserAgent()),
  147. slog.Int64("user_id", request.UserID(r)),
  148. )
  149. response.JSONUnauthorized(w, r)
  150. return
  151. }
  152. slog.Debug("[GoogleReader] Token handler",
  153. slog.String("client_ip", clientIP),
  154. slog.String("user_agent", r.UserAgent()),
  155. slog.Int64("user_id", request.UserID(r)),
  156. )
  157. response.Text(w, r, token)
  158. }
  159. func (h *greaderHandler) editTagHandler(w http.ResponseWriter, r *http.Request) {
  160. userID := request.UserID(r)
  161. clientIP := request.ClientIP(r)
  162. slog.Debug("[GoogleReader] Handle /edit-tag",
  163. slog.String("handler", "editTagHandler"),
  164. slog.String("client_ip", clientIP),
  165. slog.String("user_agent", r.UserAgent()),
  166. slog.Int64("user_id", userID),
  167. )
  168. if err := r.ParseForm(); err != nil {
  169. response.JSONServerError(w, r, err)
  170. return
  171. }
  172. addTags, err := getStreams(r.PostForm[paramTagsAdd], userID)
  173. if err != nil {
  174. response.JSONServerError(w, r, err)
  175. return
  176. }
  177. removeTags, err := getStreams(r.PostForm[paramTagsRemove], userID)
  178. if err != nil {
  179. response.JSONServerError(w, r, err)
  180. return
  181. }
  182. if len(addTags) == 0 && len(removeTags) == 0 {
  183. err = errors.New("googlreader: add or/and remove tags should be supplied")
  184. response.JSONServerError(w, r, err)
  185. return
  186. }
  187. tags, err := checkAndSimplifyTags(addTags, removeTags)
  188. if err != nil {
  189. response.JSONServerError(w, r, err)
  190. return
  191. }
  192. itemIDs, err := parseItemIDsFromRequest(r)
  193. if err != nil {
  194. response.JSONBadRequest(w, r, err)
  195. return
  196. }
  197. slog.Debug("[GoogleReader] Edited tags",
  198. slog.String("handler", "editTagHandler"),
  199. slog.String("client_ip", clientIP),
  200. slog.String("user_agent", r.UserAgent()),
  201. slog.Int64("user_id", userID),
  202. slog.Any("item_ids", itemIDs),
  203. slog.Any("tags", tags),
  204. )
  205. entries, err := h.store.NewEntryQueryBuilder(userID).
  206. WithEntryIDs(itemIDs...).
  207. GetEntries()
  208. if err != nil {
  209. response.JSONServerError(w, r, err)
  210. return
  211. }
  212. n := 0
  213. var readEntryIDs []int64
  214. var unreadEntryIDs []int64
  215. var starredEntryIDs []int64
  216. var unstarredEntryIDs []int64
  217. for _, entry := range entries {
  218. if read, exists := tags[ReadStream]; exists {
  219. if read && entry.Status == model.EntryStatusUnread {
  220. readEntryIDs = append(readEntryIDs, entry.ID)
  221. } else if !read && entry.Status == model.EntryStatusRead {
  222. unreadEntryIDs = append(unreadEntryIDs, entry.ID)
  223. }
  224. }
  225. if starred, exists := tags[StarredStream]; exists {
  226. if starred && !entry.Starred {
  227. starredEntryIDs = append(starredEntryIDs, entry.ID)
  228. // filter the original array
  229. entries[n] = entry
  230. n++
  231. } else if !starred && entry.Starred {
  232. unstarredEntryIDs = append(unstarredEntryIDs, entry.ID)
  233. }
  234. }
  235. }
  236. entries = entries[:n]
  237. if len(readEntryIDs) > 0 {
  238. err = h.store.SetEntriesStatus(userID, readEntryIDs, model.EntryStatusRead)
  239. if err != nil {
  240. response.JSONServerError(w, r, err)
  241. return
  242. }
  243. }
  244. if len(unreadEntryIDs) > 0 {
  245. err = h.store.SetEntriesStatus(userID, unreadEntryIDs, model.EntryStatusUnread)
  246. if err != nil {
  247. response.JSONServerError(w, r, err)
  248. return
  249. }
  250. }
  251. if len(unstarredEntryIDs) > 0 {
  252. err = h.store.SetEntriesStarredState(userID, unstarredEntryIDs, false)
  253. if err != nil {
  254. response.JSONServerError(w, r, err)
  255. return
  256. }
  257. }
  258. if len(starredEntryIDs) > 0 {
  259. err = h.store.SetEntriesStarredState(userID, starredEntryIDs, true)
  260. if err != nil {
  261. response.JSONServerError(w, r, err)
  262. return
  263. }
  264. }
  265. if len(entries) > 0 {
  266. settings, err := h.store.Integration(userID)
  267. if err != nil {
  268. response.JSONServerError(w, r, err)
  269. return
  270. }
  271. for _, entry := range entries {
  272. e := entry
  273. go func() {
  274. integration.SendEntry(e, settings)
  275. }()
  276. }
  277. }
  278. response.Text(w, r, "OK")
  279. }
  280. func (h *greaderHandler) quickAddHandler(w http.ResponseWriter, r *http.Request) {
  281. userID := request.UserID(r)
  282. clientIP := request.ClientIP(r)
  283. slog.Debug("[GoogleReader] Handle /subscription/quickadd",
  284. slog.String("handler", "quickAddHandler"),
  285. slog.String("client_ip", clientIP),
  286. slog.String("user_agent", r.UserAgent()),
  287. slog.Int64("user_id", userID),
  288. )
  289. err := r.ParseForm()
  290. if err != nil {
  291. response.JSONBadRequest(w, r, err)
  292. return
  293. }
  294. feedURL := r.Form.Get(paramQuickAdd)
  295. if !urllib.IsAbsoluteURL(feedURL) {
  296. response.JSONBadRequest(w, r, fmt.Errorf("googlereader: invalid URL: %s", feedURL))
  297. return
  298. }
  299. requestBuilder := fetcher.NewRequestBuilder()
  300. requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
  301. requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
  302. requestBuilder.WithUserAgent("", config.Opts.HTTPClientUserAgent())
  303. var rssBridgeURL string
  304. var rssBridgeToken string
  305. if intg, err := h.store.Integration(userID); err == nil && intg != nil && intg.RSSBridgeEnabled {
  306. rssBridgeURL = intg.RSSBridgeURL
  307. rssBridgeToken = intg.RSSBridgeToken
  308. }
  309. subscriptions, localizedError := mfs.NewSubscriptionFinder(requestBuilder).FindSubscriptions(feedURL, rssBridgeURL, rssBridgeToken)
  310. if localizedError != nil {
  311. response.JSONServerError(w, r, localizedError.Error())
  312. return
  313. }
  314. if len(subscriptions) == 0 {
  315. response.JSON(w, r, quickAddResponse{
  316. NumResults: 0,
  317. })
  318. return
  319. }
  320. toSubscribe := Stream{FeedStream, subscriptions[0].URL}
  321. category := Stream{NoStream, ""}
  322. newFeed, err := subscribe(toSubscribe, category, "", h.store, userID)
  323. if err != nil {
  324. response.JSONServerError(w, r, err)
  325. return
  326. }
  327. slog.Debug("[GoogleReader] Added a new feed",
  328. slog.String("handler", "quickAddHandler"),
  329. slog.String("client_ip", clientIP),
  330. slog.String("user_agent", r.UserAgent()),
  331. slog.Int64("user_id", userID),
  332. slog.String("feed_url", newFeed.FeedURL),
  333. )
  334. response.JSON(w, r, quickAddResponse{
  335. NumResults: 1,
  336. Query: newFeed.FeedURL,
  337. StreamID: feedPrefix + strconv.FormatInt(newFeed.ID, 10),
  338. StreamName: newFeed.Title,
  339. })
  340. }
  341. func getFeed(stream Stream, store *storage.Storage, userID int64) (*model.Feed, error) {
  342. feedID, err := strconv.ParseInt(stream.ID, 10, 64)
  343. if err != nil {
  344. return nil, err
  345. }
  346. return store.FeedByID(userID, feedID)
  347. }
  348. func getOrCreateCategory(streamCategory Stream, store *storage.Storage, userID int64) (*model.Category, error) {
  349. switch {
  350. case streamCategory.ID == "":
  351. return store.FirstCategory(userID)
  352. case store.CategoryTitleExists(userID, streamCategory.ID):
  353. return store.CategoryByTitle(userID, streamCategory.ID)
  354. default:
  355. return store.CreateCategory(userID, &model.CategoryCreationRequest{
  356. Title: streamCategory.ID,
  357. })
  358. }
  359. }
  360. func subscribe(newFeed Stream, category Stream, title string, store *storage.Storage, userID int64) (*model.Feed, error) {
  361. destCategory, err := getOrCreateCategory(category, store, userID)
  362. if err != nil {
  363. return nil, err
  364. }
  365. feedRequest := model.FeedCreationRequest{
  366. FeedURL: newFeed.ID,
  367. CategoryID: destCategory.ID,
  368. }
  369. verr := validator.ValidateFeedCreation(store, userID, &feedRequest)
  370. if verr != nil {
  371. return nil, verr.Error()
  372. }
  373. created, localizedError := mff.CreateFeed(store, userID, &feedRequest)
  374. if localizedError != nil {
  375. return nil, localizedError.Error()
  376. }
  377. if title != "" {
  378. feedModification := model.FeedModificationRequest{
  379. Title: &title,
  380. }
  381. feedModification.Patch(created)
  382. if err := store.UpdateFeed(created); err != nil {
  383. return nil, err
  384. }
  385. }
  386. return created, nil
  387. }
  388. func unsubscribe(streams []Stream, store *storage.Storage, userID int64) error {
  389. for _, stream := range streams {
  390. feedID, err := strconv.ParseInt(stream.ID, 10, 64)
  391. if err != nil {
  392. return err
  393. }
  394. err = store.RemoveFeed(userID, feedID)
  395. if err != nil {
  396. return err
  397. }
  398. }
  399. return nil
  400. }
  401. func rename(feedStream Stream, title string, store *storage.Storage, userID int64) error {
  402. slog.Debug("[GoogleReader] Renaming feed",
  403. slog.Int64("user_id", userID),
  404. slog.Any("feed_stream", feedStream),
  405. slog.String("new_title", title),
  406. )
  407. if title == "" {
  408. return errEmptyFeedTitle
  409. }
  410. feed, err := getFeed(feedStream, store, userID)
  411. if err != nil {
  412. return err
  413. }
  414. if feed == nil {
  415. return errFeedNotFound
  416. }
  417. feedModification := model.FeedModificationRequest{
  418. Title: &title,
  419. }
  420. feedModification.Patch(feed)
  421. return store.UpdateFeed(feed)
  422. }
  423. func move(feedStream Stream, labelStream Stream, store *storage.Storage, userID int64) error {
  424. slog.Debug("[GoogleReader] Moving feed",
  425. slog.Int64("user_id", userID),
  426. slog.Any("feed_stream", feedStream),
  427. slog.Any("label_stream", labelStream),
  428. )
  429. feed, err := getFeed(feedStream, store, userID)
  430. if err != nil {
  431. return err
  432. }
  433. if feed == nil {
  434. return errFeedNotFound
  435. }
  436. category, err := getOrCreateCategory(labelStream, store, userID)
  437. if err != nil {
  438. return err
  439. }
  440. if category == nil {
  441. return errCategoryNotFound
  442. }
  443. feedModification := model.FeedModificationRequest{
  444. CategoryID: &category.ID,
  445. }
  446. feedModification.Patch(feed)
  447. return store.UpdateFeed(feed)
  448. }
  449. func (h *greaderHandler) feedIconURL(f *model.Feed) string {
  450. if f.Icon != nil && f.Icon.ExternalIconID != "" {
  451. return config.Opts.BaseURL() + "/feed-icon/" + f.Icon.ExternalIconID
  452. }
  453. return ""
  454. }
  455. func (h *greaderHandler) editSubscriptionHandler(w http.ResponseWriter, r *http.Request) {
  456. userID := request.UserID(r)
  457. clientIP := request.ClientIP(r)
  458. slog.Debug("[GoogleReader] Handle /subscription/edit",
  459. slog.String("handler", "editSubscriptionHandler"),
  460. slog.String("client_ip", clientIP),
  461. slog.String("user_agent", r.UserAgent()),
  462. slog.Int64("user_id", userID),
  463. )
  464. if err := r.ParseForm(); err != nil {
  465. response.JSONBadRequest(w, r, err)
  466. return
  467. }
  468. streamIds, err := getStreams(r.Form[paramStreamID], userID)
  469. if err != nil || len(streamIds) == 0 {
  470. response.JSONBadRequest(w, r, errors.New("googlereader: no valid stream IDs provided"))
  471. return
  472. }
  473. newLabel, err := getStream(r.Form.Get(paramTagsAdd), userID)
  474. if err != nil {
  475. response.JSONBadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramTagsAdd))
  476. return
  477. }
  478. title := r.Form.Get(paramTitle)
  479. action := r.Form.Get(paramSubscribeAction)
  480. switch action {
  481. case "subscribe":
  482. _, err := subscribe(streamIds[0], newLabel, title, h.store, userID)
  483. if err != nil {
  484. response.JSONServerError(w, r, err)
  485. return
  486. }
  487. case "unsubscribe":
  488. err := unsubscribe(streamIds, h.store, userID)
  489. if err != nil {
  490. response.JSONServerError(w, r, err)
  491. return
  492. }
  493. case "edit":
  494. if title != "" {
  495. if err := rename(streamIds[0], title, h.store, userID); err != nil {
  496. if errors.Is(err, errFeedNotFound) || errors.Is(err, errEmptyFeedTitle) {
  497. response.JSONBadRequest(w, r, err)
  498. } else {
  499. response.JSONServerError(w, r, err)
  500. }
  501. return
  502. }
  503. }
  504. if r.Form.Has(paramTagsAdd) {
  505. if newLabel.Type != LabelStream {
  506. response.JSONBadRequest(w, r, errors.New("destination must be a label"))
  507. return
  508. }
  509. if err := move(streamIds[0], newLabel, h.store, userID); err != nil {
  510. if errors.Is(err, errFeedNotFound) || errors.Is(err, errCategoryNotFound) {
  511. response.JSONBadRequest(w, r, err)
  512. } else {
  513. response.JSONServerError(w, r, err)
  514. }
  515. return
  516. }
  517. }
  518. default:
  519. response.JSONBadRequest(w, r, fmt.Errorf("googlereader: unrecognized action %s", action))
  520. return
  521. }
  522. response.Text(w, r, "OK")
  523. }
  524. func (h *greaderHandler) streamItemContentsHandler(w http.ResponseWriter, r *http.Request) {
  525. userID := request.UserID(r)
  526. userName := request.UserName(r)
  527. clientIP := request.ClientIP(r)
  528. slog.Debug("[GoogleReader] Handle /stream/items/contents",
  529. slog.String("handler", "streamItemContentsHandler"),
  530. slog.String("client_ip", clientIP),
  531. slog.String("user_agent", r.UserAgent()),
  532. slog.Int64("user_id", userID),
  533. )
  534. if err := checkOutputFormat(r); err != nil {
  535. response.JSONBadRequest(w, r, err)
  536. return
  537. }
  538. err := r.ParseForm()
  539. if err != nil {
  540. response.JSONServerError(w, r, err)
  541. return
  542. }
  543. requestModifiers, err := parseStreamFilterFromRequest(r)
  544. if err != nil {
  545. response.JSONServerError(w, r, err)
  546. return
  547. }
  548. streamPrefix := fmt.Sprintf(userStreamPrefix, userID)
  549. userReadingList := streamPrefix + readingListStreamSuffix
  550. userRead := streamPrefix + readStreamSuffix
  551. userStarred := streamPrefix + starredStreamSuffix
  552. itemIDs, err := parseItemIDsFromRequest(r)
  553. if err != nil {
  554. response.JSONBadRequest(w, r, err)
  555. return
  556. }
  557. slog.Debug("[GoogleReader] Fetching item contents",
  558. slog.String("handler", "streamItemContentsHandler"),
  559. slog.String("client_ip", clientIP),
  560. slog.String("user_agent", r.UserAgent()),
  561. slog.Int64("user_id", userID),
  562. slog.Any("item_ids", itemIDs),
  563. )
  564. entries, err := h.store.NewEntryQueryBuilder(userID).
  565. WithEnclosures().
  566. WithEntryIDs(itemIDs...).
  567. WithSorting(model.DefaultSortingOrder, requestModifiers.SortDirection).
  568. GetEntries()
  569. if err != nil {
  570. response.JSONServerError(w, r, err)
  571. return
  572. }
  573. result := streamContentItemsResponse{
  574. Direction: "ltr",
  575. ID: "user/-/state/com.google/reading-list",
  576. Title: "Reading List",
  577. Updated: time.Now().Unix(),
  578. Self: []contentHREF{{
  579. HREF: config.Opts.BaseURL() + "/reader/api/0/stream/items/contents",
  580. }},
  581. Author: userName,
  582. Items: make([]contentItem, len(entries)),
  583. }
  584. labelPrefix := fmt.Sprintf(userLabelPrefix, userID)
  585. for i, entry := range entries {
  586. enclosures := make([]contentItemEnclosure, 0, len(entry.Enclosures))
  587. for _, enclosure := range entry.Enclosures {
  588. enclosures = append(enclosures, contentItemEnclosure{URL: enclosure.URL, Type: enclosure.MimeType})
  589. }
  590. categories := make([]string, 0, 4)
  591. categories = append(categories, userReadingList)
  592. if entry.Feed.Category.Title != "" {
  593. categories = append(categories, labelPrefix+entry.Feed.Category.Title)
  594. }
  595. if entry.Status == model.EntryStatusRead {
  596. categories = append(categories, userRead)
  597. }
  598. if entry.Starred {
  599. categories = append(categories, userStarred)
  600. }
  601. entry.Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(entry.Content)
  602. entry.Enclosures.ProxifyEnclosureURL(config.Opts.MediaProxyMode(), config.Opts.MediaProxyResourceTypes())
  603. result.Items[i] = contentItem{
  604. ID: convertEntryIDToLongFormItemID(entry.ID),
  605. Title: entry.Title,
  606. Author: entry.Author,
  607. TimestampUsec: strconv.FormatInt(entry.Date.UnixMicro(), 10),
  608. CrawlTimeMsec: strconv.FormatInt(entry.CreatedAt.UnixMilli(), 10),
  609. Published: entry.Date.Unix(),
  610. Updated: entry.ChangedAt.Unix(),
  611. Categories: categories,
  612. Canonical: []contentHREF{
  613. {
  614. HREF: entry.URL,
  615. },
  616. },
  617. Alternate: []contentHREFType{
  618. {
  619. HREF: entry.URL,
  620. Type: "text/html",
  621. },
  622. },
  623. Content: contentItemContent{
  624. Direction: "ltr",
  625. Content: entry.Content,
  626. },
  627. Summary: contentItemContent{
  628. Direction: "ltr",
  629. Content: entry.Content,
  630. },
  631. Origin: contentItemOrigin{
  632. StreamID: feedPrefix + strconv.FormatInt(entry.FeedID, 10),
  633. Title: entry.Feed.Title,
  634. HTMLUrl: entry.Feed.SiteURL,
  635. },
  636. Enclosure: enclosures,
  637. }
  638. }
  639. response.JSON(w, r, result)
  640. }
  641. func (h *greaderHandler) disableTagHandler(w http.ResponseWriter, r *http.Request) {
  642. userID := request.UserID(r)
  643. clientIP := request.ClientIP(r)
  644. slog.Debug("[GoogleReader] Handle /disable-tags",
  645. slog.String("handler", "disableTagHandler"),
  646. slog.String("client_ip", clientIP),
  647. slog.String("user_agent", r.UserAgent()),
  648. slog.Int64("user_id", userID),
  649. )
  650. err := r.ParseForm()
  651. if err != nil {
  652. response.JSONBadRequest(w, r, err)
  653. return
  654. }
  655. streams, err := getStreams(r.Form[paramStreamID], userID)
  656. if err != nil {
  657. response.JSONBadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramStreamID))
  658. return
  659. }
  660. titles := make([]string, len(streams))
  661. for i, stream := range streams {
  662. if stream.Type != LabelStream {
  663. response.JSONBadRequest(w, r, errors.New("googlereader: only labels are supported"))
  664. return
  665. }
  666. titles[i] = stream.ID
  667. }
  668. err = h.store.RemoveAndReplaceCategoriesByName(userID, titles)
  669. if err != nil {
  670. response.JSONServerError(w, r, err)
  671. return
  672. }
  673. response.Text(w, r, "OK")
  674. }
  675. func (h *greaderHandler) renameTagHandler(w http.ResponseWriter, r *http.Request) {
  676. userID := request.UserID(r)
  677. clientIP := request.ClientIP(r)
  678. slog.Debug("[GoogleReader] Handle /rename-tag",
  679. slog.String("handler", "renameTagHandler"),
  680. slog.String("client_ip", clientIP),
  681. slog.String("user_agent", r.UserAgent()),
  682. )
  683. err := r.ParseForm()
  684. if err != nil {
  685. response.JSONBadRequest(w, r, err)
  686. return
  687. }
  688. source, err := getStream(r.Form.Get(paramStreamID), userID)
  689. if err != nil {
  690. response.JSONBadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramStreamID))
  691. return
  692. }
  693. destination, err := getStream(r.Form.Get(paramDestination), userID)
  694. if err != nil {
  695. response.JSONBadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramDestination))
  696. return
  697. }
  698. if source.Type != LabelStream || destination.Type != LabelStream {
  699. response.JSONBadRequest(w, r, errors.New("googlereader: only labels supported"))
  700. return
  701. }
  702. if destination.ID == "" {
  703. response.JSONBadRequest(w, r, errors.New("googlereader: empty destination name"))
  704. return
  705. }
  706. category, err := h.store.CategoryByTitle(userID, source.ID)
  707. if err != nil {
  708. response.JSONServerError(w, r, err)
  709. return
  710. }
  711. if category == nil {
  712. response.JSONNotFound(w, r)
  713. return
  714. }
  715. categoryModificationRequest := model.CategoryModificationRequest{
  716. Title: new(destination.ID),
  717. }
  718. if validationError := validator.ValidateCategoryModification(h.store, userID, category.ID, &categoryModificationRequest); validationError != nil {
  719. response.JSONBadRequest(w, r, validationError.Error())
  720. return
  721. }
  722. categoryModificationRequest.Patch(category)
  723. if err := h.store.UpdateCategory(category); err != nil {
  724. response.JSONServerError(w, r, err)
  725. return
  726. }
  727. response.Text(w, r, "OK")
  728. }
  729. func (h *greaderHandler) tagListHandler(w http.ResponseWriter, r *http.Request) {
  730. userID := request.UserID(r)
  731. clientIP := request.ClientIP(r)
  732. slog.Debug("[GoogleReader] Handle /tags/list",
  733. slog.String("handler", "tagListHandler"),
  734. slog.String("client_ip", clientIP),
  735. slog.String("user_agent", r.UserAgent()),
  736. )
  737. if err := checkOutputFormat(r); err != nil {
  738. response.JSONBadRequest(w, r, err)
  739. return
  740. }
  741. var result tagsResponse
  742. categories, err := h.store.Categories(userID)
  743. if err != nil {
  744. response.JSONServerError(w, r, err)
  745. return
  746. }
  747. result.Tags = make([]subscriptionCategoryResponse, 0, 1+len(categories))
  748. result.Tags = append(result.Tags, subscriptionCategoryResponse{
  749. ID: fmt.Sprintf(userStreamPrefix, userID) + starredStreamSuffix,
  750. })
  751. labelPrefix := fmt.Sprintf(userLabelPrefix, userID)
  752. for _, category := range categories {
  753. result.Tags = append(result.Tags, subscriptionCategoryResponse{
  754. ID: labelPrefix + category.Title,
  755. Label: category.Title,
  756. Type: "folder",
  757. })
  758. }
  759. response.JSON(w, r, result)
  760. }
  761. func (h *greaderHandler) subscriptionListHandler(w http.ResponseWriter, r *http.Request) {
  762. userID := request.UserID(r)
  763. clientIP := request.ClientIP(r)
  764. slog.Debug("[GoogleReader] Handle /subscription/list",
  765. slog.String("handler", "subscriptionListHandler"),
  766. slog.String("client_ip", clientIP),
  767. slog.String("user_agent", r.UserAgent()),
  768. )
  769. if err := checkOutputFormat(r); err != nil {
  770. response.JSONBadRequest(w, r, err)
  771. return
  772. }
  773. var result subscriptionsResponse
  774. feeds, err := h.store.Feeds(userID)
  775. if err != nil {
  776. response.JSONServerError(w, r, err)
  777. return
  778. }
  779. labelPrefix := fmt.Sprintf(userLabelPrefix, userID)
  780. result.Subscriptions = make([]subscriptionResponse, 0, len(feeds))
  781. for _, feed := range feeds {
  782. result.Subscriptions = append(result.Subscriptions, subscriptionResponse{
  783. ID: feedPrefix + strconv.FormatInt(feed.ID, 10),
  784. Title: feed.Title,
  785. URL: feed.FeedURL,
  786. Categories: []subscriptionCategoryResponse{{labelPrefix + feed.Category.Title, feed.Category.Title, "folder"}},
  787. HTMLURL: feed.SiteURL,
  788. IconURL: h.feedIconURL(feed),
  789. })
  790. }
  791. response.JSON(w, r, result)
  792. }
  793. func (h *greaderHandler) fallbackHandler(w http.ResponseWriter, r *http.Request) {
  794. clientIP := request.ClientIP(r)
  795. slog.Debug("[GoogleReader] API endpoint not implemented yet",
  796. slog.Any("url", r.RequestURI),
  797. slog.String("client_ip", clientIP),
  798. slog.String("user_agent", r.UserAgent()),
  799. )
  800. response.JSON(w, r, []string{})
  801. }
  802. func (h *greaderHandler) userInfoHandler(w http.ResponseWriter, r *http.Request) {
  803. clientIP := request.ClientIP(r)
  804. slog.Debug("[GoogleReader] Handle /user-info",
  805. slog.String("handler", "userInfoHandler"),
  806. slog.String("client_ip", clientIP),
  807. slog.String("user_agent", r.UserAgent()),
  808. )
  809. user, err := h.store.UserByID(request.UserID(r))
  810. if err != nil {
  811. response.JSONServerError(w, r, err)
  812. return
  813. }
  814. if user == nil {
  815. response.JSONNotFound(w, r)
  816. return
  817. }
  818. userInfo := userInfoResponse{UserID: strconv.FormatInt(user.ID, 10), UserName: user.Username, UserProfileID: strconv.FormatInt(user.ID, 10), UserEmail: user.Username}
  819. response.JSON(w, r, userInfo)
  820. }
  821. func (h *greaderHandler) streamItemIDsHandler(w http.ResponseWriter, r *http.Request) {
  822. userID := request.UserID(r)
  823. clientIP := request.ClientIP(r)
  824. slog.Debug("[GoogleReader] Handle /stream/items/ids",
  825. slog.String("handler", "streamItemIDsHandler"),
  826. slog.String("client_ip", clientIP),
  827. slog.String("user_agent", r.UserAgent()),
  828. slog.Int64("user_id", userID),
  829. )
  830. if err := checkOutputFormat(r); err != nil {
  831. response.JSONBadRequest(w, r, err)
  832. return
  833. }
  834. rm, err := parseStreamFilterFromRequest(r)
  835. if err != nil {
  836. response.JSONServerError(w, r, err)
  837. return
  838. }
  839. slog.Debug("[GoogleReader] Request Modifiers",
  840. slog.String("handler", "streamItemIDsHandler"),
  841. slog.String("client_ip", clientIP),
  842. slog.String("user_agent", r.UserAgent()),
  843. slog.Any("modifiers", rm),
  844. )
  845. if len(rm.Streams) != 1 {
  846. response.JSONServerError(w, r, errors.New("googlereader: only one stream type expected"))
  847. return
  848. }
  849. switch rm.Streams[0].Type {
  850. case ReadingListStream:
  851. h.handleReadingListStreamHandler(w, r, rm)
  852. case StarredStream:
  853. h.handleStarredStreamHandler(w, r, rm)
  854. case ReadStream:
  855. h.handleReadStreamHandler(w, r, rm)
  856. case FeedStream:
  857. h.handleFeedStreamHandler(w, r, rm)
  858. default:
  859. slog.Warn("[GoogleReader] Unknown Stream",
  860. slog.String("handler", "streamItemIDsHandler"),
  861. slog.String("client_ip", clientIP),
  862. slog.String("user_agent", r.UserAgent()),
  863. slog.Any("stream_type", rm.Streams[0].Type),
  864. )
  865. response.JSONServerError(w, r, fmt.Errorf("googlereader: unknown stream type %s", rm.Streams[0].Type))
  866. }
  867. }
  868. func (h *greaderHandler) handleReadingListStreamHandler(w http.ResponseWriter, r *http.Request, rm requestModifiers) {
  869. clientIP := request.ClientIP(r)
  870. slog.Debug("[GoogleReader] Handle ReadingListStream",
  871. slog.String("handler", "handleReadingListStreamHandler"),
  872. slog.String("client_ip", clientIP),
  873. slog.String("user_agent", r.UserAgent()),
  874. )
  875. builder := h.store.NewEntryQueryBuilder(rm.UserID).
  876. WithLimit(rm.Count).
  877. WithOffset(rm.Offset).
  878. WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  879. for _, s := range rm.ExcludeTargets {
  880. switch s.Type {
  881. case ReadStream:
  882. builder.WithStatuses(model.EntryStatusUnread)
  883. default:
  884. slog.Warn("[GoogleReader] Unknown ExcludeTargets filter type",
  885. slog.String("handler", "handleReadingListStreamHandler"),
  886. slog.String("client_ip", clientIP),
  887. slog.String("user_agent", r.UserAgent()),
  888. slog.Int("filter_type", int(s.Type)),
  889. )
  890. }
  891. }
  892. if rm.StartTime > 0 {
  893. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  894. }
  895. if rm.StopTime > 0 {
  896. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  897. }
  898. itemRefs, continuation, err := getItemRefsAndContinuation(*builder, rm)
  899. if err != nil {
  900. response.JSONServerError(w, r, err)
  901. return
  902. }
  903. response.JSON(w, r, streamIDResponse{itemRefs, continuation})
  904. }
  905. func (h *greaderHandler) handleStarredStreamHandler(w http.ResponseWriter, r *http.Request, rm requestModifiers) {
  906. builder := h.store.NewEntryQueryBuilder(rm.UserID).
  907. WithStarred(true).
  908. WithLimit(rm.Count).
  909. WithOffset(rm.Offset).
  910. WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  911. if rm.StartTime > 0 {
  912. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  913. }
  914. if rm.StopTime > 0 {
  915. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  916. }
  917. itemRefs, continuation, err := getItemRefsAndContinuation(*builder, rm)
  918. if err != nil {
  919. response.JSONServerError(w, r, err)
  920. return
  921. }
  922. response.JSON(w, r, streamIDResponse{itemRefs, continuation})
  923. }
  924. func (h *greaderHandler) handleReadStreamHandler(w http.ResponseWriter, r *http.Request, rm requestModifiers) {
  925. builder := h.store.NewEntryQueryBuilder(rm.UserID).
  926. WithStatuses(model.EntryStatusRead).
  927. WithLimit(rm.Count).
  928. WithOffset(rm.Offset).
  929. WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  930. if rm.StartTime > 0 {
  931. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  932. }
  933. if rm.StopTime > 0 {
  934. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  935. }
  936. itemRefs, continuation, err := getItemRefsAndContinuation(*builder, rm)
  937. if err != nil {
  938. response.JSONServerError(w, r, err)
  939. return
  940. }
  941. response.JSON(w, r, streamIDResponse{itemRefs, continuation})
  942. }
  943. func getItemRefsAndContinuation(builder storage.EntryQueryBuilder, rm requestModifiers) ([]itemRef, int, error) {
  944. rawEntryIDs, err := builder.GetEntryIDs()
  945. if err != nil {
  946. return nil, 0, err
  947. }
  948. itemRefs := make([]itemRef, 0, len(rawEntryIDs))
  949. for _, entryID := range rawEntryIDs {
  950. formattedID := strconv.FormatInt(entryID, 10)
  951. itemRefs = append(itemRefs, itemRef{ID: formattedID})
  952. }
  953. totalEntries, err := builder.CountEntries()
  954. if err != nil {
  955. return nil, 0, err
  956. }
  957. continuation := 0
  958. if len(itemRefs)+rm.Offset < totalEntries {
  959. continuation = len(itemRefs) + rm.Offset
  960. }
  961. return itemRefs, continuation, nil
  962. }
  963. func (h *greaderHandler) handleFeedStreamHandler(w http.ResponseWriter, r *http.Request, rm requestModifiers) {
  964. feedID, err := strconv.ParseInt(rm.Streams[0].ID, 10, 64)
  965. if err != nil {
  966. response.JSONServerError(w, r, err)
  967. return
  968. }
  969. builder := h.store.NewEntryQueryBuilder(rm.UserID).
  970. WithFeedID(feedID).
  971. WithLimit(rm.Count).
  972. WithOffset(rm.Offset).
  973. WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  974. if rm.StartTime > 0 {
  975. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  976. }
  977. if rm.StopTime > 0 {
  978. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  979. }
  980. for _, s := range rm.ExcludeTargets {
  981. if s.Type == ReadStream {
  982. builder.WithoutStatus(model.EntryStatusRead)
  983. }
  984. }
  985. itemRefs, continuation, err := getItemRefsAndContinuation(*builder, rm)
  986. if err != nil {
  987. response.JSONServerError(w, r, err)
  988. return
  989. }
  990. response.JSON(w, r, streamIDResponse{itemRefs, continuation})
  991. }
  992. func (h *greaderHandler) markAllAsReadHandler(w http.ResponseWriter, r *http.Request) {
  993. userID := request.UserID(r)
  994. clientIP := request.ClientIP(r)
  995. slog.Debug("[GoogleReader] Handle /mark-all-as-read",
  996. slog.String("handler", "markAllAsReadHandler"),
  997. slog.String("client_ip", clientIP),
  998. slog.String("user_agent", r.UserAgent()),
  999. )
  1000. if err := r.ParseForm(); err != nil {
  1001. response.JSONBadRequest(w, r, err)
  1002. return
  1003. }
  1004. stream, err := getStream(r.Form.Get(paramStreamID), userID)
  1005. if err != nil {
  1006. response.JSONBadRequest(w, r, err)
  1007. return
  1008. }
  1009. var before time.Time
  1010. if timestampParamValue := r.Form.Get(paramTimestamp); timestampParamValue != "" {
  1011. timestampParsedValue, err := strconv.ParseInt(timestampParamValue, 10, 64)
  1012. if err != nil {
  1013. response.JSONBadRequest(w, r, err)
  1014. return
  1015. }
  1016. if timestampParsedValue > 0 {
  1017. // It's unclear if the timestamp is in seconds or microseconds, so we try both using a naive approach.
  1018. if len(timestampParamValue) >= 16 {
  1019. before = time.UnixMicro(timestampParsedValue)
  1020. } else {
  1021. before = time.Unix(timestampParsedValue, 0)
  1022. }
  1023. }
  1024. }
  1025. if before.IsZero() {
  1026. before = time.Now()
  1027. }
  1028. switch stream.Type {
  1029. case FeedStream:
  1030. feedID, err := strconv.ParseInt(stream.ID, 10, 64)
  1031. if err != nil {
  1032. response.JSONBadRequest(w, r, err)
  1033. return
  1034. }
  1035. err = h.store.MarkFeedAsRead(userID, feedID, before)
  1036. if err != nil {
  1037. response.JSONServerError(w, r, err)
  1038. return
  1039. }
  1040. case LabelStream:
  1041. category, err := h.store.CategoryByTitle(userID, stream.ID)
  1042. if err != nil {
  1043. response.JSONServerError(w, r, err)
  1044. return
  1045. }
  1046. if category == nil {
  1047. response.JSONNotFound(w, r)
  1048. return
  1049. }
  1050. if err := h.store.MarkCategoryAsRead(userID, category.ID, before); err != nil {
  1051. response.JSONServerError(w, r, err)
  1052. return
  1053. }
  1054. case ReadingListStream:
  1055. if err = h.store.MarkAllAsReadBeforeDate(userID, before); err != nil {
  1056. response.JSONServerError(w, r, err)
  1057. return
  1058. }
  1059. }
  1060. response.Text(w, r, "OK")
  1061. }
  1062. func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType]bool, error) {
  1063. tags := make(map[StreamType]bool)
  1064. for _, s := range addTags {
  1065. switch s.Type {
  1066. case ReadStream:
  1067. if _, ok := tags[KeptUnreadStream]; ok {
  1068. return nil, errSimultaneously
  1069. }
  1070. tags[ReadStream] = true
  1071. case KeptUnreadStream:
  1072. if _, ok := tags[ReadStream]; ok {
  1073. return nil, errSimultaneously
  1074. }
  1075. tags[ReadStream] = false
  1076. case StarredStream:
  1077. tags[StarredStream] = true
  1078. case BroadcastStream, LikeStream:
  1079. slog.Debug("Broadcast & Like tags are not implemented!")
  1080. default:
  1081. return nil, fmt.Errorf("googlereader: unsupported tag type: %s", s.Type)
  1082. }
  1083. }
  1084. for _, s := range removeTags {
  1085. switch s.Type {
  1086. case ReadStream:
  1087. if _, ok := tags[ReadStream]; ok {
  1088. return nil, errSimultaneously
  1089. }
  1090. tags[ReadStream] = false
  1091. case KeptUnreadStream:
  1092. if _, ok := tags[ReadStream]; ok {
  1093. return nil, errSimultaneously
  1094. }
  1095. tags[ReadStream] = true
  1096. case StarredStream:
  1097. if _, ok := tags[StarredStream]; ok {
  1098. return nil, fmt.Errorf("googlereader: %s should not be supplied for add and remove simultaneously", starredStreamSuffix)
  1099. }
  1100. tags[StarredStream] = false
  1101. case BroadcastStream, LikeStream:
  1102. slog.Debug("Broadcast & Like tags are not implemented!")
  1103. default:
  1104. return nil, fmt.Errorf("googlereader: unsupported tag type: %s", s.Type)
  1105. }
  1106. }
  1107. return tags, nil
  1108. }
  1109. func checkOutputFormat(r *http.Request) error {
  1110. var output string
  1111. if r.Method == http.MethodPost {
  1112. err := r.ParseForm()
  1113. if err != nil {
  1114. return err
  1115. }
  1116. output = r.Form.Get("output")
  1117. } else {
  1118. output = request.QueryStringParam(r, "output", "")
  1119. }
  1120. if output != "json" {
  1121. return errors.New("googlereader: only json output is supported")
  1122. }
  1123. return nil
  1124. }