client.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package client // import "miniflux.app/v2/client"
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. )
  12. // Client holds API procedure calls.
  13. type Client struct {
  14. request *request
  15. }
  16. // New returns a new Miniflux client.
  17. // Deprecated: use NewClient instead.
  18. func New(endpoint string, credentials ...string) *Client {
  19. return NewClient(endpoint, credentials...)
  20. }
  21. // NewClient returns a new Miniflux client.
  22. func NewClient(endpoint string, credentials ...string) *Client {
  23. // Trim trailing slashes and /v1 from the endpoint.
  24. endpoint = strings.TrimSuffix(endpoint, "/")
  25. endpoint = strings.TrimSuffix(endpoint, "/v1")
  26. switch len(credentials) {
  27. case 2:
  28. return &Client{request: &request{endpoint: endpoint, username: credentials[0], password: credentials[1]}}
  29. case 1:
  30. return &Client{request: &request{endpoint: endpoint, apiKey: credentials[0]}}
  31. default:
  32. return &Client{request: &request{endpoint: endpoint}}
  33. }
  34. }
  35. // Healthcheck checks if the application is up and running.
  36. func (c *Client) Healthcheck() error {
  37. body, err := c.request.Get("/healthcheck")
  38. if err != nil {
  39. return fmt.Errorf("miniflux: unable to perform healthcheck: %w", err)
  40. }
  41. defer body.Close()
  42. responseBodyContent, err := io.ReadAll(body)
  43. if err != nil {
  44. return fmt.Errorf("miniflux: unable to read healthcheck response: %w", err)
  45. }
  46. if string(responseBodyContent) != "OK" {
  47. return fmt.Errorf("miniflux: invalid healthcheck response: %q", responseBodyContent)
  48. }
  49. return nil
  50. }
  51. // Version returns the version of the Miniflux instance.
  52. func (c *Client) Version() (*VersionResponse, error) {
  53. body, err := c.request.Get("/v1/version")
  54. if err != nil {
  55. return nil, err
  56. }
  57. defer body.Close()
  58. var versionResponse *VersionResponse
  59. if err := json.NewDecoder(body).Decode(&versionResponse); err != nil {
  60. return nil, fmt.Errorf("miniflux: json error (%v)", err)
  61. }
  62. return versionResponse, nil
  63. }
  64. // Me returns the logged user information.
  65. func (c *Client) Me() (*User, error) {
  66. body, err := c.request.Get("/v1/me")
  67. if err != nil {
  68. return nil, err
  69. }
  70. defer body.Close()
  71. var user *User
  72. if err := json.NewDecoder(body).Decode(&user); err != nil {
  73. return nil, fmt.Errorf("miniflux: json error (%v)", err)
  74. }
  75. return user, nil
  76. }
  77. // Users returns all users.
  78. func (c *Client) Users() (Users, error) {
  79. body, err := c.request.Get("/v1/users")
  80. if err != nil {
  81. return nil, err
  82. }
  83. defer body.Close()
  84. var users Users
  85. if err := json.NewDecoder(body).Decode(&users); err != nil {
  86. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  87. }
  88. return users, nil
  89. }
  90. // UserByID returns a single user.
  91. func (c *Client) UserByID(userID int64) (*User, error) {
  92. body, err := c.request.Get(fmt.Sprintf("/v1/users/%d", userID))
  93. if err != nil {
  94. return nil, err
  95. }
  96. defer body.Close()
  97. var user User
  98. if err := json.NewDecoder(body).Decode(&user); err != nil {
  99. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  100. }
  101. return &user, nil
  102. }
  103. // UserByUsername returns a single user.
  104. func (c *Client) UserByUsername(username string) (*User, error) {
  105. body, err := c.request.Get(fmt.Sprintf("/v1/users/%s", username))
  106. if err != nil {
  107. return nil, err
  108. }
  109. defer body.Close()
  110. var user User
  111. if err := json.NewDecoder(body).Decode(&user); err != nil {
  112. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  113. }
  114. return &user, nil
  115. }
  116. // CreateUser creates a new user in the system.
  117. func (c *Client) CreateUser(username, password string, isAdmin bool) (*User, error) {
  118. body, err := c.request.Post("/v1/users", &UserCreationRequest{
  119. Username: username,
  120. Password: password,
  121. IsAdmin: isAdmin,
  122. })
  123. if err != nil {
  124. return nil, err
  125. }
  126. defer body.Close()
  127. var user *User
  128. if err := json.NewDecoder(body).Decode(&user); err != nil {
  129. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  130. }
  131. return user, nil
  132. }
  133. // UpdateUser updates a user in the system.
  134. func (c *Client) UpdateUser(userID int64, userChanges *UserModificationRequest) (*User, error) {
  135. body, err := c.request.Put(fmt.Sprintf("/v1/users/%d", userID), userChanges)
  136. if err != nil {
  137. return nil, err
  138. }
  139. defer body.Close()
  140. var u *User
  141. if err := json.NewDecoder(body).Decode(&u); err != nil {
  142. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  143. }
  144. return u, nil
  145. }
  146. // DeleteUser removes a user from the system.
  147. func (c *Client) DeleteUser(userID int64) error {
  148. return c.request.Delete(fmt.Sprintf("/v1/users/%d", userID))
  149. }
  150. // MarkAllAsRead marks all unread entries as read for a given user.
  151. func (c *Client) MarkAllAsRead(userID int64) error {
  152. _, err := c.request.Put(fmt.Sprintf("/v1/users/%d/mark-all-as-read", userID), nil)
  153. return err
  154. }
  155. // Discover try to find subscriptions from a website.
  156. func (c *Client) Discover(url string) (Subscriptions, error) {
  157. body, err := c.request.Post("/v1/discover", map[string]string{"url": url})
  158. if err != nil {
  159. return nil, err
  160. }
  161. defer body.Close()
  162. var subscriptions Subscriptions
  163. if err := json.NewDecoder(body).Decode(&subscriptions); err != nil {
  164. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  165. }
  166. return subscriptions, nil
  167. }
  168. // Categories gets the list of categories.
  169. func (c *Client) Categories() (Categories, error) {
  170. body, err := c.request.Get("/v1/categories")
  171. if err != nil {
  172. return nil, err
  173. }
  174. defer body.Close()
  175. var categories Categories
  176. if err := json.NewDecoder(body).Decode(&categories); err != nil {
  177. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  178. }
  179. return categories, nil
  180. }
  181. // CreateCategory creates a new category.
  182. func (c *Client) CreateCategory(title string) (*Category, error) {
  183. body, err := c.request.Post("/v1/categories", map[string]interface{}{
  184. "title": title,
  185. })
  186. if err != nil {
  187. return nil, err
  188. }
  189. defer body.Close()
  190. var category *Category
  191. if err := json.NewDecoder(body).Decode(&category); err != nil {
  192. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  193. }
  194. return category, nil
  195. }
  196. // UpdateCategory updates a category.
  197. func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, error) {
  198. body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), map[string]interface{}{
  199. "title": title,
  200. })
  201. if err != nil {
  202. return nil, err
  203. }
  204. defer body.Close()
  205. var category *Category
  206. if err := json.NewDecoder(body).Decode(&category); err != nil {
  207. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  208. }
  209. return category, nil
  210. }
  211. // MarkCategoryAsRead marks all unread entries in a category as read.
  212. func (c *Client) MarkCategoryAsRead(categoryID int64) error {
  213. _, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/mark-all-as-read", categoryID), nil)
  214. return err
  215. }
  216. // CategoryFeeds gets feeds of a category.
  217. func (c *Client) CategoryFeeds(categoryID int64) (Feeds, error) {
  218. body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/feeds", categoryID))
  219. if err != nil {
  220. return nil, err
  221. }
  222. defer body.Close()
  223. var feeds Feeds
  224. if err := json.NewDecoder(body).Decode(&feeds); err != nil {
  225. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  226. }
  227. return feeds, nil
  228. }
  229. // DeleteCategory removes a category.
  230. func (c *Client) DeleteCategory(categoryID int64) error {
  231. return c.request.Delete(fmt.Sprintf("/v1/categories/%d", categoryID))
  232. }
  233. // RefreshCategory refreshes a category.
  234. func (c *Client) RefreshCategory(categoryID int64) error {
  235. _, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/refresh", categoryID), nil)
  236. return err
  237. }
  238. // Feeds gets all feeds.
  239. func (c *Client) Feeds() (Feeds, error) {
  240. body, err := c.request.Get("/v1/feeds")
  241. if err != nil {
  242. return nil, err
  243. }
  244. defer body.Close()
  245. var feeds Feeds
  246. if err := json.NewDecoder(body).Decode(&feeds); err != nil {
  247. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  248. }
  249. return feeds, nil
  250. }
  251. // Export creates OPML file.
  252. func (c *Client) Export() ([]byte, error) {
  253. body, err := c.request.Get("/v1/export")
  254. if err != nil {
  255. return nil, err
  256. }
  257. defer body.Close()
  258. opml, err := io.ReadAll(body)
  259. if err != nil {
  260. return nil, err
  261. }
  262. return opml, nil
  263. }
  264. // Import imports an OPML file.
  265. func (c *Client) Import(f io.ReadCloser) error {
  266. _, err := c.request.PostFile("/v1/import", f)
  267. return err
  268. }
  269. // Feed gets a feed.
  270. func (c *Client) Feed(feedID int64) (*Feed, error) {
  271. body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d", feedID))
  272. if err != nil {
  273. return nil, err
  274. }
  275. defer body.Close()
  276. var feed *Feed
  277. if err := json.NewDecoder(body).Decode(&feed); err != nil {
  278. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  279. }
  280. return feed, nil
  281. }
  282. // CreateFeed creates a new feed.
  283. func (c *Client) CreateFeed(feedCreationRequest *FeedCreationRequest) (int64, error) {
  284. body, err := c.request.Post("/v1/feeds", feedCreationRequest)
  285. if err != nil {
  286. return 0, err
  287. }
  288. defer body.Close()
  289. type result struct {
  290. FeedID int64 `json:"feed_id"`
  291. }
  292. var r result
  293. if err := json.NewDecoder(body).Decode(&r); err != nil {
  294. return 0, fmt.Errorf("miniflux: response error (%v)", err)
  295. }
  296. return r.FeedID, nil
  297. }
  298. // UpdateFeed updates a feed.
  299. func (c *Client) UpdateFeed(feedID int64, feedChanges *FeedModificationRequest) (*Feed, error) {
  300. body, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d", feedID), feedChanges)
  301. if err != nil {
  302. return nil, err
  303. }
  304. defer body.Close()
  305. var f *Feed
  306. if err := json.NewDecoder(body).Decode(&f); err != nil {
  307. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  308. }
  309. return f, nil
  310. }
  311. // MarkFeedAsRead marks all unread entries of the feed as read.
  312. func (c *Client) MarkFeedAsRead(feedID int64) error {
  313. _, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d/mark-all-as-read", feedID), nil)
  314. return err
  315. }
  316. // RefreshAllFeeds refreshes all feeds.
  317. func (c *Client) RefreshAllFeeds() error {
  318. _, err := c.request.Put("/v1/feeds/refresh", nil)
  319. return err
  320. }
  321. // RefreshFeed refreshes a feed.
  322. func (c *Client) RefreshFeed(feedID int64) error {
  323. _, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d/refresh", feedID), nil)
  324. return err
  325. }
  326. // DeleteFeed removes a feed.
  327. func (c *Client) DeleteFeed(feedID int64) error {
  328. return c.request.Delete(fmt.Sprintf("/v1/feeds/%d", feedID))
  329. }
  330. // FeedIcon gets a feed icon.
  331. func (c *Client) FeedIcon(feedID int64) (*FeedIcon, error) {
  332. body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/icon", feedID))
  333. if err != nil {
  334. return nil, err
  335. }
  336. defer body.Close()
  337. var feedIcon *FeedIcon
  338. if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
  339. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  340. }
  341. return feedIcon, nil
  342. }
  343. // FeedEntry gets a single feed entry.
  344. func (c *Client) FeedEntry(feedID, entryID int64) (*Entry, error) {
  345. body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/entries/%d", feedID, entryID))
  346. if err != nil {
  347. return nil, err
  348. }
  349. defer body.Close()
  350. var entry *Entry
  351. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  352. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  353. }
  354. return entry, nil
  355. }
  356. // CategoryEntry gets a single category entry.
  357. func (c *Client) CategoryEntry(categoryID, entryID int64) (*Entry, error) {
  358. body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/entries/%d", categoryID, entryID))
  359. if err != nil {
  360. return nil, err
  361. }
  362. defer body.Close()
  363. var entry *Entry
  364. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  365. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  366. }
  367. return entry, nil
  368. }
  369. // Entry gets a single entry.
  370. func (c *Client) Entry(entryID int64) (*Entry, error) {
  371. body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d", entryID))
  372. if err != nil {
  373. return nil, err
  374. }
  375. defer body.Close()
  376. var entry *Entry
  377. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  378. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  379. }
  380. return entry, nil
  381. }
  382. // Entries fetch entries.
  383. func (c *Client) Entries(filter *Filter) (*EntryResultSet, error) {
  384. path := buildFilterQueryString("/v1/entries", filter)
  385. body, err := c.request.Get(path)
  386. if err != nil {
  387. return nil, err
  388. }
  389. defer body.Close()
  390. var result EntryResultSet
  391. if err := json.NewDecoder(body).Decode(&result); err != nil {
  392. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  393. }
  394. return &result, nil
  395. }
  396. // FeedEntries fetch feed entries.
  397. func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, error) {
  398. path := buildFilterQueryString(fmt.Sprintf("/v1/feeds/%d/entries", feedID), filter)
  399. body, err := c.request.Get(path)
  400. if err != nil {
  401. return nil, err
  402. }
  403. defer body.Close()
  404. var result EntryResultSet
  405. if err := json.NewDecoder(body).Decode(&result); err != nil {
  406. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  407. }
  408. return &result, nil
  409. }
  410. // CategoryEntries fetch entries of a category.
  411. func (c *Client) CategoryEntries(categoryID int64, filter *Filter) (*EntryResultSet, error) {
  412. path := buildFilterQueryString(fmt.Sprintf("/v1/categories/%d/entries", categoryID), filter)
  413. body, err := c.request.Get(path)
  414. if err != nil {
  415. return nil, err
  416. }
  417. defer body.Close()
  418. var result EntryResultSet
  419. if err := json.NewDecoder(body).Decode(&result); err != nil {
  420. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  421. }
  422. return &result, nil
  423. }
  424. // UpdateEntries updates the status of a list of entries.
  425. func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
  426. type payload struct {
  427. EntryIDs []int64 `json:"entry_ids"`
  428. Status string `json:"status"`
  429. }
  430. _, err := c.request.Put("/v1/entries", &payload{EntryIDs: entryIDs, Status: status})
  431. return err
  432. }
  433. // UpdateEntry updates an entry.
  434. func (c *Client) UpdateEntry(entryID int64, entryChanges *EntryModificationRequest) (*Entry, error) {
  435. body, err := c.request.Put(fmt.Sprintf("/v1/entries/%d", entryID), entryChanges)
  436. if err != nil {
  437. return nil, err
  438. }
  439. defer body.Close()
  440. var entry *Entry
  441. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  442. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  443. }
  444. return entry, nil
  445. }
  446. // ToggleBookmark toggles entry bookmark value.
  447. func (c *Client) ToggleBookmark(entryID int64) error {
  448. _, err := c.request.Put(fmt.Sprintf("/v1/entries/%d/bookmark", entryID), nil)
  449. return err
  450. }
  451. // SaveEntry sends an entry to a third-party service.
  452. func (c *Client) SaveEntry(entryID int64) error {
  453. _, err := c.request.Post(fmt.Sprintf("/v1/entries/%d/save", entryID), nil)
  454. return err
  455. }
  456. // FetchEntryOriginalContent fetches the original content of an entry using the scraper.
  457. func (c *Client) FetchEntryOriginalContent(entryID int64) (string, error) {
  458. body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d/fetch-content", entryID))
  459. if err != nil {
  460. return "", err
  461. }
  462. defer body.Close()
  463. var response struct {
  464. Content string `json:"content"`
  465. }
  466. if err := json.NewDecoder(body).Decode(&response); err != nil {
  467. return "", fmt.Errorf("miniflux: response error (%v)", err)
  468. }
  469. return response.Content, nil
  470. }
  471. // FetchCounters fetches feed counters.
  472. func (c *Client) FetchCounters() (*FeedCounters, error) {
  473. body, err := c.request.Get("/v1/feeds/counters")
  474. if err != nil {
  475. return nil, err
  476. }
  477. defer body.Close()
  478. var result FeedCounters
  479. if err := json.NewDecoder(body).Decode(&result); err != nil {
  480. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  481. }
  482. return &result, nil
  483. }
  484. // FlushHistory changes all entries with the status "read" to "removed".
  485. func (c *Client) FlushHistory() error {
  486. _, err := c.request.Put("/v1/flush-history", nil)
  487. return err
  488. }
  489. // Icon fetches a feed icon.
  490. func (c *Client) Icon(iconID int64) (*FeedIcon, error) {
  491. body, err := c.request.Get(fmt.Sprintf("/v1/icons/%d", iconID))
  492. if err != nil {
  493. return nil, err
  494. }
  495. defer body.Close()
  496. var feedIcon *FeedIcon
  497. if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
  498. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  499. }
  500. return feedIcon, nil
  501. }
  502. // Enclosure fetches a specific enclosure.
  503. func (c *Client) Enclosure(enclosureID int64) (*Enclosure, error) {
  504. body, err := c.request.Get(fmt.Sprintf("/v1/enclosures/%d", enclosureID))
  505. if err != nil {
  506. return nil, err
  507. }
  508. defer body.Close()
  509. var enclosure *Enclosure
  510. if err := json.NewDecoder(body).Decode(&enclosure); err != nil {
  511. return nil, fmt.Errorf("miniflux: response error(%v)", err)
  512. }
  513. return enclosure, nil
  514. }
  515. // UpdateEnclosure updates an enclosure.
  516. func (c *Client) UpdateEnclosure(enclosureID int64, enclosureUpdate *EnclosureUpdateRequest) error {
  517. _, err := c.request.Put(fmt.Sprintf("/v1/enclosures/%d", enclosureID), enclosureUpdate)
  518. return err
  519. }
  520. func buildFilterQueryString(path string, filter *Filter) string {
  521. if filter != nil {
  522. values := url.Values{}
  523. if filter.Status != "" {
  524. values.Set("status", filter.Status)
  525. }
  526. if filter.Direction != "" {
  527. values.Set("direction", filter.Direction)
  528. }
  529. if filter.Order != "" {
  530. values.Set("order", filter.Order)
  531. }
  532. if filter.Limit >= 0 {
  533. values.Set("limit", strconv.Itoa(filter.Limit))
  534. }
  535. if filter.Offset >= 0 {
  536. values.Set("offset", strconv.Itoa(filter.Offset))
  537. }
  538. if filter.After > 0 {
  539. values.Set("after", strconv.FormatInt(filter.After, 10))
  540. }
  541. if filter.Before > 0 {
  542. values.Set("before", strconv.FormatInt(filter.Before, 10))
  543. }
  544. if filter.PublishedAfter > 0 {
  545. values.Set("published_after", strconv.FormatInt(filter.PublishedAfter, 10))
  546. }
  547. if filter.PublishedBefore > 0 {
  548. values.Set("published_before", strconv.FormatInt(filter.PublishedBefore, 10))
  549. }
  550. if filter.ChangedAfter > 0 {
  551. values.Set("changed_after", strconv.FormatInt(filter.ChangedAfter, 10))
  552. }
  553. if filter.ChangedBefore > 0 {
  554. values.Set("changed_before", strconv.FormatInt(filter.ChangedBefore, 10))
  555. }
  556. if filter.AfterEntryID > 0 {
  557. values.Set("after_entry_id", strconv.FormatInt(filter.AfterEntryID, 10))
  558. }
  559. if filter.BeforeEntryID > 0 {
  560. values.Set("before_entry_id", strconv.FormatInt(filter.BeforeEntryID, 10))
  561. }
  562. if filter.Starred != "" {
  563. values.Set("starred", filter.Starred)
  564. }
  565. if filter.Search != "" {
  566. values.Set("search", filter.Search)
  567. }
  568. if filter.CategoryID > 0 {
  569. values.Set("category_id", strconv.FormatInt(filter.CategoryID, 10))
  570. }
  571. if filter.FeedID > 0 {
  572. values.Set("feed_id", strconv.FormatInt(filter.FeedID, 10))
  573. }
  574. if filter.GloballyVisible {
  575. values.Set("globally_visible", "true")
  576. }
  577. for _, status := range filter.Statuses {
  578. values.Add("status", status)
  579. }
  580. path = fmt.Sprintf("%s?%s", path, values.Encode())
  581. }
  582. return path
  583. }