client.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  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. "context"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "strconv"
  12. "strings"
  13. )
  14. // Client holds API procedure calls.
  15. type Client struct {
  16. request *request
  17. }
  18. // New returns a new Miniflux client.
  19. //
  20. // Deprecated: use NewClient instead.
  21. //
  22. //go:fix inline
  23. func New(endpoint string, credentials ...string) *Client {
  24. return NewClient(endpoint, credentials...)
  25. }
  26. // NewClient returns a new Miniflux client.
  27. func NewClient(endpoint string, credentials ...string) *Client {
  28. switch len(credentials) {
  29. case 2:
  30. return NewClientWithOptions(endpoint, WithCredentials(credentials[0], credentials[1]))
  31. case 1:
  32. return NewClientWithOptions(endpoint, WithAPIKey(credentials[0]))
  33. default:
  34. return NewClientWithOptions(endpoint)
  35. }
  36. }
  37. // NewClientWithOptions returns a new Miniflux client with options.
  38. func NewClientWithOptions(endpoint string, options ...Option) *Client {
  39. // Trim trailing slashes and /v1 from the endpoint.
  40. endpoint = strings.TrimSuffix(endpoint, "/")
  41. endpoint = strings.TrimSuffix(endpoint, "/v1")
  42. request := &request{endpoint: endpoint, client: http.DefaultClient}
  43. for _, option := range options {
  44. option(request)
  45. }
  46. return &Client{request: request}
  47. }
  48. func withDefaultTimeout() (context.Context, func()) {
  49. ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
  50. return ctx, cancel
  51. }
  52. // Healthcheck checks if the application is up and running.
  53. func (c *Client) Healthcheck() error {
  54. ctx, cancel := withDefaultTimeout()
  55. defer cancel()
  56. return c.HealthcheckContext(ctx)
  57. }
  58. // HealthcheckContext checks if the application is up and running.
  59. func (c *Client) HealthcheckContext(ctx context.Context) error {
  60. body, err := c.request.Get(ctx, "/healthcheck")
  61. if err != nil {
  62. return fmt.Errorf("miniflux: unable to perform healthcheck: %w", err)
  63. }
  64. defer body.Close()
  65. responseBodyContent, err := io.ReadAll(body)
  66. if err != nil {
  67. return fmt.Errorf("miniflux: unable to read healthcheck response: %w", err)
  68. }
  69. if string(responseBodyContent) != "OK" {
  70. return fmt.Errorf("miniflux: invalid healthcheck response: %q", responseBodyContent)
  71. }
  72. return nil
  73. }
  74. // Version returns the version of the Miniflux instance.
  75. func (c *Client) Version() (*VersionResponse, error) {
  76. ctx, cancel := withDefaultTimeout()
  77. defer cancel()
  78. return c.VersionContext(ctx)
  79. }
  80. // VersionContext returns the version of the Miniflux instance.
  81. func (c *Client) VersionContext(ctx context.Context) (*VersionResponse, error) {
  82. body, err := c.request.Get(ctx, "/v1/version")
  83. if err != nil {
  84. return nil, err
  85. }
  86. defer body.Close()
  87. var versionResponse *VersionResponse
  88. if err := json.NewDecoder(body).Decode(&versionResponse); err != nil {
  89. return nil, fmt.Errorf("miniflux: json error (%v)", err)
  90. }
  91. return versionResponse, nil
  92. }
  93. // Me returns the logged user information.
  94. func (c *Client) Me() (*User, error) {
  95. ctx, cancel := withDefaultTimeout()
  96. defer cancel()
  97. return c.MeContext(ctx)
  98. }
  99. // MeContext returns the logged user information.
  100. func (c *Client) MeContext(ctx context.Context) (*User, error) {
  101. body, err := c.request.Get(ctx, "/v1/me")
  102. if err != nil {
  103. return nil, err
  104. }
  105. defer body.Close()
  106. var user *User
  107. if err := json.NewDecoder(body).Decode(&user); err != nil {
  108. return nil, fmt.Errorf("miniflux: json error (%v)", err)
  109. }
  110. return user, nil
  111. }
  112. // Users returns all users.
  113. func (c *Client) Users() (Users, error) {
  114. ctx, cancel := withDefaultTimeout()
  115. defer cancel()
  116. return c.UsersContext(ctx)
  117. }
  118. // UsersContext returns all users.
  119. func (c *Client) UsersContext(ctx context.Context) (Users, error) {
  120. body, err := c.request.Get(ctx, "/v1/users")
  121. if err != nil {
  122. return nil, err
  123. }
  124. defer body.Close()
  125. var users Users
  126. if err := json.NewDecoder(body).Decode(&users); err != nil {
  127. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  128. }
  129. return users, nil
  130. }
  131. // UserByID returns a single user.
  132. func (c *Client) UserByID(userID int64) (*User, error) {
  133. ctx, cancel := withDefaultTimeout()
  134. defer cancel()
  135. return c.UserByIDContext(ctx, userID)
  136. }
  137. // UserByIDContext returns a single user.
  138. func (c *Client) UserByIDContext(ctx context.Context, userID int64) (*User, error) {
  139. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/users/%d", userID))
  140. if err != nil {
  141. return nil, err
  142. }
  143. defer body.Close()
  144. var user User
  145. if err := json.NewDecoder(body).Decode(&user); err != nil {
  146. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  147. }
  148. return &user, nil
  149. }
  150. // UserByUsername returns a single user.
  151. func (c *Client) UserByUsername(username string) (*User, error) {
  152. ctx, cancel := withDefaultTimeout()
  153. defer cancel()
  154. return c.UserByUsernameContext(ctx, username)
  155. }
  156. // UserByUsernameContext returns a single user.
  157. func (c *Client) UserByUsernameContext(ctx context.Context, username string) (*User, error) {
  158. body, err := c.request.Get(ctx, "/v1/users/"+username)
  159. if err != nil {
  160. return nil, err
  161. }
  162. defer body.Close()
  163. var user User
  164. if err := json.NewDecoder(body).Decode(&user); err != nil {
  165. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  166. }
  167. return &user, nil
  168. }
  169. // CreateUser creates a new user in the system.
  170. func (c *Client) CreateUser(username, password string, isAdmin bool) (*User, error) {
  171. ctx, cancel := withDefaultTimeout()
  172. defer cancel()
  173. return c.CreateUserContext(ctx, username, password, isAdmin)
  174. }
  175. // CreateUserContext creates a new user in the system.
  176. func (c *Client) CreateUserContext(ctx context.Context, username, password string, isAdmin bool) (*User, error) {
  177. body, err := c.request.Post(ctx, "/v1/users", &UserCreationRequest{
  178. Username: username,
  179. Password: password,
  180. IsAdmin: isAdmin,
  181. })
  182. if err != nil {
  183. return nil, err
  184. }
  185. defer body.Close()
  186. var user *User
  187. if err := json.NewDecoder(body).Decode(&user); err != nil {
  188. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  189. }
  190. return user, nil
  191. }
  192. // UpdateUser updates a user in the system.
  193. func (c *Client) UpdateUser(userID int64, userChanges *UserModificationRequest) (*User, error) {
  194. ctx, cancel := withDefaultTimeout()
  195. defer cancel()
  196. return c.UpdateUserContext(ctx, userID, userChanges)
  197. }
  198. // UpdateUserContext updates a user in the system.
  199. func (c *Client) UpdateUserContext(ctx context.Context, userID int64, userChanges *UserModificationRequest) (*User, error) {
  200. body, err := c.request.Put(ctx, fmt.Sprintf("/v1/users/%d", userID), userChanges)
  201. if err != nil {
  202. return nil, err
  203. }
  204. defer body.Close()
  205. var u *User
  206. if err := json.NewDecoder(body).Decode(&u); err != nil {
  207. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  208. }
  209. return u, nil
  210. }
  211. // DeleteUser removes a user from the system.
  212. func (c *Client) DeleteUser(userID int64) error {
  213. ctx, cancel := withDefaultTimeout()
  214. defer cancel()
  215. return c.DeleteUserContext(ctx, userID)
  216. }
  217. // DeleteUserContext removes a user from the system.
  218. func (c *Client) DeleteUserContext(ctx context.Context, userID int64) error {
  219. return c.request.Delete(ctx, fmt.Sprintf("/v1/users/%d", userID))
  220. }
  221. // APIKeys returns all API keys for the authenticated user.
  222. func (c *Client) APIKeys() (APIKeys, error) {
  223. ctx, cancel := withDefaultTimeout()
  224. defer cancel()
  225. return c.APIKeysContext(ctx)
  226. }
  227. // APIKeysContext returns all API keys for the authenticated user.
  228. func (c *Client) APIKeysContext(ctx context.Context) (APIKeys, error) {
  229. body, err := c.request.Get(ctx, "/v1/api-keys")
  230. if err != nil {
  231. return nil, err
  232. }
  233. defer body.Close()
  234. var apiKeys APIKeys
  235. if err := json.NewDecoder(body).Decode(&apiKeys); err != nil {
  236. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  237. }
  238. return apiKeys, nil
  239. }
  240. // CreateAPIKey creates a new API key for the authenticated user.
  241. func (c *Client) CreateAPIKey(description string) (*APIKey, error) {
  242. ctx, cancel := withDefaultTimeout()
  243. defer cancel()
  244. return c.CreateAPIKeyContext(ctx, description)
  245. }
  246. // CreateAPIKeyContext creates a new API key for the authenticated user.
  247. func (c *Client) CreateAPIKeyContext(ctx context.Context, description string) (*APIKey, error) {
  248. body, err := c.request.Post(ctx, "/v1/api-keys", &APIKeyCreationRequest{
  249. Description: description,
  250. })
  251. if err != nil {
  252. return nil, err
  253. }
  254. defer body.Close()
  255. var apiKey *APIKey
  256. if err := json.NewDecoder(body).Decode(&apiKey); err != nil {
  257. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  258. }
  259. return apiKey, nil
  260. }
  261. // DeleteAPIKey removes an API key for the authenticated user.
  262. func (c *Client) DeleteAPIKey(apiKeyID int64) error {
  263. ctx, cancel := withDefaultTimeout()
  264. defer cancel()
  265. return c.DeleteAPIKeyContext(ctx, apiKeyID)
  266. }
  267. // DeleteAPIKeyContext removes an API key for the authenticated user.
  268. func (c *Client) DeleteAPIKeyContext(ctx context.Context, apiKeyID int64) error {
  269. return c.request.Delete(ctx, fmt.Sprintf("/v1/api-keys/%d", apiKeyID))
  270. }
  271. // MarkAllAsRead marks all unread entries as read for a given user.
  272. func (c *Client) MarkAllAsRead(userID int64) error {
  273. ctx, cancel := withDefaultTimeout()
  274. defer cancel()
  275. return c.MarkAllAsReadContext(ctx, userID)
  276. }
  277. // MarkAllAsReadContext marks all unread entries as read for a given user.
  278. func (c *Client) MarkAllAsReadContext(ctx context.Context, userID int64) error {
  279. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/users/%d/mark-all-as-read", userID), nil)
  280. return err
  281. }
  282. // IntegrationsStatus fetches the integrations status for the signed-in user.
  283. func (c *Client) IntegrationsStatus() (bool, error) {
  284. ctx, cancel := withDefaultTimeout()
  285. defer cancel()
  286. return c.IntegrationsStatusContext(ctx)
  287. }
  288. // IntegrationsStatusContext fetches the integrations status for the signed-in user.
  289. func (c *Client) IntegrationsStatusContext(ctx context.Context) (bool, error) {
  290. body, err := c.request.Get(ctx, "/v1/integrations/status")
  291. if err != nil {
  292. return false, err
  293. }
  294. defer body.Close()
  295. var response struct {
  296. HasIntegrations bool `json:"has_integrations"`
  297. }
  298. if err := json.NewDecoder(body).Decode(&response); err != nil {
  299. return false, fmt.Errorf("miniflux: response error (%v)", err)
  300. }
  301. return response.HasIntegrations, nil
  302. }
  303. // Discover tries to find subscriptions on a website.
  304. func (c *Client) Discover(url string) (Subscriptions, error) {
  305. ctx, cancel := withDefaultTimeout()
  306. defer cancel()
  307. return c.DiscoverContext(ctx, url)
  308. }
  309. // DiscoverContext tries to find subscriptions from a website.
  310. func (c *Client) DiscoverContext(ctx context.Context, url string) (Subscriptions, error) {
  311. body, err := c.request.Post(ctx, "/v1/discover", map[string]string{"url": url})
  312. if err != nil {
  313. return nil, err
  314. }
  315. defer body.Close()
  316. var subscriptions Subscriptions
  317. if err := json.NewDecoder(body).Decode(&subscriptions); err != nil {
  318. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  319. }
  320. return subscriptions, nil
  321. }
  322. // Categories retrieves the list of categories.
  323. func (c *Client) Categories() (Categories, error) {
  324. ctx, cancel := withDefaultTimeout()
  325. defer cancel()
  326. return c.CategoriesContext(ctx)
  327. }
  328. // CategoriesContext retrieves the list of categories.
  329. func (c *Client) CategoriesContext(ctx context.Context) (Categories, error) {
  330. body, err := c.request.Get(ctx, "/v1/categories")
  331. if err != nil {
  332. return nil, err
  333. }
  334. defer body.Close()
  335. var categories Categories
  336. if err := json.NewDecoder(body).Decode(&categories); err != nil {
  337. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  338. }
  339. return categories, nil
  340. }
  341. // CategoriesWithCounters fetches the categories with their respective feed and unread counts.
  342. func (c *Client) CategoriesWithCounters() (Categories, error) {
  343. ctx, cancel := withDefaultTimeout()
  344. defer cancel()
  345. return c.CategoriesWithCountersContext(ctx)
  346. }
  347. // CategoriesWithCountersContext fetches the categories with their respective feed and unread counts.
  348. func (c *Client) CategoriesWithCountersContext(ctx context.Context) (Categories, error) {
  349. body, err := c.request.Get(ctx, "/v1/categories?counts=true")
  350. if err != nil {
  351. return nil, err
  352. }
  353. defer body.Close()
  354. var categories Categories
  355. if err := json.NewDecoder(body).Decode(&categories); err != nil {
  356. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  357. }
  358. return categories, nil
  359. }
  360. // CreateCategory creates a new category.
  361. func (c *Client) CreateCategory(title string) (*Category, error) {
  362. ctx, cancel := withDefaultTimeout()
  363. defer cancel()
  364. return c.CreateCategoryContext(ctx, title)
  365. }
  366. // CreateCategoryContext creates a new category.
  367. func (c *Client) CreateCategoryContext(ctx context.Context, title string) (*Category, error) {
  368. body, err := c.request.Post(ctx, "/v1/categories", &CategoryCreationRequest{
  369. Title: title,
  370. })
  371. if err != nil {
  372. return nil, err
  373. }
  374. defer body.Close()
  375. var category *Category
  376. if err := json.NewDecoder(body).Decode(&category); err != nil {
  377. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  378. }
  379. return category, nil
  380. }
  381. // CreateCategoryWithOptions creates a new category with options.
  382. func (c *Client) CreateCategoryWithOptions(createRequest *CategoryCreationRequest) (*Category, error) {
  383. ctx, cancel := withDefaultTimeout()
  384. defer cancel()
  385. return c.CreateCategoryWithOptionsContext(ctx, createRequest)
  386. }
  387. // CreateCategoryWithOptionsContext creates a new category with options.
  388. func (c *Client) CreateCategoryWithOptionsContext(ctx context.Context, createRequest *CategoryCreationRequest) (*Category, error) {
  389. body, err := c.request.Post(ctx, "/v1/categories", createRequest)
  390. if err != nil {
  391. return nil, err
  392. }
  393. defer body.Close()
  394. var category *Category
  395. if err := json.NewDecoder(body).Decode(&category); err != nil {
  396. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  397. }
  398. return category, nil
  399. }
  400. // UpdateCategory updates a category.
  401. func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, error) {
  402. ctx, cancel := withDefaultTimeout()
  403. defer cancel()
  404. return c.UpdateCategoryContext(ctx, categoryID, title)
  405. }
  406. // UpdateCategoryContext updates a category.
  407. func (c *Client) UpdateCategoryContext(ctx context.Context, categoryID int64, title string) (*Category, error) {
  408. body, err := c.request.Put(ctx, fmt.Sprintf("/v1/categories/%d", categoryID), &CategoryModificationRequest{
  409. Title: new(title),
  410. })
  411. if err != nil {
  412. return nil, err
  413. }
  414. defer body.Close()
  415. var category *Category
  416. if err := json.NewDecoder(body).Decode(&category); err != nil {
  417. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  418. }
  419. return category, nil
  420. }
  421. // UpdateCategoryWithOptions updates a category with options.
  422. func (c *Client) UpdateCategoryWithOptions(categoryID int64, categoryChanges *CategoryModificationRequest) (*Category, error) {
  423. ctx, cancel := withDefaultTimeout()
  424. defer cancel()
  425. return c.UpdateCategoryWithOptionsContext(ctx, categoryID, categoryChanges)
  426. }
  427. // UpdateCategoryWithOptionsContext updates a category with options.
  428. func (c *Client) UpdateCategoryWithOptionsContext(ctx context.Context, categoryID int64, categoryChanges *CategoryModificationRequest) (*Category, error) {
  429. body, err := c.request.Put(ctx, fmt.Sprintf("/v1/categories/%d", categoryID), categoryChanges)
  430. if err != nil {
  431. return nil, err
  432. }
  433. defer body.Close()
  434. var category *Category
  435. if err := json.NewDecoder(body).Decode(&category); err != nil {
  436. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  437. }
  438. return category, nil
  439. }
  440. // MarkCategoryAsRead marks all unread entries in a category as read.
  441. func (c *Client) MarkCategoryAsRead(categoryID int64) error {
  442. ctx, cancel := withDefaultTimeout()
  443. defer cancel()
  444. return c.MarkCategoryAsReadContext(ctx, categoryID)
  445. }
  446. // MarkCategoryAsReadContext marks all unread entries in a category as read.
  447. func (c *Client) MarkCategoryAsReadContext(ctx context.Context, categoryID int64) error {
  448. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/categories/%d/mark-all-as-read", categoryID), nil)
  449. return err
  450. }
  451. // CategoryFeeds returns all feeds for a category.
  452. func (c *Client) CategoryFeeds(categoryID int64) (Feeds, error) {
  453. ctx, cancel := withDefaultTimeout()
  454. defer cancel()
  455. return c.CategoryFeedsContext(ctx, categoryID)
  456. }
  457. // CategoryFeedsContext returns all feeds for a category.
  458. func (c *Client) CategoryFeedsContext(ctx context.Context, categoryID int64) (Feeds, error) {
  459. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/categories/%d/feeds", categoryID))
  460. if err != nil {
  461. return nil, err
  462. }
  463. defer body.Close()
  464. var feeds Feeds
  465. if err := json.NewDecoder(body).Decode(&feeds); err != nil {
  466. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  467. }
  468. return feeds, nil
  469. }
  470. // DeleteCategory removes a category.
  471. func (c *Client) DeleteCategory(categoryID int64) error {
  472. ctx, cancel := withDefaultTimeout()
  473. defer cancel()
  474. return c.DeleteCategoryContext(ctx, categoryID)
  475. }
  476. // DeleteCategoryContext removes a category.
  477. func (c *Client) DeleteCategoryContext(ctx context.Context, categoryID int64) error {
  478. return c.request.Delete(ctx, fmt.Sprintf("/v1/categories/%d", categoryID))
  479. }
  480. // RefreshCategory refreshes a category.
  481. func (c *Client) RefreshCategory(categoryID int64) error {
  482. ctx, cancel := withDefaultTimeout()
  483. defer cancel()
  484. return c.RefreshCategoryContext(ctx, categoryID)
  485. }
  486. // RefreshCategoryContext refreshes a category.
  487. func (c *Client) RefreshCategoryContext(ctx context.Context, categoryID int64) error {
  488. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/categories/%d/refresh", categoryID), nil)
  489. return err
  490. }
  491. // Feeds gets all feeds.
  492. func (c *Client) Feeds() (Feeds, error) {
  493. ctx, cancel := withDefaultTimeout()
  494. defer cancel()
  495. return c.FeedsContext(ctx)
  496. }
  497. // FeedsContext gets all feeds.
  498. func (c *Client) FeedsContext(ctx context.Context) (Feeds, error) {
  499. body, err := c.request.Get(ctx, "/v1/feeds")
  500. if err != nil {
  501. return nil, err
  502. }
  503. defer body.Close()
  504. var feeds Feeds
  505. if err := json.NewDecoder(body).Decode(&feeds); err != nil {
  506. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  507. }
  508. return feeds, nil
  509. }
  510. // Export exports subscriptions as an OPML document.
  511. func (c *Client) Export() ([]byte, error) {
  512. ctx, cancel := withDefaultTimeout()
  513. defer cancel()
  514. return c.ExportContext(ctx)
  515. }
  516. // ExportContext exports subscriptions as an OPML document.
  517. func (c *Client) ExportContext(ctx context.Context) ([]byte, error) {
  518. body, err := c.request.Get(ctx, "/v1/export")
  519. if err != nil {
  520. return nil, err
  521. }
  522. defer body.Close()
  523. opml, err := io.ReadAll(body)
  524. if err != nil {
  525. return nil, err
  526. }
  527. return opml, nil
  528. }
  529. // Import imports an OPML file.
  530. func (c *Client) Import(f io.ReadCloser) error {
  531. ctx, cancel := withDefaultTimeout()
  532. defer cancel()
  533. return c.ImportContext(ctx, f)
  534. }
  535. // ImportContext imports an OPML file.
  536. func (c *Client) ImportContext(ctx context.Context, f io.ReadCloser) error {
  537. _, err := c.request.PostFile(ctx, "/v1/import", f)
  538. return err
  539. }
  540. // Feed gets a feed.
  541. func (c *Client) Feed(feedID int64) (*Feed, error) {
  542. ctx, cancel := withDefaultTimeout()
  543. defer cancel()
  544. return c.FeedContext(ctx, feedID)
  545. }
  546. // FeedContext gets a feed.
  547. func (c *Client) FeedContext(ctx context.Context, feedID int64) (*Feed, error) {
  548. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/feeds/%d", feedID))
  549. if err != nil {
  550. return nil, err
  551. }
  552. defer body.Close()
  553. var feed *Feed
  554. if err := json.NewDecoder(body).Decode(&feed); err != nil {
  555. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  556. }
  557. return feed, nil
  558. }
  559. // CreateFeed creates a new feed.
  560. func (c *Client) CreateFeed(feedCreationRequest *FeedCreationRequest) (int64, error) {
  561. ctx, cancel := withDefaultTimeout()
  562. defer cancel()
  563. return c.CreateFeedContext(ctx, feedCreationRequest)
  564. }
  565. // CreateFeedContext creates a new feed.
  566. func (c *Client) CreateFeedContext(ctx context.Context, feedCreationRequest *FeedCreationRequest) (int64, error) {
  567. body, err := c.request.Post(ctx, "/v1/feeds", feedCreationRequest)
  568. if err != nil {
  569. return 0, err
  570. }
  571. defer body.Close()
  572. type result struct {
  573. FeedID int64 `json:"feed_id"`
  574. }
  575. var r result
  576. if err := json.NewDecoder(body).Decode(&r); err != nil {
  577. return 0, fmt.Errorf("miniflux: response error (%v)", err)
  578. }
  579. return r.FeedID, nil
  580. }
  581. // UpdateFeed updates a feed.
  582. func (c *Client) UpdateFeed(feedID int64, feedChanges *FeedModificationRequest) (*Feed, error) {
  583. ctx, cancel := withDefaultTimeout()
  584. defer cancel()
  585. return c.UpdateFeedContext(ctx, feedID, feedChanges)
  586. }
  587. // UpdateFeedContext updates a feed.
  588. func (c *Client) UpdateFeedContext(ctx context.Context, feedID int64, feedChanges *FeedModificationRequest) (*Feed, error) {
  589. body, err := c.request.Put(ctx, fmt.Sprintf("/v1/feeds/%d", feedID), feedChanges)
  590. if err != nil {
  591. return nil, err
  592. }
  593. defer body.Close()
  594. var f *Feed
  595. if err := json.NewDecoder(body).Decode(&f); err != nil {
  596. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  597. }
  598. return f, nil
  599. }
  600. // ImportFeedEntry imports a single entry into a feed.
  601. func (c *Client) ImportFeedEntry(feedID int64, payload any) (int64, error) {
  602. ctx, cancel := withDefaultTimeout()
  603. defer cancel()
  604. body, err := c.request.Post(
  605. ctx,
  606. fmt.Sprintf("/v1/feeds/%d/entries/import", feedID),
  607. payload,
  608. )
  609. if err != nil {
  610. return 0, err
  611. }
  612. defer body.Close()
  613. var response struct {
  614. ID int64 `json:"id"`
  615. }
  616. if err := json.NewDecoder(body).Decode(&response); err != nil {
  617. return 0, fmt.Errorf("miniflux: json error (%v)", err)
  618. }
  619. return response.ID, nil
  620. }
  621. // MarkFeedAsRead marks all unread entries of the feed as read.
  622. func (c *Client) MarkFeedAsRead(feedID int64) error {
  623. ctx, cancel := withDefaultTimeout()
  624. defer cancel()
  625. return c.MarkFeedAsReadContext(ctx, feedID)
  626. }
  627. // MarkFeedAsReadContext marks all unread entries of the feed as read.
  628. func (c *Client) MarkFeedAsReadContext(ctx context.Context, feedID int64) error {
  629. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/feeds/%d/mark-all-as-read", feedID), nil)
  630. return err
  631. }
  632. // RefreshAllFeeds refreshes all feeds.
  633. func (c *Client) RefreshAllFeeds() error {
  634. ctx, cancel := withDefaultTimeout()
  635. defer cancel()
  636. return c.RefreshAllFeedsContext(ctx)
  637. }
  638. // RefreshAllFeedsContext refreshes all feeds.
  639. func (c *Client) RefreshAllFeedsContext(ctx context.Context) error {
  640. _, err := c.request.Put(ctx, "/v1/feeds/refresh", nil)
  641. return err
  642. }
  643. // RefreshFeed refreshes a feed.
  644. func (c *Client) RefreshFeed(feedID int64) error {
  645. ctx, cancel := withDefaultTimeout()
  646. defer cancel()
  647. return c.RefreshFeedContext(ctx, feedID)
  648. }
  649. // RefreshFeedContext refreshes a feed.
  650. func (c *Client) RefreshFeedContext(ctx context.Context, feedID int64) error {
  651. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/feeds/%d/refresh", feedID), nil)
  652. return err
  653. }
  654. // DeleteFeed removes a feed.
  655. func (c *Client) DeleteFeed(feedID int64) error {
  656. ctx, cancel := withDefaultTimeout()
  657. defer cancel()
  658. return c.DeleteFeedContext(ctx, feedID)
  659. }
  660. // DeleteFeedContext removes a feed.
  661. func (c *Client) DeleteFeedContext(ctx context.Context, feedID int64) error {
  662. return c.request.Delete(ctx, fmt.Sprintf("/v1/feeds/%d", feedID))
  663. }
  664. // FeedIcon gets a feed icon.
  665. func (c *Client) FeedIcon(feedID int64) (*FeedIcon, error) {
  666. ctx, cancel := withDefaultTimeout()
  667. defer cancel()
  668. return c.FeedIconContext(ctx, feedID)
  669. }
  670. // FeedIconContext gets a feed icon.
  671. func (c *Client) FeedIconContext(ctx context.Context, feedID int64) (*FeedIcon, error) {
  672. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/feeds/%d/icon", feedID))
  673. if err != nil {
  674. return nil, err
  675. }
  676. defer body.Close()
  677. var feedIcon *FeedIcon
  678. if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
  679. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  680. }
  681. return feedIcon, nil
  682. }
  683. // FeedEntry gets a single feed entry.
  684. func (c *Client) FeedEntry(feedID, entryID int64) (*Entry, error) {
  685. ctx, cancel := withDefaultTimeout()
  686. defer cancel()
  687. return c.FeedEntryContext(ctx, feedID, entryID)
  688. }
  689. // FeedEntryContext gets a single feed entry.
  690. func (c *Client) FeedEntryContext(ctx context.Context, feedID, entryID int64) (*Entry, error) {
  691. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/feeds/%d/entries/%d", feedID, entryID))
  692. if err != nil {
  693. return nil, err
  694. }
  695. defer body.Close()
  696. var entry *Entry
  697. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  698. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  699. }
  700. return entry, nil
  701. }
  702. // CategoryEntry gets a single category entry.
  703. func (c *Client) CategoryEntry(categoryID, entryID int64) (*Entry, error) {
  704. ctx, cancel := withDefaultTimeout()
  705. defer cancel()
  706. return c.CategoryEntryContext(ctx, categoryID, entryID)
  707. }
  708. // CategoryEntryContext gets a single category entry.
  709. func (c *Client) CategoryEntryContext(ctx context.Context, categoryID, entryID int64) (*Entry, error) {
  710. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/categories/%d/entries/%d", categoryID, entryID))
  711. if err != nil {
  712. return nil, err
  713. }
  714. defer body.Close()
  715. var entry *Entry
  716. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  717. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  718. }
  719. return entry, nil
  720. }
  721. // Entry gets a single entry.
  722. func (c *Client) Entry(entryID int64) (*Entry, error) {
  723. ctx, cancel := withDefaultTimeout()
  724. defer cancel()
  725. return c.EntryContext(ctx, entryID)
  726. }
  727. // EntryContext gets a single entry.
  728. func (c *Client) EntryContext(ctx context.Context, entryID int64) (*Entry, error) {
  729. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/entries/%d", entryID))
  730. if err != nil {
  731. return nil, err
  732. }
  733. defer body.Close()
  734. var entry *Entry
  735. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  736. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  737. }
  738. return entry, nil
  739. }
  740. // EntryIDs returns entry IDs for the current user, optionally filtered by starred status and/or read status.
  741. func (c *Client) EntryIDs(filter *EntryIDsFilter) (*EntryIDsResultSet, error) {
  742. ctx, cancel := withDefaultTimeout()
  743. defer cancel()
  744. return c.EntryIDsContext(ctx, filter)
  745. }
  746. // EntryIDsContext returns entry IDs for the current user, optionally filtered by starred status and/or read status.
  747. func (c *Client) EntryIDsContext(ctx context.Context, filter *EntryIDsFilter) (*EntryIDsResultSet, error) {
  748. body, err := c.request.Get(ctx, buildEntryIDsFilterQueryString("/v1/entries/ids", filter))
  749. if err != nil {
  750. return nil, err
  751. }
  752. defer body.Close()
  753. var result EntryIDsResultSet
  754. if err := json.NewDecoder(body).Decode(&result); err != nil {
  755. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  756. }
  757. return &result, nil
  758. }
  759. func buildEntryIDsFilterQueryString(path string, filter *EntryIDsFilter) string {
  760. if filter == nil {
  761. return path
  762. }
  763. params := url.Values{}
  764. if filter.Limit > 0 {
  765. params.Set("limit", strconv.Itoa(filter.Limit))
  766. }
  767. if filter.Offset > 0 {
  768. params.Set("offset", strconv.Itoa(filter.Offset))
  769. }
  770. if filter.Starred != nil {
  771. params.Set("starred", strconv.FormatBool(*filter.Starred))
  772. }
  773. if filter.Status != "" {
  774. params.Set("status", filter.Status)
  775. }
  776. if len(params) == 0 {
  777. return path
  778. }
  779. return path + "?" + params.Encode()
  780. }
  781. // Entries fetches entries using the given filter.
  782. func (c *Client) Entries(filter *Filter) (*EntryResultSet, error) {
  783. ctx, cancel := withDefaultTimeout()
  784. defer cancel()
  785. return c.EntriesContext(ctx, filter)
  786. }
  787. // EntriesContext fetches entries.
  788. func (c *Client) EntriesContext(ctx context.Context, filter *Filter) (*EntryResultSet, error) {
  789. path := buildFilterQueryString("/v1/entries", filter)
  790. body, err := c.request.Get(ctx, path)
  791. if err != nil {
  792. return nil, err
  793. }
  794. defer body.Close()
  795. var result EntryResultSet
  796. if err := json.NewDecoder(body).Decode(&result); err != nil {
  797. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  798. }
  799. return &result, nil
  800. }
  801. // FeedEntries fetches entries for a feed using the given filter.
  802. func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, error) {
  803. ctx, cancel := withDefaultTimeout()
  804. defer cancel()
  805. return c.FeedEntriesContext(ctx, feedID, filter)
  806. }
  807. // FeedEntriesContext fetches feed entries.
  808. func (c *Client) FeedEntriesContext(ctx context.Context, feedID int64, filter *Filter) (*EntryResultSet, error) {
  809. path := buildFilterQueryString(fmt.Sprintf("/v1/feeds/%d/entries", feedID), filter)
  810. body, err := c.request.Get(ctx, path)
  811. if err != nil {
  812. return nil, err
  813. }
  814. defer body.Close()
  815. var result EntryResultSet
  816. if err := json.NewDecoder(body).Decode(&result); err != nil {
  817. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  818. }
  819. return &result, nil
  820. }
  821. // CategoryEntries fetches entries for a category using the given filter.
  822. func (c *Client) CategoryEntries(categoryID int64, filter *Filter) (*EntryResultSet, error) {
  823. ctx, cancel := withDefaultTimeout()
  824. defer cancel()
  825. return c.CategoryEntriesContext(ctx, categoryID, filter)
  826. }
  827. // CategoryEntriesContext fetches category entries.
  828. func (c *Client) CategoryEntriesContext(ctx context.Context, categoryID int64, filter *Filter) (*EntryResultSet, error) {
  829. path := buildFilterQueryString(fmt.Sprintf("/v1/categories/%d/entries", categoryID), filter)
  830. body, err := c.request.Get(ctx, path)
  831. if err != nil {
  832. return nil, err
  833. }
  834. defer body.Close()
  835. var result EntryResultSet
  836. if err := json.NewDecoder(body).Decode(&result); err != nil {
  837. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  838. }
  839. return &result, nil
  840. }
  841. // UpdateEntries updates the status of a list of entries.
  842. func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
  843. ctx, cancel := withDefaultTimeout()
  844. defer cancel()
  845. return c.UpdateEntriesContext(ctx, entryIDs, status)
  846. }
  847. // UpdateEntriesContext updates the status of a list of entries.
  848. func (c *Client) UpdateEntriesContext(ctx context.Context, entryIDs []int64, status string) error {
  849. type payload struct {
  850. EntryIDs []int64 `json:"entry_ids"`
  851. Status string `json:"status"`
  852. }
  853. _, err := c.request.Put(ctx, "/v1/entries", &payload{EntryIDs: entryIDs, Status: status})
  854. return err
  855. }
  856. // UpdateEntry updates an entry.
  857. func (c *Client) UpdateEntry(entryID int64, entryChanges *EntryModificationRequest) (*Entry, error) {
  858. ctx, cancel := withDefaultTimeout()
  859. defer cancel()
  860. return c.UpdateEntryContext(ctx, entryID, entryChanges)
  861. }
  862. // UpdateEntryContext updates an entry.
  863. func (c *Client) UpdateEntryContext(ctx context.Context, entryID int64, entryChanges *EntryModificationRequest) (*Entry, error) {
  864. body, err := c.request.Put(ctx, fmt.Sprintf("/v1/entries/%d", entryID), entryChanges)
  865. if err != nil {
  866. return nil, err
  867. }
  868. defer body.Close()
  869. var entry *Entry
  870. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  871. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  872. }
  873. return entry, nil
  874. }
  875. // ToggleStarred toggles the starred flag of an entry.
  876. func (c *Client) ToggleStarred(entryID int64) error {
  877. ctx, cancel := withDefaultTimeout()
  878. defer cancel()
  879. return c.ToggleStarredContext(ctx, entryID)
  880. }
  881. // ToggleStarredContext toggles entry starred value.
  882. func (c *Client) ToggleStarredContext(ctx context.Context, entryID int64) error {
  883. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/entries/%d/star", entryID), nil)
  884. return err
  885. }
  886. // SaveEntry sends an entry to a third-party service.
  887. func (c *Client) SaveEntry(entryID int64) error {
  888. ctx, cancel := withDefaultTimeout()
  889. defer cancel()
  890. return c.SaveEntryContext(ctx, entryID)
  891. }
  892. // SaveEntryContext sends an entry to a third-party service.
  893. func (c *Client) SaveEntryContext(ctx context.Context, entryID int64) error {
  894. _, err := c.request.Post(ctx, fmt.Sprintf("/v1/entries/%d/save", entryID), nil)
  895. return err
  896. }
  897. // FetchEntryOriginalContent fetches the original content of an entry using the scraper.
  898. func (c *Client) FetchEntryOriginalContent(entryID int64) (string, error) {
  899. ctx, cancel := withDefaultTimeout()
  900. defer cancel()
  901. return c.FetchEntryOriginalContentContext(ctx, entryID)
  902. }
  903. // FetchEntryOriginalContentContext fetches the original content of an entry using the scraper.
  904. func (c *Client) FetchEntryOriginalContentContext(ctx context.Context, entryID int64) (string, error) {
  905. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/entries/%d/fetch-content", entryID))
  906. if err != nil {
  907. return "", err
  908. }
  909. defer body.Close()
  910. var response struct {
  911. Content string `json:"content"`
  912. }
  913. if err := json.NewDecoder(body).Decode(&response); err != nil {
  914. return "", fmt.Errorf("miniflux: response error (%v)", err)
  915. }
  916. return response.Content, nil
  917. }
  918. // FetchCounters fetches feed counters.
  919. func (c *Client) FetchCounters() (*FeedCounters, error) {
  920. ctx, cancel := withDefaultTimeout()
  921. defer cancel()
  922. return c.FetchCountersContext(ctx)
  923. }
  924. // FetchCountersContext fetches feed counters.
  925. func (c *Client) FetchCountersContext(ctx context.Context) (*FeedCounters, error) {
  926. body, err := c.request.Get(ctx, "/v1/feeds/counters")
  927. if err != nil {
  928. return nil, err
  929. }
  930. defer body.Close()
  931. var result FeedCounters
  932. if err := json.NewDecoder(body).Decode(&result); err != nil {
  933. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  934. }
  935. return &result, nil
  936. }
  937. // FlushHistory deletes all entries with the status "read".
  938. func (c *Client) FlushHistory() error {
  939. ctx, cancel := withDefaultTimeout()
  940. defer cancel()
  941. return c.FlushHistoryContext(ctx)
  942. }
  943. // FlushHistoryContext deletes all entries with the status "read".
  944. func (c *Client) FlushHistoryContext(ctx context.Context) error {
  945. _, err := c.request.Put(ctx, "/v1/flush-history", nil)
  946. return err
  947. }
  948. // Icon fetches a feed icon.
  949. func (c *Client) Icon(iconID int64) (*FeedIcon, error) {
  950. ctx, cancel := withDefaultTimeout()
  951. defer cancel()
  952. return c.IconContext(ctx, iconID)
  953. }
  954. // IconContext fetches a feed icon.
  955. func (c *Client) IconContext(ctx context.Context, iconID int64) (*FeedIcon, error) {
  956. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/icons/%d", iconID))
  957. if err != nil {
  958. return nil, err
  959. }
  960. defer body.Close()
  961. var feedIcon *FeedIcon
  962. if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
  963. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  964. }
  965. return feedIcon, nil
  966. }
  967. // Enclosure fetches a specific enclosure.
  968. func (c *Client) Enclosure(enclosureID int64) (*Enclosure, error) {
  969. ctx, cancel := withDefaultTimeout()
  970. defer cancel()
  971. return c.EnclosureContext(ctx, enclosureID)
  972. }
  973. // EnclosureContext fetches a specific enclosure.
  974. func (c *Client) EnclosureContext(ctx context.Context, enclosureID int64) (*Enclosure, error) {
  975. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/enclosures/%d", enclosureID))
  976. if err != nil {
  977. return nil, err
  978. }
  979. defer body.Close()
  980. var enclosure *Enclosure
  981. if err := json.NewDecoder(body).Decode(&enclosure); err != nil {
  982. return nil, fmt.Errorf("miniflux: response error(%v)", err)
  983. }
  984. return enclosure, nil
  985. }
  986. // UpdateEnclosure updates an enclosure.
  987. func (c *Client) UpdateEnclosure(enclosureID int64, enclosureUpdate *EnclosureUpdateRequest) error {
  988. ctx, cancel := withDefaultTimeout()
  989. defer cancel()
  990. return c.UpdateEnclosureContext(ctx, enclosureID, enclosureUpdate)
  991. }
  992. // UpdateEnclosureContext updates an enclosure.
  993. func (c *Client) UpdateEnclosureContext(ctx context.Context, enclosureID int64, enclosureUpdate *EnclosureUpdateRequest) error {
  994. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/enclosures/%d", enclosureID), enclosureUpdate)
  995. return err
  996. }
  997. func buildFilterQueryString(path string, filter *Filter) string {
  998. if filter != nil {
  999. values := url.Values{}
  1000. if filter.Status != "" {
  1001. values.Set("status", filter.Status)
  1002. }
  1003. if filter.Direction != "" {
  1004. values.Set("direction", filter.Direction)
  1005. }
  1006. if filter.Order != "" {
  1007. values.Set("order", filter.Order)
  1008. }
  1009. if filter.Limit >= 0 {
  1010. values.Set("limit", strconv.Itoa(filter.Limit))
  1011. }
  1012. if filter.Offset >= 0 {
  1013. values.Set("offset", strconv.Itoa(filter.Offset))
  1014. }
  1015. if filter.After > 0 {
  1016. values.Set("after", strconv.FormatInt(filter.After, 10))
  1017. }
  1018. if filter.Before > 0 {
  1019. values.Set("before", strconv.FormatInt(filter.Before, 10))
  1020. }
  1021. if filter.PublishedAfter > 0 {
  1022. values.Set("published_after", strconv.FormatInt(filter.PublishedAfter, 10))
  1023. }
  1024. if filter.PublishedBefore > 0 {
  1025. values.Set("published_before", strconv.FormatInt(filter.PublishedBefore, 10))
  1026. }
  1027. if filter.ChangedAfter > 0 {
  1028. values.Set("changed_after", strconv.FormatInt(filter.ChangedAfter, 10))
  1029. }
  1030. if filter.ChangedBefore > 0 {
  1031. values.Set("changed_before", strconv.FormatInt(filter.ChangedBefore, 10))
  1032. }
  1033. if filter.AfterEntryID > 0 {
  1034. values.Set("after_entry_id", strconv.FormatInt(filter.AfterEntryID, 10))
  1035. }
  1036. if filter.BeforeEntryID > 0 {
  1037. values.Set("before_entry_id", strconv.FormatInt(filter.BeforeEntryID, 10))
  1038. }
  1039. if filter.Starred != "" {
  1040. values.Set("starred", filter.Starred)
  1041. }
  1042. if filter.Search != "" {
  1043. values.Set("search", filter.Search)
  1044. }
  1045. if filter.CategoryID > 0 {
  1046. values.Set("category_id", strconv.FormatInt(filter.CategoryID, 10))
  1047. }
  1048. if filter.FeedID > 0 {
  1049. values.Set("feed_id", strconv.FormatInt(filter.FeedID, 10))
  1050. }
  1051. if filter.GloballyVisible {
  1052. values.Set("globally_visible", "true")
  1053. }
  1054. for _, status := range filter.Statuses {
  1055. values.Add("status", status)
  1056. }
  1057. path = fmt.Sprintf("%s?%s", path, values.Encode())
  1058. }
  1059. return path
  1060. }