@@ -5,7 +5,7 @@ import Imap from "node-imap";
55import libmime from "libmime" ;
66import minimist from "minimist" ;
77import RSS from "rss" ;
8- import { existsSync , readFileSync , writeFileSync } from "fs" ;
8+ import { existsSync , readFileSync , writeFileSync , mkdirSync } from "fs" ;
99import { simpleParser } from "mailparser" ;
1010import { decrypt } from "../utilities/security.utility.ts" ;
1111import { fileURLToPath } from "url" ;
@@ -62,6 +62,7 @@ interface RSSItemOptions {
6262}
6363
6464interface RSSFeedOptions {
65+ webhook : any ;
6566 title : string ; // Title of the feed
6667 description : string ; // Description of the feed
6768 feed_url : string ; // URL of the RSS feed itself (where it will be published)
@@ -207,7 +208,7 @@ class ImapWatcher {
207208 this . fetchRecentStartupEmails ( ) ;
208209
209210 this . imap . on ( "mail" , ( n ) => {
210- console . log ( `[IMAP] New mail event: ${ n } ` ) ;
211+ console . log ( `[IMAP] New mail event received for feed ${ this . config . feedId } : ${ n } new email(s) ` ) ;
211212 this . fetchNewEmails ( ) ;
212213 } ) ;
213214
@@ -438,15 +439,23 @@ class ImapWatcher {
438439 . map ( ( result ) => ( result as PromiseFulfilledResult < Email > ) . value ) ;
439440
440441 if ( emails . length > 0 ) {
441- console . log ( " [IMAP] Recent emails fetched, updating RSS..." ) ;
442+ console . log ( ` [IMAP] Recent emails fetched for feed ${ this . config . feedId } , updating RSS with ${ emails . length } emails...` ) ;
442443 const rss = buildRSSFromEmailFolder ( emails , this . config ) ;
443444 writeFileSync (
444445 path . join ( __dirname , "../public/feeds" , `${ this . config . feedId } .xml` ) ,
445446 rss ,
446447 ) ;
447- console . log ( "[IMAP] RSS Feed regenerated" ) ;
448+ console . log ( `[IMAP] RSS Feed regenerated for feed ${ this . config . feedId } ` ) ;
449+
450+ // Handle webhook if configured
451+ if ( this . config . webhook ?. enabled && this . config . webhook ?. url ) {
452+ console . log ( `[IMAP] Calling webhook handler for feed ${ this . config . feedId } ` ) ;
453+ this . handleWebhook ( rss ) ;
454+ } else {
455+ console . log ( `[IMAP] Webhook not configured for feed ${ this . config . feedId } - enabled: ${ this . config . webhook ?. enabled } , url: ${ ! ! this . config . webhook ?. url } ` ) ;
456+ }
448457 } else {
449- console . log ( " [IMAP] No valid emails found." ) ;
458+ console . log ( ` [IMAP] No valid emails found for feed ${ this . config . feedId } ` ) ;
450459 }
451460 } ) ;
452461 console . log ( "[IMAP] Completed processing new emails." ) ;
@@ -469,6 +478,97 @@ class ImapWatcher {
469478 this . imap . end ( ) ;
470479 }
471480 }
481+
482+ private async handleWebhook ( rssXml : string ) : Promise < void > {
483+ try {
484+ console . log ( `[IMAP] Webhook handler called for feed ${ this . config . feedId } ` ) ;
485+
486+ if ( ! this . config . webhook ?. enabled || ! this . config . webhook ?. url ) {
487+ console . log ( `[IMAP] Webhook not configured for feed ${ this . config . feedId } - enabled: ${ this . config . webhook ?. enabled } , url: ${ ! ! this . config . webhook ?. url } ` ) ;
488+ return ;
489+ }
490+
491+ console.log(`[IMAP] Webhook configured for feed ${ this . config . feedId } - URL: ${ this . config . webhook . url } , format: ${ this . config . webhook . format } , newItemsOnly: ${ this . config . webhook . newItemsOnly } `);
492+
493+ let shouldSendWebhook = true;
494+ let webhookRssXml = rssXml;
495+
496+ // Check if only new items should be sent
497+ if (this.config.webhook.newItemsOnly) {
498+ const historyPath = path . join ( __dirname , "../feed-history" , `${ this . config . feedId } .xml` ) ;
499+ let previousRss = null ;
500+
501+ try {
502+ if ( existsSync ( historyPath ) ) {
503+ previousRss = readFileSync ( historyPath , "utf8" ) ;
504+ }
505+ } catch ( err ) {
506+ console . warn ( "[IMAP] Could not read feed history:" , err . message ) ;
507+ }
508+
509+ // Use centralized new item detection
510+ try {
511+ const { getNewItemsFromRSS } = await import("../utilities/webhook.utility.ts");
512+ const newItemsRss = getNewItemsFromRSS(rssXml, previousRss);
513+
514+ if (!newItemsRss) {
515+ shouldSendWebhook = false ;
516+ console . log ( `[IMAP] No new items detected for feed ${ this . config . feedId } , skipping webhook` ) ;
517+ } else {
518+ webhookRssXml = newItemsRss ;
519+ console . log ( `[IMAP] New items detected for feed ${ this . config . feedId } , will send webhook` ) ;
520+ }
521+ } catch ( importErr ) {
522+ console . warn ( "[IMAP] Could not import webhook utilities, falling back to simple comparison:" , importErr . message ) ;
523+ // Fallback to simple comparison
524+ if ( previousRss && previousRss . trim ( ) === rssXml . trim ( ) ) {
525+ shouldSendWebhook = false ;
526+ console . log ( "[IMAP] No changes detected, skipping webhook" ) ;
527+ }
528+ }
529+ }
530+
531+ if ( shouldSendWebhook ) {
532+ try {
533+ // Use centralized webhook system
534+ const { sendWebhook, createWebhookPayload, createJsonWebhookPayload } = await import ( "../utilities/webhook.utility.ts" ) ;
535+
536+ const payload = this . config . webhook . format === "json"
537+ ? createJsonWebhookPayload ( this . config , webhookRssXml , "automatic" )
538+ : createWebhookPayload ( this . config , webhookRssXml , "automatic" ) ;
539+
540+ const success = await sendWebhook ( this . config . webhook , payload ) ;
541+
542+ if ( success ) {
543+ console . log ( `[IMAP] Webhook sent successfully for feed ${ this . config . feedId } to ${ this . config . webhook . url } ` ) ;
544+
545+ // Store current RSS for future comparison using centralized history utility
546+ try {
547+ const { storeFeedHistory } = await import ( "../utilities/feed-history.utility.ts" ) ;
548+ await storeFeedHistory ( this . config . feedId , rssXml ) ;
549+ console . log ( `[IMAP] Feed history stored for feed ${ this . config . feedId } ` ) ;
550+ } catch ( historyErr ) {
551+ console . warn ( `[IMAP] Could not store feed history for feed ${ this . config . feedId } :` , historyErr . message ) ;
552+ // Fallback to manual file storage
553+ const historyDir = path . join ( __dirname , "../feed-history" ) ;
554+ if ( ! existsSync ( historyDir ) ) {
555+ mkdirSync ( historyDir , { recursive : true } ) ;
556+ }
557+ writeFileSync ( path . join ( historyDir , `${ this . config . feedId } .xml` ) , rssXml , "utf8" ) ;
558+ console . log ( `[IMAP] Feed history stored manually for feed ${ this . config . feedId } ` ) ;
559+ }
560+ } else {
561+ console . warn ( `[IMAP] Webhook failed for feed ${ this . config . feedId } to ${ this . config . webhook . url } ` ) ;
562+ }
563+ } catch ( webhookErr ) {
564+ console . error ( `[IMAP] Error using centralized webhook system:` , webhookErr . message ) ;
565+ }
566+ }
567+ } catch ( error ) {
568+ console . error ( `[IMAP] Webhook error:` , error . message ) ;
569+ }
570+ }
571+
472572}
473573
474574export function buildRSSFromEmailFolder ( emails : Email [ ] , feedSetup : RSSFeedOptions ) : string {
@@ -591,9 +691,16 @@ const completeFeedConfig: RSSFeedOptions = {
591691 feedImage : rawConfig . feedImage ,
592692 generator : rawConfig . generator ,
593693 config : imapOriginalConfig ,
694+ webhook : rawConfig . webhook , // Pass through webhook configuration
594695} ;
595696
596697console.log("[IMAP Node Watcher] ImapWatcher will attempt to connect to host:", completeFeedConfig.config?.host, "port:", completeFeedConfig.config?.port);
698+ console.log("[IMAP Node Watcher] Webhook configuration:", JSON.stringify({
699+ enabled : completeFeedConfig . webhook ?. enabled ,
700+ url : completeFeedConfig . webhook ?. url ? '[REDACTED]' : undefined ,
701+ format : completeFeedConfig . webhook ?. format ,
702+ newItemsOnly : completeFeedConfig . webhook ?. newItemsOnly
703+ } , null, 2));
597704
598705const watcher = new ImapWatcher(completeFeedConfig);
599706
0 commit comments