integration.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package integration // import "miniflux.app/v2/internal/integration"
  4. import (
  5. "log/slog"
  6. "miniflux.app/v2/internal/config"
  7. "miniflux.app/v2/internal/integration/apprise"
  8. "miniflux.app/v2/internal/integration/espial"
  9. "miniflux.app/v2/internal/integration/instapaper"
  10. "miniflux.app/v2/internal/integration/linkace"
  11. "miniflux.app/v2/internal/integration/linkding"
  12. "miniflux.app/v2/internal/integration/matrixbot"
  13. "miniflux.app/v2/internal/integration/notion"
  14. "miniflux.app/v2/internal/integration/nunuxkeeper"
  15. "miniflux.app/v2/internal/integration/omnivore"
  16. "miniflux.app/v2/internal/integration/pinboard"
  17. "miniflux.app/v2/internal/integration/pocket"
  18. "miniflux.app/v2/internal/integration/readwise"
  19. "miniflux.app/v2/internal/integration/shaarli"
  20. "miniflux.app/v2/internal/integration/shiori"
  21. "miniflux.app/v2/internal/integration/telegrambot"
  22. "miniflux.app/v2/internal/integration/wallabag"
  23. "miniflux.app/v2/internal/integration/webhook"
  24. "miniflux.app/v2/internal/model"
  25. )
  26. // SendEntry sends the entry to third-party providers when the user click on "Save".
  27. func SendEntry(entry *model.Entry, userIntegrations *model.Integration) {
  28. if userIntegrations.PinboardEnabled {
  29. slog.Debug("Sending entry to Pinboard",
  30. slog.Int64("user_id", userIntegrations.UserID),
  31. slog.Int64("entry_id", entry.ID),
  32. slog.String("entry_url", entry.URL),
  33. )
  34. client := pinboard.NewClient(userIntegrations.PinboardToken)
  35. err := client.CreateBookmark(
  36. entry.URL,
  37. entry.Title,
  38. userIntegrations.PinboardTags,
  39. userIntegrations.PinboardMarkAsUnread,
  40. )
  41. if err != nil {
  42. slog.Error("Unable to send entry to Pinboard",
  43. slog.Int64("user_id", userIntegrations.UserID),
  44. slog.Int64("entry_id", entry.ID),
  45. slog.String("entry_url", entry.URL),
  46. slog.Any("error", err),
  47. )
  48. }
  49. }
  50. if userIntegrations.InstapaperEnabled {
  51. slog.Debug("Sending entry to Instapaper",
  52. slog.Int64("user_id", userIntegrations.UserID),
  53. slog.Int64("entry_id", entry.ID),
  54. slog.String("entry_url", entry.URL),
  55. )
  56. client := instapaper.NewClient(userIntegrations.InstapaperUsername, userIntegrations.InstapaperPassword)
  57. if err := client.AddURL(entry.URL, entry.Title); err != nil {
  58. slog.Error("Unable to send entry to Instapaper",
  59. slog.Int64("user_id", userIntegrations.UserID),
  60. slog.Int64("entry_id", entry.ID),
  61. slog.String("entry_url", entry.URL),
  62. slog.Any("error", err),
  63. )
  64. }
  65. }
  66. if userIntegrations.WallabagEnabled {
  67. slog.Debug("Sending entry to Wallabag",
  68. slog.Int64("user_id", userIntegrations.UserID),
  69. slog.Int64("entry_id", entry.ID),
  70. slog.String("entry_url", entry.URL),
  71. )
  72. client := wallabag.NewClient(
  73. userIntegrations.WallabagURL,
  74. userIntegrations.WallabagClientID,
  75. userIntegrations.WallabagClientSecret,
  76. userIntegrations.WallabagUsername,
  77. userIntegrations.WallabagPassword,
  78. userIntegrations.WallabagOnlyURL,
  79. )
  80. if err := client.CreateEntry(entry.URL, entry.Title, entry.Content); err != nil {
  81. slog.Error("Unable to send entry to Wallabag",
  82. slog.Int64("user_id", userIntegrations.UserID),
  83. slog.Int64("entry_id", entry.ID),
  84. slog.String("entry_url", entry.URL),
  85. slog.Any("error", err),
  86. )
  87. }
  88. }
  89. if userIntegrations.NotionEnabled {
  90. slog.Debug("Sending entry to Notion",
  91. slog.Int64("user_id", userIntegrations.UserID),
  92. slog.Int64("entry_id", entry.ID),
  93. slog.String("entry_url", entry.URL),
  94. )
  95. client := notion.NewClient(
  96. userIntegrations.NotionToken,
  97. userIntegrations.NotionPageID,
  98. )
  99. if err := client.UpdateDocument(entry.URL, entry.Title); err != nil {
  100. slog.Error("Unable to send entry to Notion",
  101. slog.Int64("user_id", userIntegrations.UserID),
  102. slog.Int64("entry_id", entry.ID),
  103. slog.String("entry_url", entry.URL),
  104. slog.Any("error", err),
  105. )
  106. }
  107. }
  108. if userIntegrations.NunuxKeeperEnabled {
  109. slog.Debug("Sending entry to NunuxKeeper",
  110. slog.Int64("user_id", userIntegrations.UserID),
  111. slog.Int64("entry_id", entry.ID),
  112. slog.String("entry_url", entry.URL),
  113. )
  114. client := nunuxkeeper.NewClient(
  115. userIntegrations.NunuxKeeperURL,
  116. userIntegrations.NunuxKeeperAPIKey,
  117. )
  118. if err := client.AddEntry(entry.URL, entry.Title, entry.Content); err != nil {
  119. slog.Error("Unable to send entry to NunuxKeeper",
  120. slog.Int64("user_id", userIntegrations.UserID),
  121. slog.Int64("entry_id", entry.ID),
  122. slog.String("entry_url", entry.URL),
  123. slog.Any("error", err),
  124. )
  125. }
  126. }
  127. if userIntegrations.EspialEnabled {
  128. slog.Debug("Sending entry to Espial",
  129. slog.Int64("user_id", userIntegrations.UserID),
  130. slog.Int64("entry_id", entry.ID),
  131. slog.String("entry_url", entry.URL),
  132. )
  133. client := espial.NewClient(
  134. userIntegrations.EspialURL,
  135. userIntegrations.EspialAPIKey,
  136. )
  137. if err := client.CreateLink(entry.URL, entry.Title, userIntegrations.EspialTags); err != nil {
  138. slog.Error("Unable to send entry to Espial",
  139. slog.Int64("user_id", userIntegrations.UserID),
  140. slog.Int64("entry_id", entry.ID),
  141. slog.String("entry_url", entry.URL),
  142. slog.Any("error", err),
  143. )
  144. }
  145. }
  146. if userIntegrations.PocketEnabled {
  147. slog.Debug("Sending entry to Pocket",
  148. slog.Int64("user_id", userIntegrations.UserID),
  149. slog.Int64("entry_id", entry.ID),
  150. slog.String("entry_url", entry.URL),
  151. )
  152. client := pocket.NewClient(config.Opts.PocketConsumerKey(userIntegrations.PocketConsumerKey), userIntegrations.PocketAccessToken)
  153. if err := client.AddURL(entry.URL, entry.Title); err != nil {
  154. slog.Error("Unable to send entry to Pocket",
  155. slog.Int64("user_id", userIntegrations.UserID),
  156. slog.Int64("entry_id", entry.ID),
  157. slog.String("entry_url", entry.URL),
  158. slog.Any("error", err),
  159. )
  160. }
  161. }
  162. if userIntegrations.LinkAceEnabled {
  163. slog.Debug("Sending entry to LinkAce",
  164. slog.Int64("user_id", userIntegrations.UserID),
  165. slog.Int64("entry_id", entry.ID),
  166. slog.String("entry_url", entry.URL),
  167. )
  168. client := linkace.NewClient(
  169. userIntegrations.LinkAceURL,
  170. userIntegrations.LinkAceAPIKey,
  171. userIntegrations.LinkAceTags,
  172. userIntegrations.LinkAcePrivate,
  173. userIntegrations.LinkAceCheckDisabled,
  174. )
  175. if err := client.AddURL(entry.URL, entry.Title); err != nil {
  176. slog.Error("Unable to send entry to LinkAce",
  177. slog.Int64("user_id", userIntegrations.UserID),
  178. slog.Int64("entry_id", entry.ID),
  179. slog.String("entry_url", entry.URL),
  180. slog.Any("error", err),
  181. )
  182. }
  183. }
  184. if userIntegrations.LinkdingEnabled {
  185. slog.Debug("Sending entry to Linkding",
  186. slog.Int64("user_id", userIntegrations.UserID),
  187. slog.Int64("entry_id", entry.ID),
  188. slog.String("entry_url", entry.URL),
  189. )
  190. client := linkding.NewClient(
  191. userIntegrations.LinkdingURL,
  192. userIntegrations.LinkdingAPIKey,
  193. userIntegrations.LinkdingTags,
  194. userIntegrations.LinkdingMarkAsUnread,
  195. )
  196. if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
  197. slog.Error("Unable to send entry to Linkding",
  198. slog.Int64("user_id", userIntegrations.UserID),
  199. slog.Int64("entry_id", entry.ID),
  200. slog.String("entry_url", entry.URL),
  201. slog.Any("error", err),
  202. )
  203. }
  204. }
  205. if userIntegrations.ReadwiseEnabled {
  206. slog.Debug("Sending entry to Readwise",
  207. slog.Int64("user_id", userIntegrations.UserID),
  208. slog.Int64("entry_id", entry.ID),
  209. slog.String("entry_url", entry.URL),
  210. )
  211. client := readwise.NewClient(
  212. userIntegrations.ReadwiseAPIKey,
  213. )
  214. if err := client.CreateDocument(entry.URL); err != nil {
  215. slog.Error("Unable to send entry to Readwise",
  216. slog.Int64("user_id", userIntegrations.UserID),
  217. slog.Int64("entry_id", entry.ID),
  218. slog.String("entry_url", entry.URL),
  219. slog.Any("error", err),
  220. )
  221. }
  222. }
  223. if userIntegrations.ShioriEnabled {
  224. slog.Debug("Sending entry to Shiori",
  225. slog.Int64("user_id", userIntegrations.UserID),
  226. slog.Int64("entry_id", entry.ID),
  227. slog.String("entry_url", entry.URL),
  228. )
  229. client := shiori.NewClient(
  230. userIntegrations.ShioriURL,
  231. userIntegrations.ShioriUsername,
  232. userIntegrations.ShioriPassword,
  233. )
  234. if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
  235. slog.Error("Unable to send entry to Shiori",
  236. slog.Int64("user_id", userIntegrations.UserID),
  237. slog.Int64("entry_id", entry.ID),
  238. slog.String("entry_url", entry.URL),
  239. slog.Any("error", err),
  240. )
  241. }
  242. }
  243. if userIntegrations.ShaarliEnabled {
  244. slog.Debug("Sending entry to Shaarli",
  245. slog.Int64("user_id", userIntegrations.UserID),
  246. slog.Int64("entry_id", entry.ID),
  247. slog.String("entry_url", entry.URL),
  248. )
  249. client := shaarli.NewClient(
  250. userIntegrations.ShaarliURL,
  251. userIntegrations.ShaarliAPISecret,
  252. )
  253. if err := client.CreateLink(entry.URL, entry.Title); err != nil {
  254. slog.Error("Unable to send entry to Shaarli",
  255. slog.Int64("user_id", userIntegrations.UserID),
  256. slog.Int64("entry_id", entry.ID),
  257. slog.String("entry_url", entry.URL),
  258. slog.Any("error", err),
  259. )
  260. }
  261. }
  262. if userIntegrations.WebhookEnabled {
  263. slog.Debug("Sending entry to Webhook",
  264. slog.Int64("user_id", userIntegrations.UserID),
  265. slog.Int64("entry_id", entry.ID),
  266. slog.String("entry_url", entry.URL),
  267. slog.String("webhook_url", userIntegrations.WebhookURL),
  268. )
  269. webhookClient := webhook.NewClient(userIntegrations.WebhookURL, userIntegrations.WebhookSecret)
  270. if err := webhookClient.SendSaveEntryWebhookEvent(entry); err != nil {
  271. slog.Error("Unable to send entry to Webhook",
  272. slog.Int64("user_id", userIntegrations.UserID),
  273. slog.Int64("entry_id", entry.ID),
  274. slog.String("entry_url", entry.URL),
  275. slog.String("webhook_url", userIntegrations.WebhookURL),
  276. slog.Any("error", err),
  277. )
  278. }
  279. }
  280. if userIntegrations.OmnivoreEnabled {
  281. slog.Debug("Sending entry to Omnivore",
  282. slog.Int64("user_id", userIntegrations.UserID),
  283. slog.Int64("entry_id", entry.ID),
  284. slog.String("entry_url", entry.URL),
  285. )
  286. client := omnivore.NewClient(userIntegrations.OmnivoreAPIKey, userIntegrations.OmnivoreURL)
  287. if err := client.SaveUrl(entry.URL); err != nil {
  288. slog.Error("Unable to send entry to Omnivore",
  289. slog.Int64("user_id", userIntegrations.UserID),
  290. slog.Int64("entry_id", entry.ID),
  291. slog.String("entry_url", entry.URL),
  292. slog.Any("error", err),
  293. )
  294. }
  295. }
  296. }
  297. // PushEntries pushes a list of entries to activated third-party providers during feed refreshes.
  298. func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *model.Integration) {
  299. if userIntegrations.MatrixBotEnabled {
  300. slog.Debug("Sending new entries to Matrix",
  301. slog.Int64("user_id", userIntegrations.UserID),
  302. slog.Int("nb_entries", len(entries)),
  303. slog.Int64("feed_id", feed.ID),
  304. )
  305. err := matrixbot.PushEntries(
  306. feed,
  307. entries,
  308. userIntegrations.MatrixBotURL,
  309. userIntegrations.MatrixBotUser,
  310. userIntegrations.MatrixBotPassword,
  311. userIntegrations.MatrixBotChatID,
  312. )
  313. if err != nil {
  314. slog.Error("Unable to send new entries to Matrix",
  315. slog.Int64("user_id", userIntegrations.UserID),
  316. slog.Int("nb_entries", len(entries)),
  317. slog.Int64("feed_id", feed.ID),
  318. slog.Any("error", err),
  319. )
  320. }
  321. }
  322. if userIntegrations.WebhookEnabled {
  323. slog.Debug("Sending new entries to Webhook",
  324. slog.Int64("user_id", userIntegrations.UserID),
  325. slog.Int("nb_entries", len(entries)),
  326. slog.Int64("feed_id", feed.ID),
  327. slog.String("webhook_url", userIntegrations.WebhookURL),
  328. )
  329. webhookClient := webhook.NewClient(userIntegrations.WebhookURL, userIntegrations.WebhookSecret)
  330. if err := webhookClient.SendNewEntriesWebhookEvent(feed, entries); err != nil {
  331. slog.Debug("Unable to send new entries to Webhook",
  332. slog.Int64("user_id", userIntegrations.UserID),
  333. slog.Int("nb_entries", len(entries)),
  334. slog.Int64("feed_id", feed.ID),
  335. slog.String("webhook_url", userIntegrations.WebhookURL),
  336. slog.Any("error", err),
  337. )
  338. }
  339. }
  340. // Integrations that only support sending individual entries
  341. if userIntegrations.TelegramBotEnabled || userIntegrations.AppriseEnabled {
  342. for _, entry := range entries {
  343. if userIntegrations.TelegramBotEnabled {
  344. slog.Debug("Sending a new entry to Telegram",
  345. slog.Int64("user_id", userIntegrations.UserID),
  346. slog.Int64("entry_id", entry.ID),
  347. slog.String("entry_url", entry.URL),
  348. )
  349. if err := telegrambot.PushEntry(
  350. feed,
  351. entry,
  352. userIntegrations.TelegramBotToken,
  353. userIntegrations.TelegramBotChatID,
  354. userIntegrations.TelegramBotTopicID,
  355. userIntegrations.TelegramBotDisableWebPagePreview,
  356. userIntegrations.TelegramBotDisableNotification,
  357. userIntegrations.TelegramBotDisableButtons,
  358. ); err != nil {
  359. slog.Error("Unable to send entry to Telegram",
  360. slog.Int64("user_id", userIntegrations.UserID),
  361. slog.Int64("entry_id", entry.ID),
  362. slog.String("entry_url", entry.URL),
  363. slog.Any("error", err),
  364. )
  365. }
  366. }
  367. if userIntegrations.AppriseEnabled {
  368. slog.Debug("Sending a new entry to Apprise",
  369. slog.Int64("user_id", userIntegrations.UserID),
  370. slog.Int64("entry_id", entry.ID),
  371. slog.String("entry_url", entry.URL),
  372. slog.String("apprise_url", userIntegrations.AppriseURL),
  373. )
  374. appriseServiceURLs := userIntegrations.AppriseServicesURL
  375. if feed.AppriseServiceURLs != "" {
  376. appriseServiceURLs = feed.AppriseServiceURLs
  377. }
  378. client := apprise.NewClient(
  379. appriseServiceURLs,
  380. userIntegrations.AppriseURL,
  381. )
  382. if err := client.SendNotification(entry); err != nil {
  383. slog.Error("Unable to send entry to Apprise",
  384. slog.Int64("user_id", userIntegrations.UserID),
  385. slog.Int64("entry_id", entry.ID),
  386. slog.String("entry_url", entry.URL),
  387. slog.String("apprise_url", userIntegrations.AppriseURL),
  388. slog.Any("error", err),
  389. )
  390. }
  391. }
  392. }
  393. }
  394. }