client.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  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. // IntegrationsStatus fetches the integrations status for the logged user.
  156. func (c *Client) IntegrationsStatus() (bool, error) {
  157. body, err := c.request.Get("/v1/integrations/status")
  158. if err != nil {
  159. return false, err
  160. }
  161. defer body.Close()
  162. var response struct {
  163. HasIntegrations bool `json:"has_integrations"`
  164. }
  165. if err := json.NewDecoder(body).Decode(&response); err != nil {
  166. return false, fmt.Errorf("miniflux: response error (%v)", err)
  167. }
  168. return response.HasIntegrations, nil
  169. }
  170. // Discover try to find subscriptions from a website.
  171. func (c *Client) Discover(url string) (Subscriptions, error) {
  172. body, err := c.request.Post("/v1/discover", map[string]string{"url": url})
  173. if err != nil {
  174. return nil, err
  175. }
  176. defer body.Close()
  177. var subscriptions Subscriptions
  178. if err := json.NewDecoder(body).Decode(&subscriptions); err != nil {
  179. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  180. }
  181. return subscriptions, nil
  182. }
  183. // Categories gets the list of categories.
  184. func (c *Client) Categories() (Categories, error) {
  185. body, err := c.request.Get("/v1/categories")
  186. if err != nil {
  187. return nil, err
  188. }
  189. defer body.Close()
  190. var categories Categories
  191. if err := json.NewDecoder(body).Decode(&categories); err != nil {
  192. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  193. }
  194. return categories, nil
  195. }
  196. // CreateCategory creates a new category.
  197. func (c *Client) CreateCategory(title string) (*Category, error) {
  198. body, err := c.request.Post("/v1/categories", 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. // UpdateCategory updates a category.
  212. func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, error) {
  213. body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), map[string]interface{}{
  214. "title": title,
  215. })
  216. if err != nil {
  217. return nil, err
  218. }
  219. defer body.Close()
  220. var category *Category
  221. if err := json.NewDecoder(body).Decode(&category); err != nil {
  222. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  223. }
  224. return category, nil
  225. }
  226. // MarkCategoryAsRead marks all unread entries in a category as read.
  227. func (c *Client) MarkCategoryAsRead(categoryID int64) error {
  228. _, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/mark-all-as-read", categoryID), nil)
  229. return err
  230. }
  231. // CategoryFeeds gets feeds of a category.
  232. func (c *Client) CategoryFeeds(categoryID int64) (Feeds, error) {
  233. body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/feeds", categoryID))
  234. if err != nil {
  235. return nil, err
  236. }
  237. defer body.Close()
  238. var feeds Feeds
  239. if err := json.NewDecoder(body).Decode(&feeds); err != nil {
  240. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  241. }
  242. return feeds, nil
  243. }
  244. // DeleteCategory removes a category.
  245. func (c *Client) DeleteCategory(categoryID int64) error {
  246. return c.request.Delete(fmt.Sprintf("/v1/categories/%d", categoryID))
  247. }
  248. // RefreshCategory refreshes a category.
  249. func (c *Client) RefreshCategory(categoryID int64) error {
  250. _, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/refresh", categoryID), nil)
  251. return err
  252. }
  253. // Feeds gets all feeds.
  254. func (c *Client) Feeds() (Feeds, error) {
  255. body, err := c.request.Get("/v1/feeds")
  256. if err != nil {
  257. return nil, err
  258. }
  259. defer body.Close()
  260. var feeds Feeds
  261. if err := json.NewDecoder(body).Decode(&feeds); err != nil {
  262. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  263. }
  264. return feeds, nil
  265. }
  266. // Export creates OPML file.
  267. func (c *Client) Export() ([]byte, error) {
  268. body, err := c.request.Get("/v1/export")
  269. if err != nil {
  270. return nil, err
  271. }
  272. defer body.Close()
  273. opml, err := io.ReadAll(body)
  274. if err != nil {
  275. return nil, err
  276. }
  277. return opml, nil
  278. }
  279. // Import imports an OPML file.
  280. func (c *Client) Import(f io.ReadCloser) error {
  281. _, err := c.request.PostFile("/v1/import", f)
  282. return err
  283. }
  284. // Feed gets a feed.
  285. func (c *Client) Feed(feedID int64) (*Feed, error) {
  286. body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d", feedID))
  287. if err != nil {
  288. return nil, err
  289. }
  290. defer body.Close()
  291. var feed *Feed
  292. if err := json.NewDecoder(body).Decode(&feed); err != nil {
  293. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  294. }
  295. return feed, nil
  296. }
  297. // CreateFeed creates a new feed.
  298. func (c *Client) CreateFeed(feedCreationRequest *FeedCreationRequest) (int64, error) {
  299. body, err := c.request.Post("/v1/feeds", feedCreationRequest)
  300. if err != nil {
  301. return 0, err
  302. }
  303. defer body.Close()
  304. type result struct {
  305. FeedID int64 `json:"feed_id"`
  306. }
  307. var r result
  308. if err := json.NewDecoder(body).Decode(&r); err != nil {
  309. return 0, fmt.Errorf("miniflux: response error (%v)", err)
  310. }
  311. return r.FeedID, nil
  312. }
  313. // UpdateFeed updates a feed.
  314. func (c *Client) UpdateFeed(feedID int64, feedChanges *FeedModificationRequest) (*Feed, error) {
  315. body, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d", feedID), feedChanges)
  316. if err != nil {
  317. return nil, err
  318. }
  319. defer body.Close()
  320. var f *Feed
  321. if err := json.NewDecoder(body).Decode(&f); err != nil {
  322. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  323. }
  324. return f, nil
  325. }
  326. // MarkFeedAsRead marks all unread entries of the feed as read.
  327. func (c *Client) MarkFeedAsRead(feedID int64) error {
  328. _, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d/mark-all-as-read", feedID), nil)
  329. return err
  330. }
  331. // RefreshAllFeeds refreshes all feeds.
  332. func (c *Client) RefreshAllFeeds() error {
  333. _, err := c.request.Put("/v1/feeds/refresh", nil)
  334. return err
  335. }
  336. // RefreshFeed refreshes a feed.
  337. func (c *Client) RefreshFeed(feedID int64) error {
  338. _, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d/refresh", feedID), nil)
  339. return err
  340. }
  341. // DeleteFeed removes a feed.
  342. func (c *Client) DeleteFeed(feedID int64) error {
  343. return c.request.Delete(fmt.Sprintf("/v1/feeds/%d", feedID))
  344. }
  345. // FeedIcon gets a feed icon.
  346. func (c *Client) FeedIcon(feedID int64) (*FeedIcon, error) {
  347. body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/icon", feedID))
  348. if err != nil {
  349. return nil, err
  350. }
  351. defer body.Close()
  352. var feedIcon *FeedIcon
  353. if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
  354. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  355. }
  356. return feedIcon, nil
  357. }
  358. // FeedEntry gets a single feed entry.
  359. func (c *Client) FeedEntry(feedID, entryID int64) (*Entry, error) {
  360. body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/entries/%d", feedID, entryID))
  361. if err != nil {
  362. return nil, err
  363. }
  364. defer body.Close()
  365. var entry *Entry
  366. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  367. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  368. }
  369. return entry, nil
  370. }
  371. // CategoryEntry gets a single category entry.
  372. func (c *Client) CategoryEntry(categoryID, entryID int64) (*Entry, error) {
  373. body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/entries/%d", categoryID, entryID))
  374. if err != nil {
  375. return nil, err
  376. }
  377. defer body.Close()
  378. var entry *Entry
  379. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  380. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  381. }
  382. return entry, nil
  383. }
  384. // Entry gets a single entry.
  385. func (c *Client) Entry(entryID int64) (*Entry, error) {
  386. body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d", entryID))
  387. if err != nil {
  388. return nil, err
  389. }
  390. defer body.Close()
  391. var entry *Entry
  392. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  393. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  394. }
  395. return entry, nil
  396. }
  397. // Entries fetch entries.
  398. func (c *Client) Entries(filter *Filter) (*EntryResultSet, error) {
  399. path := buildFilterQueryString("/v1/entries", filter)
  400. body, err := c.request.Get(path)
  401. if err != nil {
  402. return nil, err
  403. }
  404. defer body.Close()
  405. var result EntryResultSet
  406. if err := json.NewDecoder(body).Decode(&result); err != nil {
  407. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  408. }
  409. return &result, nil
  410. }
  411. // FeedEntries fetch feed entries.
  412. func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, error) {
  413. path := buildFilterQueryString(fmt.Sprintf("/v1/feeds/%d/entries", feedID), filter)
  414. body, err := c.request.Get(path)
  415. if err != nil {
  416. return nil, err
  417. }
  418. defer body.Close()
  419. var result EntryResultSet
  420. if err := json.NewDecoder(body).Decode(&result); err != nil {
  421. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  422. }
  423. return &result, nil
  424. }
  425. // CategoryEntries fetch entries of a category.
  426. func (c *Client) CategoryEntries(categoryID int64, filter *Filter) (*EntryResultSet, error) {
  427. path := buildFilterQueryString(fmt.Sprintf("/v1/categories/%d/entries", categoryID), filter)
  428. body, err := c.request.Get(path)
  429. if err != nil {
  430. return nil, err
  431. }
  432. defer body.Close()
  433. var result EntryResultSet
  434. if err := json.NewDecoder(body).Decode(&result); err != nil {
  435. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  436. }
  437. return &result, nil
  438. }
  439. // UpdateEntries updates the status of a list of entries.
  440. func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
  441. type payload struct {
  442. EntryIDs []int64 `json:"entry_ids"`
  443. Status string `json:"status"`
  444. }
  445. _, err := c.request.Put("/v1/entries", &payload{EntryIDs: entryIDs, Status: status})
  446. return err
  447. }
  448. // UpdateEntry updates an entry.
  449. func (c *Client) UpdateEntry(entryID int64, entryChanges *EntryModificationRequest) (*Entry, error) {
  450. body, err := c.request.Put(fmt.Sprintf("/v1/entries/%d", entryID), entryChanges)
  451. if err != nil {
  452. return nil, err
  453. }
  454. defer body.Close()
  455. var entry *Entry
  456. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  457. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  458. }
  459. return entry, nil
  460. }
  461. // ToggleBookmark toggles entry bookmark value.
  462. func (c *Client) ToggleBookmark(entryID int64) error {
  463. _, err := c.request.Put(fmt.Sprintf("/v1/entries/%d/bookmark", entryID), nil)
  464. return err
  465. }
  466. // SaveEntry sends an entry to a third-party service.
  467. func (c *Client) SaveEntry(entryID int64) error {
  468. _, err := c.request.Post(fmt.Sprintf("/v1/entries/%d/save", entryID), nil)
  469. return err
  470. }
  471. // FetchEntryOriginalContent fetches the original content of an entry using the scraper.
  472. func (c *Client) FetchEntryOriginalContent(entryID int64) (string, error) {
  473. body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d/fetch-content", entryID))
  474. if err != nil {
  475. return "", err
  476. }
  477. defer body.Close()
  478. var response struct {
  479. Content string `json:"content"`
  480. }
  481. if err := json.NewDecoder(body).Decode(&response); err != nil {
  482. return "", fmt.Errorf("miniflux: response error (%v)", err)
  483. }
  484. return response.Content, nil
  485. }
  486. // FetchCounters fetches feed counters.
  487. func (c *Client) FetchCounters() (*FeedCounters, error) {
  488. body, err := c.request.Get("/v1/feeds/counters")
  489. if err != nil {
  490. return nil, err
  491. }
  492. defer body.Close()
  493. var result FeedCounters
  494. if err := json.NewDecoder(body).Decode(&result); err != nil {
  495. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  496. }
  497. return &result, nil
  498. }
  499. // FlushHistory changes all entries with the status "read" to "removed".
  500. func (c *Client) FlushHistory() error {
  501. _, err := c.request.Put("/v1/flush-history", nil)
  502. return err
  503. }
  504. // Icon fetches a feed icon.
  505. func (c *Client) Icon(iconID int64) (*FeedIcon, error) {
  506. body, err := c.request.Get(fmt.Sprintf("/v1/icons/%d", iconID))
  507. if err != nil {
  508. return nil, err
  509. }
  510. defer body.Close()
  511. var feedIcon *FeedIcon
  512. if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
  513. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  514. }
  515. return feedIcon, nil
  516. }
  517. // Enclosure fetches a specific enclosure.
  518. func (c *Client) Enclosure(enclosureID int64) (*Enclosure, error) {
  519. body, err := c.request.Get(fmt.Sprintf("/v1/enclosures/%d", enclosureID))
  520. if err != nil {
  521. return nil, err
  522. }
  523. defer body.Close()
  524. var enclosure *Enclosure
  525. if err := json.NewDecoder(body).Decode(&enclosure); err != nil {
  526. return nil, fmt.Errorf("miniflux: response error(%v)", err)
  527. }
  528. return enclosure, nil
  529. }
  530. // UpdateEnclosure updates an enclosure.
  531. func (c *Client) UpdateEnclosure(enclosureID int64, enclosureUpdate *EnclosureUpdateRequest) error {
  532. _, err := c.request.Put(fmt.Sprintf("/v1/enclosures/%d", enclosureID), enclosureUpdate)
  533. return err
  534. }
  535. func buildFilterQueryString(path string, filter *Filter) string {
  536. if filter != nil {
  537. values := url.Values{}
  538. if filter.Status != "" {
  539. values.Set("status", filter.Status)
  540. }
  541. if filter.Direction != "" {
  542. values.Set("direction", filter.Direction)
  543. }
  544. if filter.Order != "" {
  545. values.Set("order", filter.Order)
  546. }
  547. if filter.Limit >= 0 {
  548. values.Set("limit", strconv.Itoa(filter.Limit))
  549. }
  550. if filter.Offset >= 0 {
  551. values.Set("offset", strconv.Itoa(filter.Offset))
  552. }
  553. if filter.After > 0 {
  554. values.Set("after", strconv.FormatInt(filter.After, 10))
  555. }
  556. if filter.Before > 0 {
  557. values.Set("before", strconv.FormatInt(filter.Before, 10))
  558. }
  559. if filter.PublishedAfter > 0 {
  560. values.Set("published_after", strconv.FormatInt(filter.PublishedAfter, 10))
  561. }
  562. if filter.PublishedBefore > 0 {
  563. values.Set("published_before", strconv.FormatInt(filter.PublishedBefore, 10))
  564. }
  565. if filter.ChangedAfter > 0 {
  566. values.Set("changed_after", strconv.FormatInt(filter.ChangedAfter, 10))
  567. }
  568. if filter.ChangedBefore > 0 {
  569. values.Set("changed_before", strconv.FormatInt(filter.ChangedBefore, 10))
  570. }
  571. if filter.AfterEntryID > 0 {
  572. values.Set("after_entry_id", strconv.FormatInt(filter.AfterEntryID, 10))
  573. }
  574. if filter.BeforeEntryID > 0 {
  575. values.Set("before_entry_id", strconv.FormatInt(filter.BeforeEntryID, 10))
  576. }
  577. if filter.Starred != "" {
  578. values.Set("starred", filter.Starred)
  579. }
  580. if filter.Search != "" {
  581. values.Set("search", filter.Search)
  582. }
  583. if filter.CategoryID > 0 {
  584. values.Set("category_id", strconv.FormatInt(filter.CategoryID, 10))
  585. }
  586. if filter.FeedID > 0 {
  587. values.Set("feed_id", strconv.FormatInt(filter.FeedID, 10))
  588. }
  589. if filter.GloballyVisible {
  590. values.Set("globally_visible", "true")
  591. }
  592. for _, status := range filter.Statuses {
  593. values.Add("status", status)
  594. }
  595. path = fmt.Sprintf("%s?%s", path, values.Encode())
  596. }
  597. return path
  598. }