integration.go 21 KB

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