client.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238
  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. // Entries fetches entries using the given filter.
  741. func (c *Client) Entries(filter *Filter) (*EntryResultSet, error) {
  742. ctx, cancel := withDefaultTimeout()
  743. defer cancel()
  744. return c.EntriesContext(ctx, filter)
  745. }
  746. // EntriesContext fetches entries.
  747. func (c *Client) EntriesContext(ctx context.Context, filter *Filter) (*EntryResultSet, error) {
  748. path := buildFilterQueryString("/v1/entries", filter)
  749. body, err := c.request.Get(ctx, path)
  750. if err != nil {
  751. return nil, err
  752. }
  753. defer body.Close()
  754. var result EntryResultSet
  755. if err := json.NewDecoder(body).Decode(&result); err != nil {
  756. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  757. }
  758. return &result, nil
  759. }
  760. // FeedEntries fetches entries for a feed using the given filter.
  761. func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, error) {
  762. ctx, cancel := withDefaultTimeout()
  763. defer cancel()
  764. return c.FeedEntriesContext(ctx, feedID, filter)
  765. }
  766. // FeedEntriesContext fetches feed entries.
  767. func (c *Client) FeedEntriesContext(ctx context.Context, feedID int64, filter *Filter) (*EntryResultSet, error) {
  768. path := buildFilterQueryString(fmt.Sprintf("/v1/feeds/%d/entries", feedID), filter)
  769. body, err := c.request.Get(ctx, path)
  770. if err != nil {
  771. return nil, err
  772. }
  773. defer body.Close()
  774. var result EntryResultSet
  775. if err := json.NewDecoder(body).Decode(&result); err != nil {
  776. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  777. }
  778. return &result, nil
  779. }
  780. // CategoryEntries fetches entries for a category using the given filter.
  781. func (c *Client) CategoryEntries(categoryID int64, filter *Filter) (*EntryResultSet, error) {
  782. ctx, cancel := withDefaultTimeout()
  783. defer cancel()
  784. return c.CategoryEntriesContext(ctx, categoryID, filter)
  785. }
  786. // CategoryEntriesContext fetches category entries.
  787. func (c *Client) CategoryEntriesContext(ctx context.Context, categoryID int64, filter *Filter) (*EntryResultSet, error) {
  788. path := buildFilterQueryString(fmt.Sprintf("/v1/categories/%d/entries", categoryID), filter)
  789. body, err := c.request.Get(ctx, path)
  790. if err != nil {
  791. return nil, err
  792. }
  793. defer body.Close()
  794. var result EntryResultSet
  795. if err := json.NewDecoder(body).Decode(&result); err != nil {
  796. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  797. }
  798. return &result, nil
  799. }
  800. // UpdateEntries updates the status of a list of entries.
  801. func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
  802. ctx, cancel := withDefaultTimeout()
  803. defer cancel()
  804. return c.UpdateEntriesContext(ctx, entryIDs, status)
  805. }
  806. // UpdateEntriesContext updates the status of a list of entries.
  807. func (c *Client) UpdateEntriesContext(ctx context.Context, entryIDs []int64, status string) error {
  808. type payload struct {
  809. EntryIDs []int64 `json:"entry_ids"`
  810. Status string `json:"status"`
  811. }
  812. _, err := c.request.Put(ctx, "/v1/entries", &payload{EntryIDs: entryIDs, Status: status})
  813. return err
  814. }
  815. // UpdateEntry updates an entry.
  816. func (c *Client) UpdateEntry(entryID int64, entryChanges *EntryModificationRequest) (*Entry, error) {
  817. ctx, cancel := withDefaultTimeout()
  818. defer cancel()
  819. return c.UpdateEntryContext(ctx, entryID, entryChanges)
  820. }
  821. // UpdateEntryContext updates an entry.
  822. func (c *Client) UpdateEntryContext(ctx context.Context, entryID int64, entryChanges *EntryModificationRequest) (*Entry, error) {
  823. body, err := c.request.Put(ctx, fmt.Sprintf("/v1/entries/%d", entryID), entryChanges)
  824. if err != nil {
  825. return nil, err
  826. }
  827. defer body.Close()
  828. var entry *Entry
  829. if err := json.NewDecoder(body).Decode(&entry); err != nil {
  830. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  831. }
  832. return entry, nil
  833. }
  834. // ToggleStarred toggles the starred flag of an entry.
  835. func (c *Client) ToggleStarred(entryID int64) error {
  836. ctx, cancel := withDefaultTimeout()
  837. defer cancel()
  838. return c.ToggleStarredContext(ctx, entryID)
  839. }
  840. // ToggleStarredContext toggles entry starred value.
  841. func (c *Client) ToggleStarredContext(ctx context.Context, entryID int64) error {
  842. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/entries/%d/star", entryID), nil)
  843. return err
  844. }
  845. // SaveEntry sends an entry to a third-party service.
  846. func (c *Client) SaveEntry(entryID int64) error {
  847. ctx, cancel := withDefaultTimeout()
  848. defer cancel()
  849. return c.SaveEntryContext(ctx, entryID)
  850. }
  851. // SaveEntryContext sends an entry to a third-party service.
  852. func (c *Client) SaveEntryContext(ctx context.Context, entryID int64) error {
  853. _, err := c.request.Post(ctx, fmt.Sprintf("/v1/entries/%d/save", entryID), nil)
  854. return err
  855. }
  856. // FetchEntryOriginalContent fetches the original content of an entry using the scraper.
  857. func (c *Client) FetchEntryOriginalContent(entryID int64) (string, error) {
  858. ctx, cancel := withDefaultTimeout()
  859. defer cancel()
  860. return c.FetchEntryOriginalContentContext(ctx, entryID)
  861. }
  862. // FetchEntryOriginalContentContext fetches the original content of an entry using the scraper.
  863. func (c *Client) FetchEntryOriginalContentContext(ctx context.Context, entryID int64) (string, error) {
  864. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/entries/%d/fetch-content", entryID))
  865. if err != nil {
  866. return "", err
  867. }
  868. defer body.Close()
  869. var response struct {
  870. Content string `json:"content"`
  871. }
  872. if err := json.NewDecoder(body).Decode(&response); err != nil {
  873. return "", fmt.Errorf("miniflux: response error (%v)", err)
  874. }
  875. return response.Content, nil
  876. }
  877. // FetchCounters fetches feed counters.
  878. func (c *Client) FetchCounters() (*FeedCounters, error) {
  879. ctx, cancel := withDefaultTimeout()
  880. defer cancel()
  881. return c.FetchCountersContext(ctx)
  882. }
  883. // FetchCountersContext fetches feed counters.
  884. func (c *Client) FetchCountersContext(ctx context.Context) (*FeedCounters, error) {
  885. body, err := c.request.Get(ctx, "/v1/feeds/counters")
  886. if err != nil {
  887. return nil, err
  888. }
  889. defer body.Close()
  890. var result FeedCounters
  891. if err := json.NewDecoder(body).Decode(&result); err != nil {
  892. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  893. }
  894. return &result, nil
  895. }
  896. // FlushHistory changes all entries with the status "read" to "removed".
  897. func (c *Client) FlushHistory() error {
  898. ctx, cancel := withDefaultTimeout()
  899. defer cancel()
  900. return c.FlushHistoryContext(ctx)
  901. }
  902. // FlushHistoryContext changes all entries with the status "read" to "removed".
  903. func (c *Client) FlushHistoryContext(ctx context.Context) error {
  904. _, err := c.request.Put(ctx, "/v1/flush-history", nil)
  905. return err
  906. }
  907. // Icon fetches a feed icon.
  908. func (c *Client) Icon(iconID int64) (*FeedIcon, error) {
  909. ctx, cancel := withDefaultTimeout()
  910. defer cancel()
  911. return c.IconContext(ctx, iconID)
  912. }
  913. // IconContext fetches a feed icon.
  914. func (c *Client) IconContext(ctx context.Context, iconID int64) (*FeedIcon, error) {
  915. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/icons/%d", iconID))
  916. if err != nil {
  917. return nil, err
  918. }
  919. defer body.Close()
  920. var feedIcon *FeedIcon
  921. if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
  922. return nil, fmt.Errorf("miniflux: response error (%v)", err)
  923. }
  924. return feedIcon, nil
  925. }
  926. // Enclosure fetches a specific enclosure.
  927. func (c *Client) Enclosure(enclosureID int64) (*Enclosure, error) {
  928. ctx, cancel := withDefaultTimeout()
  929. defer cancel()
  930. return c.EnclosureContext(ctx, enclosureID)
  931. }
  932. // EnclosureContext fetches a specific enclosure.
  933. func (c *Client) EnclosureContext(ctx context.Context, enclosureID int64) (*Enclosure, error) {
  934. body, err := c.request.Get(ctx, fmt.Sprintf("/v1/enclosures/%d", enclosureID))
  935. if err != nil {
  936. return nil, err
  937. }
  938. defer body.Close()
  939. var enclosure *Enclosure
  940. if err := json.NewDecoder(body).Decode(&enclosure); err != nil {
  941. return nil, fmt.Errorf("miniflux: response error(%v)", err)
  942. }
  943. return enclosure, nil
  944. }
  945. // UpdateEnclosure updates an enclosure.
  946. func (c *Client) UpdateEnclosure(enclosureID int64, enclosureUpdate *EnclosureUpdateRequest) error {
  947. ctx, cancel := withDefaultTimeout()
  948. defer cancel()
  949. return c.UpdateEnclosureContext(ctx, enclosureID, enclosureUpdate)
  950. }
  951. // UpdateEnclosureContext updates an enclosure.
  952. func (c *Client) UpdateEnclosureContext(ctx context.Context, enclosureID int64, enclosureUpdate *EnclosureUpdateRequest) error {
  953. _, err := c.request.Put(ctx, fmt.Sprintf("/v1/enclosures/%d", enclosureID), enclosureUpdate)
  954. return err
  955. }
  956. func buildFilterQueryString(path string, filter *Filter) string {
  957. if filter != nil {
  958. values := url.Values{}
  959. if filter.Status != "" {
  960. values.Set("status", filter.Status)
  961. }
  962. if filter.Direction != "" {
  963. values.Set("direction", filter.Direction)
  964. }
  965. if filter.Order != "" {
  966. values.Set("order", filter.Order)
  967. }
  968. if filter.Limit >= 0 {
  969. values.Set("limit", strconv.Itoa(filter.Limit))
  970. }
  971. if filter.Offset >= 0 {
  972. values.Set("offset", strconv.Itoa(filter.Offset))
  973. }
  974. if filter.After > 0 {
  975. values.Set("after", strconv.FormatInt(filter.After, 10))
  976. }
  977. if filter.Before > 0 {
  978. values.Set("before", strconv.FormatInt(filter.Before, 10))
  979. }
  980. if filter.PublishedAfter > 0 {
  981. values.Set("published_after", strconv.FormatInt(filter.PublishedAfter, 10))
  982. }
  983. if filter.PublishedBefore > 0 {
  984. values.Set("published_before", strconv.FormatInt(filter.PublishedBefore, 10))
  985. }
  986. if filter.ChangedAfter > 0 {
  987. values.Set("changed_after", strconv.FormatInt(filter.ChangedAfter, 10))
  988. }
  989. if filter.ChangedBefore > 0 {
  990. values.Set("changed_before", strconv.FormatInt(filter.ChangedBefore, 10))
  991. }
  992. if filter.AfterEntryID > 0 {
  993. values.Set("after_entry_id", strconv.FormatInt(filter.AfterEntryID, 10))
  994. }
  995. if filter.BeforeEntryID > 0 {
  996. values.Set("before_entry_id", strconv.FormatInt(filter.BeforeEntryID, 10))
  997. }
  998. if filter.Starred != "" {
  999. values.Set("starred", filter.Starred)
  1000. }
  1001. if filter.Search != "" {
  1002. values.Set("search", filter.Search)
  1003. }
  1004. if filter.CategoryID > 0 {
  1005. values.Set("category_id", strconv.FormatInt(filter.CategoryID, 10))
  1006. }
  1007. if filter.FeedID > 0 {
  1008. values.Set("feed_id", strconv.FormatInt(filter.FeedID, 10))
  1009. }
  1010. if filter.GloballyVisible {
  1011. values.Set("globally_visible", "true")
  1012. }
  1013. for _, status := range filter.Statuses {
  1014. values.Add("status", status)
  1015. }
  1016. path = fmt.Sprintf("%s?%s", path, values.Encode())
  1017. }
  1018. return path
  1019. }