integration.go 13 KB

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