handler.go 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490
  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. "strings"
  11. "time"
  12. "miniflux.app/v2/internal/config"
  13. "miniflux.app/v2/internal/http/request"
  14. "miniflux.app/v2/internal/http/response"
  15. "miniflux.app/v2/internal/http/response/json"
  16. "miniflux.app/v2/internal/http/route"
  17. "miniflux.app/v2/internal/integration"
  18. "miniflux.app/v2/internal/model"
  19. "miniflux.app/v2/internal/proxy"
  20. "miniflux.app/v2/internal/reader/fetcher"
  21. mff "miniflux.app/v2/internal/reader/handler"
  22. mfs "miniflux.app/v2/internal/reader/subscription"
  23. "miniflux.app/v2/internal/storage"
  24. "miniflux.app/v2/internal/urllib"
  25. "miniflux.app/v2/internal/validator"
  26. "github.com/gorilla/mux"
  27. )
  28. type handler struct {
  29. store *storage.Storage
  30. router *mux.Router
  31. }
  32. const (
  33. // StreamPrefix is the prefix for astreams (read/starred/reading list and so on)
  34. StreamPrefix = "user/-/state/com.google/"
  35. // UserStreamPrefix is the user specific prefix for streams (read/starred/reading list and so on)
  36. UserStreamPrefix = "user/%d/state/com.google/"
  37. // LabelPrefix is the prefix for a label stream
  38. LabelPrefix = "user/-/label/"
  39. // UserLabelPrefix is the user specific prefix prefix for a label stream
  40. UserLabelPrefix = "user/%d/label/"
  41. // FeedPrefix is the prefix for a feed stream
  42. FeedPrefix = "feed/"
  43. // Read is the suffix for read stream
  44. Read = "read"
  45. // Starred is the suffix for starred stream
  46. Starred = "starred"
  47. // ReadingList is the suffix for reading list stream
  48. ReadingList = "reading-list"
  49. // KeptUnread is the suffix for kept unread stream
  50. KeptUnread = "kept-unread"
  51. // Broadcast is the suffix for broadcast stream
  52. Broadcast = "broadcast"
  53. // BroadcastFriends is the suffix for broadcast friends stream
  54. BroadcastFriends = "broadcast-friends"
  55. // Like is the suffix for like stream
  56. Like = "like"
  57. // EntryIDLong is the long entry id representation
  58. EntryIDLong = "tag:google.com,2005:reader/item/%016x"
  59. )
  60. const (
  61. // ParamItemIDs - name of the parameter with the item ids
  62. ParamItemIDs = "i"
  63. // ParamStreamID - name of the parameter containing the stream to be included
  64. ParamStreamID = "s"
  65. // ParamStreamExcludes - name of the parameter containing streams to be excluded
  66. ParamStreamExcludes = "xt"
  67. // ParamStreamFilters - name of the parameter containing streams to be included
  68. ParamStreamFilters = "it"
  69. // ParamStreamMaxItems - name of the parameter containing number of items per page/max items returned
  70. ParamStreamMaxItems = "n"
  71. // ParamStreamOrder - name of the parameter containing the sort criteria
  72. ParamStreamOrder = "r"
  73. // ParamStreamStartTime - name of the parameter containing epoch timestamp, filtering items older than
  74. ParamStreamStartTime = "ot"
  75. // ParamStreamStopTime - name of the parameter containing epoch timestamp, filtering items newer than
  76. ParamStreamStopTime = "nt"
  77. // ParamTagsRemove - name of the parameter containing tags (streams) to be removed
  78. ParamTagsRemove = "r"
  79. // ParamTagsAdd - name of the parameter containing tags (streams) to be added
  80. ParamTagsAdd = "a"
  81. // ParamSubscribeAction - name of the parameter indicating the action to take for subscription/edit
  82. ParamSubscribeAction = "ac"
  83. // ParamTitle - name of the parameter for the title of the subscription
  84. ParamTitle = "t"
  85. // ParamQuickAdd - name of the parameter for a URL being quick subscribed to
  86. ParamQuickAdd = "quickadd"
  87. // ParamDestination - name of the parameter for the new name of a tag
  88. ParamDestination = "dest"
  89. // ParamContinuation - name of the parameter for callers to pass to receive the next page of results
  90. ParamContinuation = "c"
  91. )
  92. // StreamType represents the possible stream types
  93. type StreamType int
  94. const (
  95. // NoStream - no stream type
  96. NoStream StreamType = iota
  97. // ReadStream - read stream type
  98. ReadStream
  99. // StarredStream - starred stream type
  100. StarredStream
  101. // ReadingListStream - reading list stream type
  102. ReadingListStream
  103. // KeptUnreadStream - kept unread stream type
  104. KeptUnreadStream
  105. // BroadcastStream - broadcast stream type
  106. BroadcastStream
  107. // BroadcastFriendsStream - broadcast friends stream type
  108. BroadcastFriendsStream
  109. // LabelStream - label stream type
  110. LabelStream
  111. // FeedStream - feed stream type
  112. FeedStream
  113. // LikeStream - like stream type
  114. LikeStream
  115. )
  116. // Stream defines a stream type and its id
  117. type Stream struct {
  118. Type StreamType
  119. ID string
  120. }
  121. // RequestModifiers are the parsed request parameters
  122. type RequestModifiers struct {
  123. ExcludeTargets []Stream
  124. FilterTargets []Stream
  125. Streams []Stream
  126. Count int
  127. Offset int
  128. SortDirection string
  129. StartTime int64
  130. StopTime int64
  131. ContinuationToken string
  132. UserID int64
  133. }
  134. func (st StreamType) String() string {
  135. switch st {
  136. case NoStream:
  137. return "NoStream"
  138. case ReadStream:
  139. return "ReadStream"
  140. case StarredStream:
  141. return "StarredStream"
  142. case ReadingListStream:
  143. return "ReadingListStream"
  144. case KeptUnreadStream:
  145. return "KeptUnreadStream"
  146. case BroadcastStream:
  147. return "BroadcastStream"
  148. case BroadcastFriendsStream:
  149. return "BroadcastFriendsStream"
  150. case LabelStream:
  151. return "LabelStream"
  152. case FeedStream:
  153. return "FeedStream"
  154. case LikeStream:
  155. return "LikeStream"
  156. default:
  157. return st.String()
  158. }
  159. }
  160. func (s Stream) String() string {
  161. return fmt.Sprintf("%v - '%s'", s.Type, s.ID)
  162. }
  163. func (r RequestModifiers) String() string {
  164. result := fmt.Sprintf("UserID: %d\n", r.UserID)
  165. result += fmt.Sprintf("Streams: %d\n", len(r.Streams))
  166. for _, s := range r.Streams {
  167. result += fmt.Sprintf(" %v\n", s)
  168. }
  169. result += fmt.Sprintf("Exclusions: %d\n", len(r.ExcludeTargets))
  170. for _, s := range r.ExcludeTargets {
  171. result += fmt.Sprintf(" %v\n", s)
  172. }
  173. result += fmt.Sprintf("Filter: %d\n", len(r.FilterTargets))
  174. for _, s := range r.FilterTargets {
  175. result += fmt.Sprintf(" %v\n", s)
  176. }
  177. result += fmt.Sprintf("Count: %d\n", r.Count)
  178. result += fmt.Sprintf("Offset: %d\n", r.Offset)
  179. result += fmt.Sprintf("Sort Direction: %s\n", r.SortDirection)
  180. result += fmt.Sprintf("Continuation Token: %s\n", r.ContinuationToken)
  181. result += fmt.Sprintf("Start Time: %d\n", r.StartTime)
  182. result += fmt.Sprintf("Stop Time: %d\n", r.StopTime)
  183. return result
  184. }
  185. // Serve handles Google Reader API calls.
  186. func Serve(router *mux.Router, store *storage.Storage) {
  187. handler := &handler{store, router}
  188. router.HandleFunc("/accounts/ClientLogin", handler.clientLoginHandler).Methods(http.MethodPost).Name("ClientLogin")
  189. middleware := newMiddleware(store)
  190. sr := router.PathPrefix("/reader/api/0").Subrouter()
  191. sr.Use(middleware.handleCORS)
  192. sr.Use(middleware.apiKeyAuth)
  193. sr.Methods(http.MethodOptions)
  194. sr.HandleFunc("/token", handler.tokenHandler).Methods(http.MethodGet).Name("Token")
  195. sr.HandleFunc("/edit-tag", handler.editTagHandler).Methods(http.MethodPost).Name("EditTag")
  196. sr.HandleFunc("/rename-tag", handler.renameTagHandler).Methods(http.MethodPost).Name("Rename Tag")
  197. sr.HandleFunc("/disable-tag", handler.disableTagHandler).Methods(http.MethodPost).Name("Disable Tag")
  198. sr.HandleFunc("/tag/list", handler.tagListHandler).Methods(http.MethodGet).Name("TagList")
  199. sr.HandleFunc("/user-info", handler.userInfoHandler).Methods(http.MethodGet).Name("UserInfo")
  200. sr.HandleFunc("/subscription/list", handler.subscriptionListHandler).Methods(http.MethodGet).Name("SubscriptonList")
  201. sr.HandleFunc("/subscription/edit", handler.editSubscriptionHandler).Methods(http.MethodPost).Name("SubscriptionEdit")
  202. sr.HandleFunc("/subscription/quickadd", handler.quickAddHandler).Methods(http.MethodPost).Name("QuickAdd")
  203. sr.HandleFunc("/stream/items/ids", handler.streamItemIDsHandler).Methods(http.MethodGet).Name("StreamItemIDs")
  204. sr.HandleFunc("/stream/items/contents", handler.streamItemContentsHandler).Methods(http.MethodPost).Name("StreamItemsContents")
  205. sr.PathPrefix("/").HandlerFunc(handler.serveHandler).Methods(http.MethodPost, http.MethodGet).Name("GoogleReaderApiEndpoint")
  206. }
  207. func getStreamFilterModifiers(r *http.Request) (RequestModifiers, error) {
  208. userID := request.UserID(r)
  209. result := RequestModifiers{
  210. SortDirection: "desc",
  211. UserID: userID,
  212. }
  213. streamOrder := request.QueryStringParam(r, ParamStreamOrder, "d")
  214. if streamOrder == "o" {
  215. result.SortDirection = "asc"
  216. }
  217. var err error
  218. result.Streams, err = getStreams(request.QueryStringParamList(r, ParamStreamID), userID)
  219. if err != nil {
  220. return RequestModifiers{}, err
  221. }
  222. result.ExcludeTargets, err = getStreams(request.QueryStringParamList(r, ParamStreamExcludes), userID)
  223. if err != nil {
  224. return RequestModifiers{}, err
  225. }
  226. result.FilterTargets, err = getStreams(request.QueryStringParamList(r, ParamStreamFilters), userID)
  227. if err != nil {
  228. return RequestModifiers{}, err
  229. }
  230. result.Count = request.QueryIntParam(r, ParamStreamMaxItems, 0)
  231. result.Offset = request.QueryIntParam(r, ParamContinuation, 0)
  232. result.StartTime = request.QueryInt64Param(r, ParamStreamStartTime, int64(0))
  233. result.StopTime = request.QueryInt64Param(r, ParamStreamStopTime, int64(0))
  234. return result, nil
  235. }
  236. func getStream(streamID string, userID int64) (Stream, error) {
  237. if strings.HasPrefix(streamID, FeedPrefix) {
  238. return Stream{Type: FeedStream, ID: strings.TrimPrefix(streamID, FeedPrefix)}, nil
  239. } else if strings.HasPrefix(streamID, fmt.Sprintf(UserStreamPrefix, userID)) || strings.HasPrefix(streamID, StreamPrefix) {
  240. id := strings.TrimPrefix(streamID, fmt.Sprintf(UserStreamPrefix, userID))
  241. id = strings.TrimPrefix(id, StreamPrefix)
  242. switch id {
  243. case Read:
  244. return Stream{ReadStream, ""}, nil
  245. case Starred:
  246. return Stream{StarredStream, ""}, nil
  247. case ReadingList:
  248. return Stream{ReadingListStream, ""}, nil
  249. case KeptUnread:
  250. return Stream{KeptUnreadStream, ""}, nil
  251. case Broadcast:
  252. return Stream{BroadcastStream, ""}, nil
  253. case BroadcastFriends:
  254. return Stream{BroadcastFriendsStream, ""}, nil
  255. case Like:
  256. return Stream{LikeStream, ""}, nil
  257. default:
  258. return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream with id: %s", id)
  259. }
  260. } else if strings.HasPrefix(streamID, fmt.Sprintf(UserLabelPrefix, userID)) || strings.HasPrefix(streamID, LabelPrefix) {
  261. id := strings.TrimPrefix(streamID, fmt.Sprintf(UserLabelPrefix, userID))
  262. id = strings.TrimPrefix(id, LabelPrefix)
  263. return Stream{LabelStream, id}, nil
  264. } else if streamID == "" {
  265. return Stream{NoStream, ""}, nil
  266. }
  267. return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream type: %s", streamID)
  268. }
  269. func getStreams(streamIDs []string, userID int64) ([]Stream, error) {
  270. streams := make([]Stream, 0)
  271. for _, streamID := range streamIDs {
  272. stream, err := getStream(streamID, userID)
  273. if err != nil {
  274. return []Stream{}, err
  275. }
  276. streams = append(streams, stream)
  277. }
  278. return streams, nil
  279. }
  280. func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType]bool, error) {
  281. tags := make(map[StreamType]bool)
  282. for _, s := range addTags {
  283. switch s.Type {
  284. case ReadStream:
  285. if _, ok := tags[KeptUnreadStream]; ok {
  286. return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
  287. }
  288. tags[ReadStream] = true
  289. case KeptUnreadStream:
  290. if _, ok := tags[ReadStream]; ok {
  291. return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
  292. }
  293. tags[ReadStream] = false
  294. case StarredStream:
  295. tags[StarredStream] = true
  296. case BroadcastStream, LikeStream:
  297. slog.Debug("Broadcast & Like tags are not implemented!")
  298. default:
  299. return nil, fmt.Errorf("googlereader: unsupported tag type: %s", s.Type)
  300. }
  301. }
  302. for _, s := range removeTags {
  303. switch s.Type {
  304. case ReadStream:
  305. if _, ok := tags[ReadStream]; ok {
  306. return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
  307. }
  308. tags[ReadStream] = false
  309. case KeptUnreadStream:
  310. if _, ok := tags[ReadStream]; ok {
  311. return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
  312. }
  313. tags[ReadStream] = true
  314. case StarredStream:
  315. if _, ok := tags[StarredStream]; ok {
  316. return nil, fmt.Errorf("googlereader: %s should not be supplied for add and remove simultaneously", Starred)
  317. }
  318. tags[StarredStream] = false
  319. case BroadcastStream, LikeStream:
  320. slog.Debug("Broadcast & Like tags are not implemented!")
  321. default:
  322. return nil, fmt.Errorf("googlereader: unsupported tag type: %s", s.Type)
  323. }
  324. }
  325. return tags, nil
  326. }
  327. func getItemIDs(r *http.Request) ([]int64, error) {
  328. items := r.Form[ParamItemIDs]
  329. if len(items) == 0 {
  330. return nil, fmt.Errorf("googlereader: no items requested")
  331. }
  332. itemIDs := make([]int64, len(items))
  333. for i, item := range items {
  334. var itemID int64
  335. _, err := fmt.Sscanf(item, EntryIDLong, &itemID)
  336. if err != nil {
  337. itemID, err = strconv.ParseInt(item, 16, 64)
  338. if err != nil {
  339. return nil, fmt.Errorf("googlereader: could not parse item: %v", item)
  340. }
  341. }
  342. itemIDs[i] = itemID
  343. }
  344. return itemIDs, nil
  345. }
  346. func checkOutputFormat(w http.ResponseWriter, r *http.Request) error {
  347. var output string
  348. if r.Method == http.MethodPost {
  349. err := r.ParseForm()
  350. if err != nil {
  351. return err
  352. }
  353. output = r.Form.Get("output")
  354. } else {
  355. output = request.QueryStringParam(r, "output", "")
  356. }
  357. if output != "json" {
  358. err := fmt.Errorf("googlereader: only json output is supported")
  359. return err
  360. }
  361. return nil
  362. }
  363. func (h *handler) clientLoginHandler(w http.ResponseWriter, r *http.Request) {
  364. clientIP := request.ClientIP(r)
  365. slog.Debug("[GoogleReader] Handle /accounts/ClientLogin",
  366. slog.String("handler", "clientLoginHandler"),
  367. slog.String("client_ip", clientIP),
  368. slog.String("user_agent", r.UserAgent()),
  369. )
  370. if err := r.ParseForm(); err != nil {
  371. slog.Warn("[GoogleReader] Could not parse request form data",
  372. slog.Bool("authentication_failed", true),
  373. slog.String("client_ip", clientIP),
  374. slog.String("user_agent", r.UserAgent()),
  375. slog.Any("error", err),
  376. )
  377. json.Unauthorized(w, r)
  378. return
  379. }
  380. username := r.Form.Get("Email")
  381. password := r.Form.Get("Passwd")
  382. output := r.Form.Get("output")
  383. if username == "" || password == "" {
  384. slog.Warn("[GoogleReader] Empty username or password",
  385. slog.Bool("authentication_failed", true),
  386. slog.String("client_ip", clientIP),
  387. slog.String("user_agent", r.UserAgent()),
  388. )
  389. json.Unauthorized(w, r)
  390. return
  391. }
  392. if err := h.store.GoogleReaderUserCheckPassword(username, password); err != nil {
  393. slog.Warn("[GoogleReader] Invalid username or password",
  394. slog.Bool("authentication_failed", true),
  395. slog.String("client_ip", clientIP),
  396. slog.String("user_agent", r.UserAgent()),
  397. slog.String("username", username),
  398. slog.Any("error", err),
  399. )
  400. json.Unauthorized(w, r)
  401. return
  402. }
  403. slog.Info("[GoogleReader] User authenticated successfully",
  404. slog.Bool("authentication_successful", true),
  405. slog.String("client_ip", clientIP),
  406. slog.String("user_agent", r.UserAgent()),
  407. slog.String("username", username),
  408. )
  409. integration, err := h.store.GoogleReaderUserGetIntegration(username)
  410. if err != nil {
  411. json.ServerError(w, r, err)
  412. return
  413. }
  414. h.store.SetLastLogin(integration.UserID)
  415. token := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
  416. slog.Debug("[GoogleReader] Created token",
  417. slog.String("client_ip", clientIP),
  418. slog.String("user_agent", r.UserAgent()),
  419. slog.String("username", username),
  420. )
  421. result := login{SID: token, LSID: token, Auth: token}
  422. if output == "json" {
  423. json.OK(w, r, result)
  424. return
  425. }
  426. builder := response.New(w, r)
  427. builder.WithHeader("Content-Type", "text/plain; charset=UTF-8")
  428. builder.WithBody(result.String())
  429. builder.Write()
  430. }
  431. func (h *handler) tokenHandler(w http.ResponseWriter, r *http.Request) {
  432. clientIP := request.ClientIP(r)
  433. slog.Debug("[GoogleReader] Handle /token",
  434. slog.String("handler", "tokenHandler"),
  435. slog.String("client_ip", clientIP),
  436. slog.String("user_agent", r.UserAgent()),
  437. )
  438. if !request.IsAuthenticated(r) {
  439. slog.Warn("[GoogleReader] User is not authenticated",
  440. slog.String("client_ip", clientIP),
  441. slog.String("user_agent", r.UserAgent()),
  442. )
  443. json.Unauthorized(w, r)
  444. return
  445. }
  446. token := request.GoolgeReaderToken(r)
  447. if token == "" {
  448. slog.Warn("[GoogleReader] User does not have token",
  449. slog.String("client_ip", clientIP),
  450. slog.String("user_agent", r.UserAgent()),
  451. slog.Int64("user_id", request.UserID(r)),
  452. )
  453. json.Unauthorized(w, r)
  454. return
  455. }
  456. slog.Debug("[GoogleReader] Token handler",
  457. slog.String("client_ip", clientIP),
  458. slog.String("user_agent", r.UserAgent()),
  459. slog.Int64("user_id", request.UserID(r)),
  460. slog.String("token", token),
  461. )
  462. w.Header().Add("Content-Type", "text/plain; charset=UTF-8")
  463. w.WriteHeader(http.StatusOK)
  464. w.Write([]byte(token))
  465. }
  466. func (h *handler) editTagHandler(w http.ResponseWriter, r *http.Request) {
  467. userID := request.UserID(r)
  468. clientIP := request.ClientIP(r)
  469. slog.Debug("[GoogleReader] Handle /edit-tag",
  470. slog.String("handler", "editTagHandler"),
  471. slog.String("client_ip", clientIP),
  472. slog.String("user_agent", r.UserAgent()),
  473. slog.Int64("user_id", userID),
  474. )
  475. if err := r.ParseForm(); err != nil {
  476. json.ServerError(w, r, err)
  477. return
  478. }
  479. addTags, err := getStreams(r.PostForm[ParamTagsAdd], userID)
  480. if err != nil {
  481. json.ServerError(w, r, err)
  482. return
  483. }
  484. removeTags, err := getStreams(r.PostForm[ParamTagsRemove], userID)
  485. if err != nil {
  486. json.ServerError(w, r, err)
  487. return
  488. }
  489. if len(addTags) == 0 && len(removeTags) == 0 {
  490. err = fmt.Errorf("googlreader: add or/and remove tags should be supplied")
  491. json.ServerError(w, r, err)
  492. return
  493. }
  494. tags, err := checkAndSimplifyTags(addTags, removeTags)
  495. if err != nil {
  496. json.ServerError(w, r, err)
  497. return
  498. }
  499. itemIDs, err := getItemIDs(r)
  500. if err != nil {
  501. json.ServerError(w, r, err)
  502. return
  503. }
  504. slog.Debug("[GoogleReader] Edited tags",
  505. slog.String("handler", "editTagHandler"),
  506. slog.String("client_ip", clientIP),
  507. slog.String("user_agent", r.UserAgent()),
  508. slog.Int64("user_id", userID),
  509. slog.Any("item_ids", itemIDs),
  510. slog.Any("tags", tags),
  511. )
  512. builder := h.store.NewEntryQueryBuilder(userID)
  513. builder.WithEntryIDs(itemIDs)
  514. builder.WithoutStatus(model.EntryStatusRemoved)
  515. entries, err := builder.GetEntries()
  516. if err != nil {
  517. json.ServerError(w, r, err)
  518. return
  519. }
  520. n := 0
  521. readEntryIDs := make([]int64, 0)
  522. unreadEntryIDs := make([]int64, 0)
  523. starredEntryIDs := make([]int64, 0)
  524. unstarredEntryIDs := make([]int64, 0)
  525. for _, entry := range entries {
  526. if read, exists := tags[ReadStream]; exists {
  527. if read && entry.Status == model.EntryStatusUnread {
  528. readEntryIDs = append(readEntryIDs, entry.ID)
  529. } else if entry.Status == model.EntryStatusRead {
  530. unreadEntryIDs = append(unreadEntryIDs, entry.ID)
  531. }
  532. }
  533. if starred, exists := tags[StarredStream]; exists {
  534. if starred && !entry.Starred {
  535. starredEntryIDs = append(starredEntryIDs, entry.ID)
  536. // filter the original array
  537. entries[n] = entry
  538. n++
  539. } else if entry.Starred {
  540. unstarredEntryIDs = append(unstarredEntryIDs, entry.ID)
  541. }
  542. }
  543. }
  544. entries = entries[:n]
  545. if len(readEntryIDs) > 0 {
  546. err = h.store.SetEntriesStatus(userID, readEntryIDs, model.EntryStatusRead)
  547. if err != nil {
  548. json.ServerError(w, r, err)
  549. return
  550. }
  551. }
  552. if len(unreadEntryIDs) > 0 {
  553. err = h.store.SetEntriesStatus(userID, unreadEntryIDs, model.EntryStatusUnread)
  554. if err != nil {
  555. json.ServerError(w, r, err)
  556. return
  557. }
  558. }
  559. if len(unstarredEntryIDs) > 0 {
  560. err = h.store.SetEntriesBookmarkedState(userID, unstarredEntryIDs, false)
  561. if err != nil {
  562. json.ServerError(w, r, err)
  563. return
  564. }
  565. }
  566. if len(starredEntryIDs) > 0 {
  567. err = h.store.SetEntriesBookmarkedState(userID, starredEntryIDs, true)
  568. if err != nil {
  569. json.ServerError(w, r, err)
  570. return
  571. }
  572. }
  573. if len(entries) > 0 {
  574. settings, err := h.store.Integration(userID)
  575. if err != nil {
  576. json.ServerError(w, r, err)
  577. return
  578. }
  579. for _, entry := range entries {
  580. e := entry
  581. go func() {
  582. integration.SendEntry(e, settings)
  583. }()
  584. }
  585. }
  586. OK(w, r)
  587. }
  588. func (h *handler) quickAddHandler(w http.ResponseWriter, r *http.Request) {
  589. userID := request.UserID(r)
  590. clientIP := request.ClientIP(r)
  591. slog.Debug("[GoogleReader] Handle /subscription/quickadd",
  592. slog.String("handler", "quickAddHandler"),
  593. slog.String("client_ip", clientIP),
  594. slog.String("user_agent", r.UserAgent()),
  595. slog.Int64("user_id", userID),
  596. )
  597. err := r.ParseForm()
  598. if err != nil {
  599. json.BadRequest(w, r, err)
  600. return
  601. }
  602. feedURL := r.Form.Get(ParamQuickAdd)
  603. if !validator.IsValidURL(feedURL) {
  604. json.BadRequest(w, r, fmt.Errorf("googlereader: invalid URL: %s", feedURL))
  605. return
  606. }
  607. requestBuilder := fetcher.NewRequestBuilder()
  608. requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
  609. requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
  610. var rssBridgeURL string
  611. if intg, err := h.store.Integration(userID); err == nil && intg != nil && intg.RSSBridgeEnabled {
  612. rssBridgeURL = intg.RSSBridgeURL
  613. }
  614. subscriptions, localizedError := mfs.NewSubscriptionFinder(requestBuilder).FindSubscriptions(feedURL, rssBridgeURL)
  615. if localizedError != nil {
  616. json.ServerError(w, r, localizedError.Error())
  617. return
  618. }
  619. if len(subscriptions) == 0 {
  620. json.OK(w, r, quickAddResponse{
  621. NumResults: 0,
  622. })
  623. return
  624. }
  625. toSubscribe := Stream{FeedStream, subscriptions[0].URL}
  626. category := Stream{NoStream, ""}
  627. newFeed, err := subscribe(toSubscribe, category, "", h.store, userID)
  628. if err != nil {
  629. json.ServerError(w, r, err)
  630. return
  631. }
  632. slog.Debug("[GoogleReader] Added a new feed",
  633. slog.String("handler", "quickAddHandler"),
  634. slog.String("client_ip", clientIP),
  635. slog.String("user_agent", r.UserAgent()),
  636. slog.Int64("user_id", userID),
  637. slog.String("feed_url", newFeed.FeedURL),
  638. )
  639. json.OK(w, r, quickAddResponse{
  640. NumResults: 1,
  641. Query: newFeed.FeedURL,
  642. StreamID: fmt.Sprintf(FeedPrefix+"%d", newFeed.ID),
  643. StreamName: newFeed.Title,
  644. })
  645. }
  646. func getFeed(stream Stream, store *storage.Storage, userID int64) (*model.Feed, error) {
  647. feedID, err := strconv.ParseInt(stream.ID, 10, 64)
  648. if err != nil {
  649. return nil, err
  650. }
  651. return store.FeedByID(userID, feedID)
  652. }
  653. func getOrCreateCategory(category Stream, store *storage.Storage, userID int64) (*model.Category, error) {
  654. if category.ID == "" {
  655. return store.FirstCategory(userID)
  656. } else if store.CategoryTitleExists(userID, category.ID) {
  657. return store.CategoryByTitle(userID, category.ID)
  658. } else {
  659. catRequest := model.CategoryRequest{
  660. Title: category.ID,
  661. }
  662. return store.CreateCategory(userID, &catRequest)
  663. }
  664. }
  665. func subscribe(newFeed Stream, category Stream, title string, store *storage.Storage, userID int64) (*model.Feed, error) {
  666. destCategory, err := getOrCreateCategory(category, store, userID)
  667. if err != nil {
  668. return nil, err
  669. }
  670. feedRequest := model.FeedCreationRequest{
  671. FeedURL: newFeed.ID,
  672. CategoryID: destCategory.ID,
  673. }
  674. verr := validator.ValidateFeedCreation(store, userID, &feedRequest)
  675. if verr != nil {
  676. return nil, verr.Error()
  677. }
  678. created, localizedError := mff.CreateFeed(store, userID, &feedRequest)
  679. if err != nil {
  680. return nil, localizedError.Error()
  681. }
  682. if title != "" {
  683. feedModification := model.FeedModificationRequest{
  684. Title: &title,
  685. }
  686. feedModification.Patch(created)
  687. if err := store.UpdateFeed(created); err != nil {
  688. return nil, err
  689. }
  690. }
  691. return created, nil
  692. }
  693. func unsubscribe(streams []Stream, store *storage.Storage, userID int64) error {
  694. for _, stream := range streams {
  695. feedID, err := strconv.ParseInt(stream.ID, 10, 64)
  696. if err != nil {
  697. return err
  698. }
  699. err = store.RemoveFeed(userID, feedID)
  700. if err != nil {
  701. return err
  702. }
  703. }
  704. return nil
  705. }
  706. func rename(stream Stream, title string, store *storage.Storage, userID int64) error {
  707. if title == "" {
  708. return errors.New("empty title")
  709. }
  710. feed, err := getFeed(stream, store, userID)
  711. if err != nil {
  712. return err
  713. }
  714. feedModification := model.FeedModificationRequest{
  715. Title: &title,
  716. }
  717. feedModification.Patch(feed)
  718. return store.UpdateFeed(feed)
  719. }
  720. func move(stream Stream, destination Stream, store *storage.Storage, userID int64) error {
  721. feed, err := getFeed(stream, store, userID)
  722. if err != nil {
  723. return err
  724. }
  725. category, err := getOrCreateCategory(destination, store, userID)
  726. if err != nil {
  727. return err
  728. }
  729. feedModification := model.FeedModificationRequest{
  730. CategoryID: &category.ID,
  731. }
  732. feedModification.Patch(feed)
  733. return store.UpdateFeed(feed)
  734. }
  735. func (h *handler) editSubscriptionHandler(w http.ResponseWriter, r *http.Request) {
  736. userID := request.UserID(r)
  737. clientIP := request.ClientIP(r)
  738. slog.Debug("[GoogleReader] Handle /subscription/edit",
  739. slog.String("handler", "editSubscriptionHandler"),
  740. slog.String("client_ip", clientIP),
  741. slog.String("user_agent", r.UserAgent()),
  742. slog.Int64("user_id", userID),
  743. )
  744. if err := r.ParseForm(); err != nil {
  745. json.BadRequest(w, r, err)
  746. return
  747. }
  748. streamIds, err := getStreams(r.Form[ParamStreamID], userID)
  749. if err != nil || len(streamIds) == 0 {
  750. json.BadRequest(w, r, errors.New("googlereader: no valid stream IDs provided"))
  751. return
  752. }
  753. newLabel, err := getStream(r.Form.Get(ParamTagsAdd), userID)
  754. if err != nil {
  755. json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", ParamTagsAdd))
  756. return
  757. }
  758. title := r.Form.Get(ParamTitle)
  759. action := r.Form.Get(ParamSubscribeAction)
  760. switch action {
  761. case "subscribe":
  762. _, err := subscribe(streamIds[0], newLabel, title, h.store, userID)
  763. if err != nil {
  764. json.ServerError(w, r, err)
  765. return
  766. }
  767. case "unsubscribe":
  768. err := unsubscribe(streamIds, h.store, userID)
  769. if err != nil {
  770. json.ServerError(w, r, err)
  771. return
  772. }
  773. case "edit":
  774. if title != "" {
  775. err := rename(streamIds[0], title, h.store, userID)
  776. if err != nil {
  777. json.ServerError(w, r, err)
  778. return
  779. }
  780. } else {
  781. if newLabel.Type != LabelStream {
  782. json.BadRequest(w, r, errors.New("destination must be a label"))
  783. return
  784. }
  785. err := move(streamIds[0], newLabel, h.store, userID)
  786. if err != nil {
  787. json.ServerError(w, r, err)
  788. return
  789. }
  790. }
  791. default:
  792. json.ServerError(w, r, fmt.Errorf("googlereader: unrecognized action %s", action))
  793. return
  794. }
  795. OK(w, r)
  796. }
  797. func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Request) {
  798. userID := request.UserID(r)
  799. clientIP := request.ClientIP(r)
  800. slog.Debug("[GoogleReader] Handle /stream/items/contents",
  801. slog.String("handler", "streamItemContentsHandler"),
  802. slog.String("client_ip", clientIP),
  803. slog.String("user_agent", r.UserAgent()),
  804. slog.Int64("user_id", userID),
  805. )
  806. if err := checkOutputFormat(w, r); err != nil {
  807. json.ServerError(w, r, err)
  808. return
  809. }
  810. err := r.ParseForm()
  811. if err != nil {
  812. json.ServerError(w, r, err)
  813. return
  814. }
  815. var user *model.User
  816. if user, err = h.store.UserByID(userID); err != nil {
  817. json.ServerError(w, r, err)
  818. return
  819. }
  820. requestModifiers, err := getStreamFilterModifiers(r)
  821. if err != nil {
  822. json.ServerError(w, r, err)
  823. return
  824. }
  825. userReadingList := fmt.Sprintf(UserStreamPrefix, userID) + ReadingList
  826. userRead := fmt.Sprintf(UserStreamPrefix, userID) + Read
  827. userStarred := fmt.Sprintf(UserStreamPrefix, userID) + Starred
  828. itemIDs, err := getItemIDs(r)
  829. if err != nil {
  830. json.ServerError(w, r, err)
  831. return
  832. }
  833. slog.Debug("[GoogleReader] Fetching item contents",
  834. slog.String("handler", "streamItemContentsHandler"),
  835. slog.String("client_ip", clientIP),
  836. slog.String("user_agent", r.UserAgent()),
  837. slog.Int64("user_id", userID),
  838. slog.Any("item_ids", itemIDs),
  839. )
  840. builder := h.store.NewEntryQueryBuilder(userID)
  841. builder.WithoutStatus(model.EntryStatusRemoved)
  842. builder.WithEntryIDs(itemIDs)
  843. builder.WithSorting(model.DefaultSortingOrder, requestModifiers.SortDirection)
  844. entries, err := builder.GetEntries()
  845. if err != nil {
  846. json.ServerError(w, r, err)
  847. return
  848. }
  849. if len(entries) == 0 {
  850. json.ServerError(w, r, fmt.Errorf("googlereader: no items returned from the database"))
  851. return
  852. }
  853. result := streamContentItems{
  854. Direction: "ltr",
  855. ID: fmt.Sprintf("feed/%d", entries[0].FeedID),
  856. Title: entries[0].Feed.Title,
  857. Alternate: []contentHREFType{
  858. {
  859. HREF: entries[0].Feed.SiteURL,
  860. Type: "text/html",
  861. },
  862. },
  863. Updated: time.Now().Unix(),
  864. Self: []contentHREF{
  865. {
  866. HREF: config.Opts.RootURL() + route.Path(h.router, "StreamItemsContents"),
  867. },
  868. },
  869. Author: user.Username,
  870. }
  871. contentItems := make([]contentItem, len(entries))
  872. for i, entry := range entries {
  873. enclosures := make([]contentItemEnclosure, len(entry.Enclosures))
  874. for _, enclosure := range entry.Enclosures {
  875. enclosures = append(enclosures, contentItemEnclosure{URL: enclosure.URL, Type: enclosure.MimeType})
  876. }
  877. categories := make([]string, 0)
  878. categories = append(categories, userReadingList)
  879. if entry.Feed.Category.Title != "" {
  880. categories = append(categories, fmt.Sprintf(UserLabelPrefix, userID)+entry.Feed.Category.Title)
  881. }
  882. if entry.Starred {
  883. categories = append(categories, userRead)
  884. }
  885. if entry.Starred {
  886. categories = append(categories, userStarred)
  887. }
  888. entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content)
  889. proxyOption := config.Opts.ProxyOption()
  890. for i := range entry.Enclosures {
  891. if proxyOption == "all" || proxyOption != "none" && !urllib.IsHTTPS(entry.Enclosures[i].URL) {
  892. for _, mediaType := range config.Opts.ProxyMediaTypes() {
  893. if strings.HasPrefix(entry.Enclosures[i].MimeType, mediaType+"/") {
  894. entry.Enclosures[i].URL = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
  895. break
  896. }
  897. }
  898. }
  899. }
  900. contentItems[i] = contentItem{
  901. ID: fmt.Sprintf(EntryIDLong, entry.ID),
  902. Title: entry.Title,
  903. Author: entry.Author,
  904. TimestampUsec: fmt.Sprintf("%d", entry.Date.UnixNano()/(int64(time.Microsecond)/int64(time.Nanosecond))),
  905. CrawlTimeMsec: fmt.Sprintf("%d", entry.Date.UnixNano()/(int64(time.Microsecond)/int64(time.Nanosecond))),
  906. Published: entry.Date.Unix(),
  907. Updated: entry.Date.Unix(),
  908. Categories: categories,
  909. Canonical: []contentHREF{
  910. {
  911. HREF: entry.URL,
  912. },
  913. },
  914. Alternate: []contentHREFType{
  915. {
  916. HREF: entry.URL,
  917. Type: "text/html",
  918. },
  919. },
  920. Content: contentItemContent{
  921. Direction: "ltr",
  922. Content: entry.Content,
  923. },
  924. Summary: contentItemContent{
  925. Direction: "ltr",
  926. Content: entry.Content,
  927. },
  928. Origin: contentItemOrigin{
  929. StreamID: fmt.Sprintf("feed/%d", entry.FeedID),
  930. Title: entry.Feed.Title,
  931. HTMLUrl: entry.Feed.SiteURL,
  932. },
  933. Enclosure: enclosures,
  934. }
  935. }
  936. result.Items = contentItems
  937. json.OK(w, r, result)
  938. }
  939. func (h *handler) disableTagHandler(w http.ResponseWriter, r *http.Request) {
  940. userID := request.UserID(r)
  941. clientIP := request.ClientIP(r)
  942. slog.Debug("[GoogleReader] Handle /disable-tags",
  943. slog.String("handler", "disableTagHandler"),
  944. slog.String("client_ip", clientIP),
  945. slog.String("user_agent", r.UserAgent()),
  946. slog.Int64("user_id", userID),
  947. )
  948. err := r.ParseForm()
  949. if err != nil {
  950. json.BadRequest(w, r, err)
  951. return
  952. }
  953. streams, err := getStreams(r.Form[ParamStreamID], userID)
  954. if err != nil {
  955. json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", ParamStreamID))
  956. return
  957. }
  958. titles := make([]string, len(streams))
  959. for i, stream := range streams {
  960. if stream.Type != LabelStream {
  961. json.BadRequest(w, r, errors.New("googlereader: only labels are supported"))
  962. return
  963. }
  964. titles[i] = stream.ID
  965. }
  966. err = h.store.RemoveAndReplaceCategoriesByName(userID, titles)
  967. if err != nil {
  968. json.ServerError(w, r, err)
  969. return
  970. }
  971. OK(w, r)
  972. }
  973. func (h *handler) renameTagHandler(w http.ResponseWriter, r *http.Request) {
  974. userID := request.UserID(r)
  975. clientIP := request.ClientIP(r)
  976. slog.Debug("[GoogleReader] Handle /rename-tag",
  977. slog.String("handler", "renameTagHandler"),
  978. slog.String("client_ip", clientIP),
  979. slog.String("user_agent", r.UserAgent()),
  980. )
  981. err := r.ParseForm()
  982. if err != nil {
  983. json.BadRequest(w, r, err)
  984. return
  985. }
  986. source, err := getStream(r.Form.Get(ParamStreamID), userID)
  987. if err != nil {
  988. json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", ParamStreamID))
  989. return
  990. }
  991. destination, err := getStream(r.Form.Get(ParamDestination), userID)
  992. if err != nil {
  993. json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", ParamDestination))
  994. return
  995. }
  996. if source.Type != LabelStream || destination.Type != LabelStream {
  997. json.BadRequest(w, r, errors.New("googlereader: only labels supported"))
  998. return
  999. }
  1000. if destination.ID == "" {
  1001. json.BadRequest(w, r, errors.New("googlereader: empty destination name"))
  1002. return
  1003. }
  1004. category, err := h.store.CategoryByTitle(userID, source.ID)
  1005. if err != nil {
  1006. json.ServerError(w, r, err)
  1007. return
  1008. }
  1009. if category == nil {
  1010. json.NotFound(w, r)
  1011. return
  1012. }
  1013. categoryRequest := model.CategoryRequest{
  1014. Title: destination.ID,
  1015. }
  1016. verr := validator.ValidateCategoryModification(h.store, userID, category.ID, &categoryRequest)
  1017. if verr != nil {
  1018. json.BadRequest(w, r, verr.Error())
  1019. return
  1020. }
  1021. categoryRequest.Patch(category)
  1022. err = h.store.UpdateCategory(category)
  1023. if err != nil {
  1024. json.ServerError(w, r, err)
  1025. return
  1026. }
  1027. OK(w, r)
  1028. }
  1029. func (h *handler) tagListHandler(w http.ResponseWriter, r *http.Request) {
  1030. userID := request.UserID(r)
  1031. clientIP := request.ClientIP(r)
  1032. slog.Debug("[GoogleReader] Handle /tags/list",
  1033. slog.String("handler", "tagListHandler"),
  1034. slog.String("client_ip", clientIP),
  1035. slog.String("user_agent", r.UserAgent()),
  1036. )
  1037. if err := checkOutputFormat(w, r); err != nil {
  1038. json.BadRequest(w, r, err)
  1039. return
  1040. }
  1041. var result tagsResponse
  1042. categories, err := h.store.Categories(userID)
  1043. if err != nil {
  1044. json.ServerError(w, r, err)
  1045. return
  1046. }
  1047. result.Tags = make([]subscriptionCategory, 0)
  1048. result.Tags = append(result.Tags, subscriptionCategory{
  1049. ID: fmt.Sprintf(UserStreamPrefix, userID) + Starred,
  1050. })
  1051. for _, category := range categories {
  1052. result.Tags = append(result.Tags, subscriptionCategory{
  1053. ID: fmt.Sprintf(UserLabelPrefix, userID) + category.Title,
  1054. Label: category.Title,
  1055. Type: "folder",
  1056. })
  1057. }
  1058. json.OK(w, r, result)
  1059. }
  1060. func (h *handler) subscriptionListHandler(w http.ResponseWriter, r *http.Request) {
  1061. userID := request.UserID(r)
  1062. clientIP := request.ClientIP(r)
  1063. slog.Debug("[GoogleReader] Handle /subscription/list",
  1064. slog.String("handler", "subscriptionListHandler"),
  1065. slog.String("client_ip", clientIP),
  1066. slog.String("user_agent", r.UserAgent()),
  1067. )
  1068. if err := checkOutputFormat(w, r); err != nil {
  1069. json.ServerError(w, r, err)
  1070. return
  1071. }
  1072. var result subscriptionsResponse
  1073. feeds, err := h.store.Feeds(userID)
  1074. if err != nil {
  1075. json.ServerError(w, r, err)
  1076. return
  1077. }
  1078. result.Subscriptions = make([]subscription, 0)
  1079. for _, feed := range feeds {
  1080. result.Subscriptions = append(result.Subscriptions, subscription{
  1081. ID: fmt.Sprintf(FeedPrefix+"%d", feed.ID),
  1082. Title: feed.Title,
  1083. URL: feed.FeedURL,
  1084. Categories: []subscriptionCategory{{fmt.Sprintf(UserLabelPrefix, userID) + feed.Category.Title, feed.Category.Title, "folder"}},
  1085. HTMLURL: feed.SiteURL,
  1086. IconURL: "", //TODO Icons are only base64 encode in DB yet
  1087. })
  1088. }
  1089. json.OK(w, r, result)
  1090. }
  1091. func (h *handler) serveHandler(w http.ResponseWriter, r *http.Request) {
  1092. clientIP := request.ClientIP(r)
  1093. slog.Debug("[GoogleReader] API endpoint not implemented yet",
  1094. slog.Any("url", r.RequestURI),
  1095. slog.String("client_ip", clientIP),
  1096. slog.String("user_agent", r.UserAgent()),
  1097. )
  1098. json.OK(w, r, []string{})
  1099. }
  1100. func (h *handler) userInfoHandler(w http.ResponseWriter, r *http.Request) {
  1101. clientIP := request.ClientIP(r)
  1102. slog.Debug("[GoogleReader] Handle /user-info",
  1103. slog.String("handler", "userInfoHandler"),
  1104. slog.String("client_ip", clientIP),
  1105. slog.String("user_agent", r.UserAgent()),
  1106. )
  1107. if err := checkOutputFormat(w, r); err != nil {
  1108. json.ServerError(w, r, err)
  1109. return
  1110. }
  1111. user, err := h.store.UserByID(request.UserID(r))
  1112. if err != nil {
  1113. json.ServerError(w, r, err)
  1114. return
  1115. }
  1116. userInfo := userInfo{UserID: fmt.Sprint(user.ID), UserName: user.Username, UserProfileID: fmt.Sprint(user.ID), UserEmail: user.Username}
  1117. json.OK(w, r, userInfo)
  1118. }
  1119. func (h *handler) streamItemIDsHandler(w http.ResponseWriter, r *http.Request) {
  1120. userID := request.UserID(r)
  1121. clientIP := request.ClientIP(r)
  1122. slog.Debug("[GoogleReader] Handle /stream/items/ids",
  1123. slog.String("handler", "streamItemIDsHandler"),
  1124. slog.String("client_ip", clientIP),
  1125. slog.String("user_agent", r.UserAgent()),
  1126. slog.Int64("user_id", userID),
  1127. )
  1128. if err := checkOutputFormat(w, r); err != nil {
  1129. json.ServerError(w, r, fmt.Errorf("googlereader: output only as json supported"))
  1130. return
  1131. }
  1132. rm, err := getStreamFilterModifiers(r)
  1133. if err != nil {
  1134. json.ServerError(w, r, err)
  1135. return
  1136. }
  1137. slog.Debug("[GoogleReader] Request modifiers",
  1138. slog.String("handler", "streamItemIDsHandler"),
  1139. slog.String("client_ip", clientIP),
  1140. slog.String("user_agent", r.UserAgent()),
  1141. slog.Any("modifiers", rm),
  1142. )
  1143. if len(rm.Streams) != 1 {
  1144. json.ServerError(w, r, fmt.Errorf("googlereader: only one stream type expected"))
  1145. return
  1146. }
  1147. switch rm.Streams[0].Type {
  1148. case ReadingListStream:
  1149. h.handleReadingListStreamHandler(w, r, rm)
  1150. case StarredStream:
  1151. h.handleStarredStreamHandler(w, r, rm)
  1152. case ReadStream:
  1153. h.handleReadStreamHandler(w, r, rm)
  1154. case FeedStream:
  1155. h.handleFeedStreamHandler(w, r, rm)
  1156. default:
  1157. slog.Warn("[GoogleReader] Unknown Stream",
  1158. slog.String("handler", "streamItemIDsHandler"),
  1159. slog.String("client_ip", clientIP),
  1160. slog.String("user_agent", r.UserAgent()),
  1161. slog.Any("stream_type", rm.Streams[0].Type),
  1162. )
  1163. json.ServerError(w, r, fmt.Errorf("googlereader: unknown stream type %s", rm.Streams[0].Type))
  1164. }
  1165. }
  1166. func (h *handler) handleReadingListStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
  1167. clientIP := request.ClientIP(r)
  1168. slog.Debug("[GoogleReader] Handle ReadingListStream",
  1169. slog.String("handler", "handleReadingListStreamHandler"),
  1170. slog.String("client_ip", clientIP),
  1171. slog.String("user_agent", r.UserAgent()),
  1172. )
  1173. builder := h.store.NewEntryQueryBuilder(rm.UserID)
  1174. for _, s := range rm.ExcludeTargets {
  1175. switch s.Type {
  1176. case ReadStream:
  1177. builder.WithStatus(model.EntryStatusUnread)
  1178. default:
  1179. slog.Warn("[GoogleReader] Unknown ExcludeTargets filter type",
  1180. slog.String("handler", "handleReadingListStreamHandler"),
  1181. slog.String("client_ip", clientIP),
  1182. slog.String("user_agent", r.UserAgent()),
  1183. slog.Any("filter_type", s.Type),
  1184. )
  1185. }
  1186. }
  1187. builder.WithoutStatus(model.EntryStatusRemoved)
  1188. builder.WithLimit(rm.Count)
  1189. builder.WithOffset(rm.Offset)
  1190. builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  1191. if rm.StartTime > 0 {
  1192. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  1193. }
  1194. if rm.StopTime > 0 {
  1195. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  1196. }
  1197. rawEntryIDs, err := builder.GetEntryIDs()
  1198. if err != nil {
  1199. json.ServerError(w, r, err)
  1200. return
  1201. }
  1202. var itemRefs = make([]itemRef, 0)
  1203. for _, entryID := range rawEntryIDs {
  1204. formattedID := strconv.FormatInt(entryID, 10)
  1205. itemRefs = append(itemRefs, itemRef{ID: formattedID})
  1206. }
  1207. totalEntries, err := builder.CountEntries()
  1208. if err != nil {
  1209. json.ServerError(w, r, err)
  1210. return
  1211. }
  1212. continuation := 0
  1213. if len(itemRefs)+rm.Offset < totalEntries {
  1214. continuation = len(itemRefs) + rm.Offset
  1215. }
  1216. json.OK(w, r, streamIDResponse{itemRefs, continuation})
  1217. }
  1218. func (h *handler) handleStarredStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
  1219. builder := h.store.NewEntryQueryBuilder(rm.UserID)
  1220. builder.WithoutStatus(model.EntryStatusRemoved)
  1221. builder.WithStarred(true)
  1222. builder.WithLimit(rm.Count)
  1223. builder.WithOffset(rm.Offset)
  1224. builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  1225. if rm.StartTime > 0 {
  1226. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  1227. }
  1228. if rm.StopTime > 0 {
  1229. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  1230. }
  1231. rawEntryIDs, err := builder.GetEntryIDs()
  1232. if err != nil {
  1233. json.ServerError(w, r, err)
  1234. return
  1235. }
  1236. var itemRefs = make([]itemRef, 0)
  1237. for _, entryID := range rawEntryIDs {
  1238. formattedID := strconv.FormatInt(entryID, 10)
  1239. itemRefs = append(itemRefs, itemRef{ID: formattedID})
  1240. }
  1241. totalEntries, err := builder.CountEntries()
  1242. if err != nil {
  1243. json.ServerError(w, r, err)
  1244. return
  1245. }
  1246. continuation := 0
  1247. if len(itemRefs)+rm.Offset < totalEntries {
  1248. continuation = len(itemRefs) + rm.Offset
  1249. }
  1250. json.OK(w, r, streamIDResponse{itemRefs, continuation})
  1251. }
  1252. func (h *handler) handleReadStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
  1253. builder := h.store.NewEntryQueryBuilder(rm.UserID)
  1254. builder.WithoutStatus(model.EntryStatusRemoved)
  1255. builder.WithStatus(model.EntryStatusRead)
  1256. builder.WithLimit(rm.Count)
  1257. builder.WithOffset(rm.Offset)
  1258. builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  1259. if rm.StartTime > 0 {
  1260. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  1261. }
  1262. if rm.StopTime > 0 {
  1263. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  1264. }
  1265. rawEntryIDs, err := builder.GetEntryIDs()
  1266. if err != nil {
  1267. json.ServerError(w, r, err)
  1268. return
  1269. }
  1270. var itemRefs = make([]itemRef, 0)
  1271. for _, entryID := range rawEntryIDs {
  1272. formattedID := strconv.FormatInt(entryID, 10)
  1273. itemRefs = append(itemRefs, itemRef{ID: formattedID})
  1274. }
  1275. totalEntries, err := builder.CountEntries()
  1276. if err != nil {
  1277. json.ServerError(w, r, err)
  1278. return
  1279. }
  1280. continuation := 0
  1281. if len(itemRefs)+rm.Offset < totalEntries {
  1282. continuation = len(itemRefs) + rm.Offset
  1283. }
  1284. json.OK(w, r, streamIDResponse{itemRefs, continuation})
  1285. }
  1286. func (h *handler) handleFeedStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
  1287. feedID, err := strconv.ParseInt(rm.Streams[0].ID, 10, 64)
  1288. if err != nil {
  1289. json.ServerError(w, r, err)
  1290. return
  1291. }
  1292. builder := h.store.NewEntryQueryBuilder(rm.UserID)
  1293. builder.WithoutStatus(model.EntryStatusRemoved)
  1294. builder.WithFeedID(feedID)
  1295. builder.WithLimit(rm.Count)
  1296. builder.WithOffset(rm.Offset)
  1297. builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
  1298. if rm.StartTime > 0 {
  1299. builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
  1300. }
  1301. if rm.StopTime > 0 {
  1302. builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
  1303. }
  1304. rawEntryIDs, err := builder.GetEntryIDs()
  1305. if err != nil {
  1306. json.ServerError(w, r, err)
  1307. return
  1308. }
  1309. var itemRefs = make([]itemRef, 0)
  1310. for _, entryID := range rawEntryIDs {
  1311. formattedID := strconv.FormatInt(entryID, 10)
  1312. itemRefs = append(itemRefs, itemRef{ID: formattedID})
  1313. }
  1314. totalEntries, err := builder.CountEntries()
  1315. if err != nil {
  1316. json.ServerError(w, r, err)
  1317. return
  1318. }
  1319. continuation := 0
  1320. if len(itemRefs)+rm.Offset < totalEntries {
  1321. continuation = len(itemRefs) + rm.Offset
  1322. }
  1323. json.OK(w, r, streamIDResponse{itemRefs, continuation})
  1324. }