Action

RSS Feed Finder

Posted by TheChels.uk, Last update about 6 hours ago

About

Takes a selection of text (which is a url) and returns and appends valid RSS feeds to the current Draft. The script validates found feeds so can take a while to process (each has a 5 second timeout).

Tip

If you find this useful and want to support me, you can tip at ko-fi.com/thechelsuk

Steps

  • script

    /*
     * @title: Find RSS Feed
     * @author: TheChelsuk
     * @notes: Takes a URL and finds its RSS feed
     * Only appends verified, working feeds
     */
    
    // Get URL from draft
    let content = draft.content.trim();
    let url = '';
    
    // Check if there's a selection
    let selection = editor.getSelectedText();
    if (selection && selection.trim()) {
        url = selection.trim();
    } else {
        // Use entire draft content
        url = content;
    }
    
    // Clean up URL
    url = url.trim();
    
    // Validate URL
    if (!url.startsWith('http://') && !url.startsWith('https://')) {
        app.displayErrorMessage('Invalid URL. Must start with http:// or https://');
        context.fail();
    }
    
    // Fetch the page
    let http = HTTP.create();
    let response = http.request({
        url: url,
        method: 'GET',
        timeout: 10
    });
    
    if (!response.success || !response.responseText) {
        app.displayErrorMessage('Failed to fetch URL: ' + (response.statusCode || 'Network error'));
        context.fail();
    }
    
    let html = response.responseText;
    
    if (!html || html.length === 0) {
        app.displayErrorMessage('No content returned from URL');
        context.fail();
    }
    
    let candidateFeeds = [];
    
    // Pattern 1: Look for RSS/Atom link tags in HTML
    let linkPattern = /<link[^>]*type=["'](application\/rss\+xml|application\/atom\+xml)["'][^>]*>/gi;
    let linkMatches = html.match(linkPattern);
    
    if (linkMatches) {
        for (let match of linkMatches) {
            let hrefMatch = match.match(/href=["']([^"']+)["']/i);
            if (hrefMatch) {
                let feedUrl = hrefMatch[1];
                
                // Convert relative URLs to absolute
                try {
                    if (feedUrl.startsWith('/')) {
                        let urlObj = new URL(url);
                        feedUrl = urlObj.origin + feedUrl;
                    } else if (!feedUrl.startsWith('http')) {
                        let urlObj = new URL(url);
                        feedUrl = urlObj.origin + '/' + feedUrl;
                    }
                    
                    candidateFeeds.push(feedUrl);
                } catch (e) {
                    // Skip malformed URLs
                    continue;
                }
            }
        }
    }
    
    // Pattern 2: Common RSS URL patterns to test
    try {
        let urlObj = new URL(url);
        let baseUrl = urlObj.origin;
    
        let commonPatterns = [
            '/feed',
            '/feed/',
            '/rss',
            '/rss/',
            '/atom',
            '/atom/',
            '/feed.xml',
            '/rss.xml',
            '/atom.xml',
            '/index.xml'
        ];
    
        for (let pattern of commonPatterns) {
            candidateFeeds.push(baseUrl + pattern);
        }
    } catch (e) {
        // Skip if URL parsing fails
    }
    
    // Remove duplicates
    candidateFeeds = [...new Set(candidateFeeds)];
    
    if (candidateFeeds.length === 0) {
        app.displayWarningMessage('No potential feeds found in page');
        context.cancel();
    }
    
    // Validate each feed
    let validFeeds = [];
    
    app.displayInfoMessage('Checking ' + candidateFeeds.length + ' potential feeds...');
    
    for (let feedUrl of candidateFeeds) {
        try {
            let feedResponse = http.request({
                url: feedUrl,
                method: 'GET',
                timeout: 5
            });
            
            if (feedResponse.success && feedResponse.statusCode === 200 && feedResponse.responseText) {
                let feedContent = feedResponse.responseText;
                
                // Check if it's actually RSS/Atom XML
                if (feedContent.includes('<rss') || 
                    feedContent.includes('<feed') || 
                    feedContent.includes('</rss>') || 
                    feedContent.includes('</feed>')) {
                    
                    validFeeds.push(feedUrl);
                }
            }
        } catch (e) {
            // Skip feeds that fail
            continue;
        }
    }
    
    if (validFeeds.length === 0) {
        app.displayWarningMessage('No valid RSS feeds found');
        context.cancel();
    }
    
    // Build output
    let output = '\n\n---\n## RSS Feed' + (validFeeds.length > 1 ? 's' : '') + '\n\n';
    
    if (validFeeds.length === 1) {
        output += `${validFeeds[0]}\n`;
    } else {
        for (let feed of validFeeds) {
            output += `- ${feed}\n`;
        }
    }
    
    // Append to draft
    draft.content = content + output;
    draft.update();
    
    app.displaySuccessMessage('Found ' + validFeeds.length + ' valid feed(s)');

Options

  • After Success Default
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.