integration.go 12 KB

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