integration.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  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/integration/apprise"
  7. "miniflux.app/v2/internal/integration/archiveorg"
  8. "miniflux.app/v2/internal/integration/betula"
  9. "miniflux.app/v2/internal/integration/cubox"
  10. "miniflux.app/v2/internal/integration/discord"
  11. "miniflux.app/v2/internal/integration/espial"
  12. "miniflux.app/v2/internal/integration/gotify"
  13. "miniflux.app/v2/internal/integration/instapaper"
  14. "miniflux.app/v2/internal/integration/karakeep"
  15. "miniflux.app/v2/internal/integration/linkace"
  16. "miniflux.app/v2/internal/integration/linkding"
  17. "miniflux.app/v2/internal/integration/linktaco"
  18. "miniflux.app/v2/internal/integration/linkwarden"
  19. "miniflux.app/v2/internal/integration/matrixbot"
  20. "miniflux.app/v2/internal/integration/notion"
  21. "miniflux.app/v2/internal/integration/ntfy"
  22. "miniflux.app/v2/internal/integration/nunuxkeeper"
  23. "miniflux.app/v2/internal/integration/omnivore"
  24. "miniflux.app/v2/internal/integration/pinboard"
  25. "miniflux.app/v2/internal/integration/pushover"
  26. "miniflux.app/v2/internal/integration/raindrop"
  27. "miniflux.app/v2/internal/integration/readeck"
  28. "miniflux.app/v2/internal/integration/readwise"
  29. "miniflux.app/v2/internal/integration/shaarli"
  30. "miniflux.app/v2/internal/integration/shiori"
  31. "miniflux.app/v2/internal/integration/slack"
  32. "miniflux.app/v2/internal/integration/telegrambot"
  33. "miniflux.app/v2/internal/integration/wallabag"
  34. "miniflux.app/v2/internal/integration/webhook"
  35. "miniflux.app/v2/internal/model"
  36. )
  37. // SendEntry sends the entry to third-party providers when the user click on "Save".
  38. func SendEntry(entry *model.Entry, userIntegrations *model.Integration) {
  39. if userIntegrations.BetulaEnabled {
  40. slog.Debug("Sending entry to Betula",
  41. slog.Int64("user_id", userIntegrations.UserID),
  42. slog.Int64("entry_id", entry.ID),
  43. slog.String("entry_url", entry.URL),
  44. )
  45. client := betula.NewClient(userIntegrations.BetulaURL, userIntegrations.BetulaToken)
  46. err := client.CreateBookmark(
  47. entry.URL,
  48. entry.Title,
  49. entry.Tags,
  50. )
  51. if err != nil {
  52. slog.Error("Unable to send entry to Betula",
  53. slog.Int64("user_id", userIntegrations.UserID),
  54. slog.Int64("entry_id", entry.ID),
  55. slog.String("entry_url", entry.URL),
  56. slog.Any("error", err),
  57. )
  58. }
  59. }
  60. if userIntegrations.PinboardEnabled {
  61. slog.Debug("Sending entry to Pinboard",
  62. slog.Int64("user_id", userIntegrations.UserID),
  63. slog.Int64("entry_id", entry.ID),
  64. slog.String("entry_url", entry.URL),
  65. )
  66. client := pinboard.NewClient(userIntegrations.PinboardToken)
  67. err := client.CreateBookmark(
  68. entry.URL,
  69. entry.Title,
  70. userIntegrations.PinboardTags,
  71. userIntegrations.PinboardMarkAsUnread,
  72. )
  73. if err != nil {
  74. slog.Error("Unable to send entry to Pinboard",
  75. slog.Int64("user_id", userIntegrations.UserID),
  76. slog.Int64("entry_id", entry.ID),
  77. slog.String("entry_url", entry.URL),
  78. slog.Any("error", err),
  79. )
  80. }
  81. }
  82. if userIntegrations.InstapaperEnabled {
  83. slog.Debug("Sending entry to Instapaper",
  84. slog.Int64("user_id", userIntegrations.UserID),
  85. slog.Int64("entry_id", entry.ID),
  86. slog.String("entry_url", entry.URL),
  87. )
  88. client := instapaper.NewClient(userIntegrations.InstapaperUsername, userIntegrations.InstapaperPassword)
  89. if err := client.AddURL(entry.URL, entry.Title); err != nil {
  90. slog.Error("Unable to send entry to Instapaper",
  91. slog.Int64("user_id", userIntegrations.UserID),
  92. slog.Int64("entry_id", entry.ID),
  93. slog.String("entry_url", entry.URL),
  94. slog.Any("error", err),
  95. )
  96. }
  97. }
  98. if userIntegrations.WallabagEnabled {
  99. slog.Debug("Sending entry to Wallabag",
  100. slog.Int64("user_id", userIntegrations.UserID),
  101. slog.String("user_tags", userIntegrations.WallabagTags),
  102. slog.Int64("entry_id", entry.ID),
  103. slog.String("entry_url", entry.URL),
  104. )
  105. client := wallabag.NewClient(
  106. userIntegrations.WallabagURL,
  107. userIntegrations.WallabagClientID,
  108. userIntegrations.WallabagClientSecret,
  109. userIntegrations.WallabagUsername,
  110. userIntegrations.WallabagPassword,
  111. userIntegrations.WallabagTags,
  112. userIntegrations.WallabagOnlyURL,
  113. )
  114. if err := client.CreateEntry(entry.URL, entry.Title, entry.Content); err != nil {
  115. slog.Error("Unable to send entry to Wallabag",
  116. slog.Int64("user_id", userIntegrations.UserID),
  117. slog.String("user_tags", userIntegrations.WallabagTags),
  118. slog.Int64("entry_id", entry.ID),
  119. slog.String("entry_url", entry.URL),
  120. slog.Any("error", err),
  121. )
  122. }
  123. }
  124. if userIntegrations.NotionEnabled {
  125. slog.Debug("Sending entry to Notion",
  126. slog.Int64("user_id", userIntegrations.UserID),
  127. slog.Int64("entry_id", entry.ID),
  128. slog.String("entry_url", entry.URL),
  129. )
  130. client := notion.NewClient(
  131. userIntegrations.NotionToken,
  132. userIntegrations.NotionPageID,
  133. )
  134. if err := client.UpdateDocument(entry.URL, entry.Title); err != nil {
  135. slog.Error("Unable to send entry to Notion",
  136. slog.Int64("user_id", userIntegrations.UserID),
  137. slog.Int64("entry_id", entry.ID),
  138. slog.String("entry_url", entry.URL),
  139. slog.Any("error", err),
  140. )
  141. }
  142. }
  143. if userIntegrations.NunuxKeeperEnabled {
  144. slog.Debug("Sending entry to NunuxKeeper",
  145. slog.Int64("user_id", userIntegrations.UserID),
  146. slog.Int64("entry_id", entry.ID),
  147. slog.String("entry_url", entry.URL),
  148. )
  149. client := nunuxkeeper.NewClient(
  150. userIntegrations.NunuxKeeperURL,
  151. userIntegrations.NunuxKeeperAPIKey,
  152. )
  153. if err := client.AddEntry(entry.URL, entry.Title, entry.Content); err != nil {
  154. slog.Error("Unable to send entry to NunuxKeeper",
  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.EspialEnabled {
  163. slog.Debug("Sending entry to Espial",
  164. slog.Int64("user_id", userIntegrations.UserID),
  165. slog.Int64("entry_id", entry.ID),
  166. slog.String("entry_url", entry.URL),
  167. )
  168. client := espial.NewClient(
  169. userIntegrations.EspialURL,
  170. userIntegrations.EspialAPIKey,
  171. )
  172. if err := client.CreateLink(entry.URL, entry.Title, userIntegrations.EspialTags); err != nil {
  173. slog.Error("Unable to send entry to Espial",
  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.LinkAceEnabled {
  182. slog.Debug("Sending entry to LinkAce",
  183. slog.Int64("user_id", userIntegrations.UserID),
  184. slog.Int64("entry_id", entry.ID),
  185. slog.String("entry_url", entry.URL),
  186. )
  187. client := linkace.NewClient(
  188. userIntegrations.LinkAceURL,
  189. userIntegrations.LinkAceAPIKey,
  190. userIntegrations.LinkAceTags,
  191. userIntegrations.LinkAcePrivate,
  192. userIntegrations.LinkAceCheckDisabled,
  193. )
  194. if err := client.AddURL(entry.URL, entry.Title); err != nil {
  195. slog.Error("Unable to send entry to LinkAce",
  196. slog.Int64("user_id", userIntegrations.UserID),
  197. slog.Int64("entry_id", entry.ID),
  198. slog.String("entry_url", entry.URL),
  199. slog.Any("error", err),
  200. )
  201. }
  202. }
  203. if userIntegrations.LinkdingEnabled {
  204. slog.Debug("Sending entry to Linkding",
  205. slog.Int64("user_id", userIntegrations.UserID),
  206. slog.Int64("entry_id", entry.ID),
  207. slog.String("entry_url", entry.URL),
  208. )
  209. client := linkding.NewClient(
  210. userIntegrations.LinkdingURL,
  211. userIntegrations.LinkdingAPIKey,
  212. userIntegrations.LinkdingTags,
  213. userIntegrations.LinkdingMarkAsUnread,
  214. )
  215. if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
  216. slog.Error("Unable to send entry to Linkding",
  217. slog.Int64("user_id", userIntegrations.UserID),
  218. slog.Int64("entry_id", entry.ID),
  219. slog.String("entry_url", entry.URL),
  220. slog.Any("error", err),
  221. )
  222. }
  223. }
  224. if userIntegrations.LinktacoEnabled {
  225. slog.Debug("Sending entry to LinkTaco",
  226. slog.Int64("user_id", userIntegrations.UserID),
  227. slog.Int64("entry_id", entry.ID),
  228. slog.String("entry_url", entry.URL),
  229. )
  230. client := linktaco.NewClient(
  231. userIntegrations.LinktacoAPIToken,
  232. userIntegrations.LinktacoOrgSlug,
  233. userIntegrations.LinktacoTags,
  234. userIntegrations.LinktacoVisibility,
  235. )
  236. if err := client.CreateBookmark(entry.URL, entry.Title, entry.Content); err != nil {
  237. slog.Error("Unable to send entry to LinkTaco",
  238. slog.Int64("user_id", userIntegrations.UserID),
  239. slog.Int64("entry_id", entry.ID),
  240. slog.String("entry_url", entry.URL),
  241. slog.Any("error", err),
  242. )
  243. }
  244. }
  245. if userIntegrations.LinkwardenEnabled {
  246. attrs := []any{
  247. slog.Int64("user_id", userIntegrations.UserID),
  248. slog.Int64("entry_id", entry.ID),
  249. slog.String("entry_url", entry.URL),
  250. }
  251. if userIntegrations.LinkwardenCollectionID != nil {
  252. attrs = append(attrs, slog.Int64("collection_id", *userIntegrations.LinkwardenCollectionID))
  253. }
  254. slog.Debug("Sending entry to linkwarden", attrs...)
  255. client := linkwarden.NewClient(
  256. userIntegrations.LinkwardenURL,
  257. userIntegrations.LinkwardenAPIKey,
  258. userIntegrations.LinkwardenCollectionID,
  259. )
  260. if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
  261. attrs = append(attrs, slog.Any("error", err))
  262. slog.Error("Unable to send entry to Linkwarden", attrs...)
  263. }
  264. }
  265. if userIntegrations.ReadeckEnabled {
  266. slog.Debug("Sending entry to Readeck",
  267. slog.Int64("user_id", userIntegrations.UserID),
  268. slog.Int64("entry_id", entry.ID),
  269. slog.String("entry_url", entry.URL),
  270. )
  271. client := readeck.NewClient(
  272. userIntegrations.ReadeckURL,
  273. userIntegrations.ReadeckAPIKey,
  274. userIntegrations.ReadeckLabels,
  275. userIntegrations.ReadeckOnlyURL,
  276. )
  277. if err := client.CreateBookmark(entry.URL, entry.Title, entry.Content); err != nil {
  278. slog.Error("Unable to send entry to Readeck",
  279. slog.Int64("user_id", userIntegrations.UserID),
  280. slog.Int64("entry_id", entry.ID),
  281. slog.String("entry_url", entry.URL),
  282. slog.Any("error", err),
  283. )
  284. }
  285. }
  286. if userIntegrations.ReadwiseEnabled {
  287. slog.Debug("Sending entry to Readwise",
  288. slog.Int64("user_id", userIntegrations.UserID),
  289. slog.Int64("entry_id", entry.ID),
  290. slog.String("entry_url", entry.URL),
  291. )
  292. client := readwise.NewClient(
  293. userIntegrations.ReadwiseAPIKey,
  294. )
  295. if err := client.CreateDocument(entry.URL); err != nil {
  296. slog.Error("Unable to send entry to Readwise",
  297. slog.Int64("user_id", userIntegrations.UserID),
  298. slog.Int64("entry_id", entry.ID),
  299. slog.String("entry_url", entry.URL),
  300. slog.Any("error", err),
  301. )
  302. }
  303. }
  304. if userIntegrations.CuboxEnabled {
  305. slog.Debug("Sending entry to Cubox",
  306. slog.Int64("user_id", userIntegrations.UserID),
  307. slog.Int64("entry_id", entry.ID),
  308. slog.String("entry_url", entry.URL),
  309. )
  310. client := cubox.NewClient(userIntegrations.CuboxAPILink)
  311. if err := client.SaveLink(entry.URL); err != nil {
  312. slog.Error("Unable to send entry to Cubox",
  313. slog.Int64("user_id", userIntegrations.UserID),
  314. slog.Int64("entry_id", entry.ID),
  315. slog.String("entry_url", entry.URL),
  316. slog.Any("error", err),
  317. )
  318. }
  319. }
  320. if userIntegrations.ShioriEnabled {
  321. slog.Debug("Sending entry to Shiori",
  322. slog.Int64("user_id", userIntegrations.UserID),
  323. slog.Int64("entry_id", entry.ID),
  324. slog.String("entry_url", entry.URL),
  325. )
  326. client := shiori.NewClient(
  327. userIntegrations.ShioriURL,
  328. userIntegrations.ShioriUsername,
  329. userIntegrations.ShioriPassword,
  330. )
  331. if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
  332. slog.Error("Unable to send entry to Shiori",
  333. slog.Int64("user_id", userIntegrations.UserID),
  334. slog.Int64("entry_id", entry.ID),
  335. slog.String("entry_url", entry.URL),
  336. slog.Any("error", err),
  337. )
  338. }
  339. }
  340. if userIntegrations.ShaarliEnabled {
  341. slog.Debug("Sending entry to Shaarli",
  342. slog.Int64("user_id", userIntegrations.UserID),
  343. slog.Int64("entry_id", entry.ID),
  344. slog.String("entry_url", entry.URL),
  345. )
  346. client := shaarli.NewClient(
  347. userIntegrations.ShaarliURL,
  348. userIntegrations.ShaarliAPISecret,
  349. )
  350. if err := client.CreateLink(entry.URL, entry.Title); err != nil {
  351. slog.Error("Unable to send entry to Shaarli",
  352. slog.Int64("user_id", userIntegrations.UserID),
  353. slog.Int64("entry_id", entry.ID),
  354. slog.String("entry_url", entry.URL),
  355. slog.Any("error", err),
  356. )
  357. }
  358. }
  359. if userIntegrations.ArchiveorgEnabled {
  360. slog.Debug("Sending entry to archive.org",
  361. slog.Int64("user_id", userIntegrations.UserID),
  362. slog.Int64("entry_id", entry.ID),
  363. slog.String("entry_url", entry.URL),
  364. )
  365. if err := archiveorg.NewClient().SendURL(entry.URL); err != nil {
  366. slog.Error("Unable to send entry to Archive.org",
  367. slog.Int64("user_id", userIntegrations.UserID),
  368. slog.Int64("entry_id", entry.ID),
  369. slog.String("entry_url", entry.URL),
  370. slog.Any("error", err),
  371. )
  372. }
  373. }
  374. if userIntegrations.WebhookEnabled {
  375. var webhookURL string
  376. if entry.Feed != nil && entry.Feed.WebhookURL != "" {
  377. webhookURL = entry.Feed.WebhookURL
  378. } else {
  379. webhookURL = userIntegrations.WebhookURL
  380. }
  381. slog.Debug("Sending entry to Webhook",
  382. slog.Int64("user_id", userIntegrations.UserID),
  383. slog.Int64("entry_id", entry.ID),
  384. slog.String("entry_url", entry.URL),
  385. slog.String("webhook_url", webhookURL),
  386. )
  387. webhookClient := webhook.NewClient(webhookURL, userIntegrations.WebhookSecret)
  388. if err := webhookClient.SendSaveEntryWebhookEvent(entry); err != nil {
  389. slog.Error("Unable to send entry to Webhook",
  390. slog.Int64("user_id", userIntegrations.UserID),
  391. slog.Int64("entry_id", entry.ID),
  392. slog.String("entry_url", entry.URL),
  393. slog.String("webhook_url", webhookURL),
  394. slog.Any("error", err),
  395. )
  396. }
  397. }
  398. if userIntegrations.OmnivoreEnabled {
  399. slog.Debug("Sending entry to Omnivore",
  400. slog.Int64("user_id", userIntegrations.UserID),
  401. slog.Int64("entry_id", entry.ID),
  402. slog.String("entry_url", entry.URL),
  403. )
  404. client := omnivore.NewClient(userIntegrations.OmnivoreAPIKey, userIntegrations.OmnivoreURL)
  405. if err := client.SaveURL(entry.URL); err != nil {
  406. slog.Error("Unable to send entry to Omnivore",
  407. slog.Int64("user_id", userIntegrations.UserID),
  408. slog.Int64("entry_id", entry.ID),
  409. slog.String("entry_url", entry.URL),
  410. slog.Any("error", err),
  411. )
  412. }
  413. }
  414. if userIntegrations.KarakeepEnabled {
  415. slog.Debug("Sending entry to Karakeep",
  416. slog.Int64("user_id", userIntegrations.UserID),
  417. slog.String("user_tags", userIntegrations.KarakeepTags),
  418. slog.Int64("entry_id", entry.ID),
  419. slog.String("entry_url", entry.URL),
  420. )
  421. client := karakeep.NewClient(
  422. userIntegrations.KarakeepAPIKey,
  423. userIntegrations.KarakeepURL,
  424. userIntegrations.KarakeepTags,
  425. )
  426. if err := client.SaveURL(entry.URL); err != nil {
  427. slog.Error("Unable to send entry to Karakeep",
  428. slog.Int64("user_id", userIntegrations.UserID),
  429. slog.String("user_tags", userIntegrations.KarakeepTags),
  430. slog.Int64("entry_id", entry.ID),
  431. slog.String("entry_url", entry.URL),
  432. slog.Any("error", err),
  433. )
  434. }
  435. }
  436. if userIntegrations.RaindropEnabled {
  437. slog.Debug("Sending entry to Raindrop",
  438. slog.Int64("user_id", userIntegrations.UserID),
  439. slog.Int64("entry_id", entry.ID),
  440. slog.String("entry_url", entry.URL),
  441. )
  442. client := raindrop.NewClient(userIntegrations.RaindropToken, userIntegrations.RaindropCollectionID, userIntegrations.RaindropTags)
  443. if err := client.CreateRaindrop(entry.URL, entry.Title); err != nil {
  444. slog.Error("Unable to send entry to Raindrop",
  445. slog.Int64("user_id", userIntegrations.UserID),
  446. slog.Int64("entry_id", entry.ID),
  447. slog.String("entry_url", entry.URL),
  448. slog.Any("error", err),
  449. )
  450. }
  451. }
  452. }
  453. // PushEntries pushes a list of entries to activated third-party providers during feed refreshes.
  454. func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *model.Integration) {
  455. if userIntegrations.MatrixBotEnabled {
  456. slog.Debug("Sending new entries to Matrix",
  457. slog.Int64("user_id", userIntegrations.UserID),
  458. slog.Int("nb_entries", len(entries)),
  459. slog.Int64("feed_id", feed.ID),
  460. )
  461. err := matrixbot.PushEntries(
  462. feed,
  463. entries,
  464. userIntegrations.MatrixBotURL,
  465. userIntegrations.MatrixBotUser,
  466. userIntegrations.MatrixBotPassword,
  467. userIntegrations.MatrixBotChatID,
  468. )
  469. if err != nil {
  470. slog.Error("Unable to send new entries to Matrix",
  471. slog.Int64("user_id", userIntegrations.UserID),
  472. slog.Int("nb_entries", len(entries)),
  473. slog.Int64("feed_id", feed.ID),
  474. slog.Any("error", err),
  475. )
  476. }
  477. }
  478. if userIntegrations.WebhookEnabled {
  479. var webhookURL string
  480. if feed.WebhookURL != "" {
  481. webhookURL = feed.WebhookURL
  482. } else {
  483. webhookURL = userIntegrations.WebhookURL
  484. }
  485. slog.Debug("Sending new entries to Webhook",
  486. slog.Int64("user_id", userIntegrations.UserID),
  487. slog.Int("nb_entries", len(entries)),
  488. slog.Int64("feed_id", feed.ID),
  489. slog.String("webhook_url", webhookURL),
  490. )
  491. webhookClient := webhook.NewClient(webhookURL, userIntegrations.WebhookSecret)
  492. if err := webhookClient.SendNewEntriesWebhookEvent(feed, entries); err != nil {
  493. slog.Warn("Unable to send new entries to Webhook",
  494. slog.Int64("user_id", userIntegrations.UserID),
  495. slog.Int("nb_entries", len(entries)),
  496. slog.Int64("feed_id", feed.ID),
  497. slog.String("webhook_url", webhookURL),
  498. slog.Any("error", err),
  499. )
  500. }
  501. }
  502. if userIntegrations.NtfyEnabled && feed.NtfyEnabled {
  503. ntfyTopic := feed.NtfyTopic
  504. if ntfyTopic == "" {
  505. ntfyTopic = userIntegrations.NtfyTopic
  506. }
  507. slog.Debug("Sending new entries to Ntfy",
  508. slog.Int64("user_id", userIntegrations.UserID),
  509. slog.Int("nb_entries", len(entries)),
  510. slog.Int64("feed_id", feed.ID),
  511. slog.String("topic", ntfyTopic),
  512. )
  513. client := ntfy.NewClient(
  514. userIntegrations.NtfyURL,
  515. ntfyTopic,
  516. userIntegrations.NtfyAPIToken,
  517. userIntegrations.NtfyUsername,
  518. userIntegrations.NtfyPassword,
  519. userIntegrations.NtfyIconURL,
  520. userIntegrations.NtfyInternalLinks,
  521. feed.NtfyPriority,
  522. )
  523. if err := client.SendMessages(feed, entries); err != nil {
  524. slog.Warn("Unable to send new entries to Ntfy", slog.Any("error", err))
  525. }
  526. }
  527. if userIntegrations.AppriseEnabled {
  528. slog.Debug("Sending new entries to Apprise",
  529. slog.Int64("user_id", userIntegrations.UserID),
  530. slog.Int("nb_entries", len(entries)),
  531. slog.Int64("feed_id", feed.ID),
  532. )
  533. appriseServiceURLs := userIntegrations.AppriseServicesURL
  534. if feed.AppriseServiceURLs != "" {
  535. appriseServiceURLs = feed.AppriseServiceURLs
  536. }
  537. client := apprise.NewClient(
  538. appriseServiceURLs,
  539. userIntegrations.AppriseURL,
  540. )
  541. if err := client.SendNotification(feed, entries); err != nil {
  542. slog.Warn("Unable to send new entries to Apprise", slog.Any("error", err))
  543. }
  544. }
  545. if userIntegrations.DiscordEnabled {
  546. slog.Debug("Sending new entries to Discord",
  547. slog.Int64("user_id", userIntegrations.UserID),
  548. slog.Int("nb_entries", len(entries)),
  549. slog.Int64("feed_id", feed.ID),
  550. )
  551. client := discord.NewClient(
  552. userIntegrations.DiscordWebhookLink,
  553. )
  554. if err := client.SendDiscordMsg(feed, entries); err != nil {
  555. slog.Warn("Unable to send new entries to Discord", slog.Any("error", err))
  556. }
  557. }
  558. if userIntegrations.SlackEnabled {
  559. slog.Debug("Sending new entries to Slack",
  560. slog.Int64("user_id", userIntegrations.UserID),
  561. slog.Int("nb_entries", len(entries)),
  562. slog.Int64("feed_id", feed.ID),
  563. )
  564. client := slack.NewClient(
  565. userIntegrations.SlackWebhookLink,
  566. )
  567. if err := client.SendSlackMsg(feed, entries); err != nil {
  568. slog.Warn("Unable to send new entries to Slack", slog.Any("error", err))
  569. }
  570. }
  571. if userIntegrations.PushoverEnabled && feed.PushoverEnabled {
  572. slog.Debug("Sending new entries to Pushover",
  573. slog.Int64("user_id", userIntegrations.UserID),
  574. slog.Int("nb_entries", len(entries)),
  575. slog.Int64("feed_id", feed.ID),
  576. )
  577. client := pushover.NewClient(
  578. userIntegrations.PushoverUser,
  579. userIntegrations.PushoverToken,
  580. feed.PushoverPriority,
  581. userIntegrations.PushoverDevice,
  582. userIntegrations.PushoverPrefix,
  583. )
  584. if err := client.SendMessages(feed, entries); err != nil {
  585. slog.Warn("Unable to send new entries to Pushover", slog.Any("error", err))
  586. }
  587. }
  588. if userIntegrations.GotifyEnabled {
  589. slog.Debug("Sending new entries to Gotify",
  590. slog.Int64("user_id", userIntegrations.UserID),
  591. slog.Int("nb_entries", len(entries)),
  592. slog.Int64("feed_id", feed.ID),
  593. )
  594. client := gotify.NewClient(
  595. userIntegrations.GotifyURL,
  596. userIntegrations.GotifyToken,
  597. userIntegrations.GotifyPriority,
  598. )
  599. if err := client.SendMessages(feed, entries); err != nil {
  600. slog.Warn("Unable to send new entries to Gotify", slog.Any("error", err))
  601. }
  602. }
  603. // Integrations that only support sending individual entries
  604. if userIntegrations.TelegramBotEnabled {
  605. for _, entry := range entries {
  606. slog.Debug("Sending a new entry to Telegram",
  607. slog.Int64("user_id", userIntegrations.UserID),
  608. slog.Int64("entry_id", entry.ID),
  609. slog.String("entry_url", entry.URL),
  610. )
  611. if err := telegrambot.PushEntry(
  612. feed,
  613. entry,
  614. userIntegrations.TelegramBotToken,
  615. userIntegrations.TelegramBotChatID,
  616. userIntegrations.TelegramBotTopicID,
  617. userIntegrations.TelegramBotDisableWebPagePreview,
  618. userIntegrations.TelegramBotDisableNotification,
  619. userIntegrations.TelegramBotDisableButtons,
  620. ); err != nil {
  621. slog.Error("Unable to send entry to Telegram",
  622. slog.Int64("user_id", userIntegrations.UserID),
  623. slog.Int64("entry_id", entry.ID),
  624. slog.String("entry_url", entry.URL),
  625. slog.Any("error", err),
  626. )
  627. }
  628. }
  629. }
  630. // Push each new entry to Readeck when push is enabled
  631. if userIntegrations.ReadeckPushEnabled {
  632. client := readeck.NewClient(
  633. userIntegrations.ReadeckURL,
  634. userIntegrations.ReadeckAPIKey,
  635. userIntegrations.ReadeckLabels,
  636. userIntegrations.ReadeckOnlyURL,
  637. )
  638. for _, entry := range entries {
  639. slog.Debug("Sending a new entry to Readeck",
  640. slog.Int64("user_id", userIntegrations.UserID),
  641. slog.Int64("entry_id", entry.ID),
  642. slog.String("entry_url", entry.URL),
  643. )
  644. if err := client.CreateBookmark(entry.URL, entry.Title, entry.Content); err != nil {
  645. slog.Error("Unable to send entry to Readeck",
  646. slog.Int64("user_id", userIntegrations.UserID),
  647. slog.Int64("entry_id", entry.ID),
  648. slog.String("entry_url", entry.URL),
  649. slog.Any("error", err),
  650. )
  651. }
  652. }
  653. }
  654. }