gpt-engineer-app[bot] 1 месяц назад
Родитель
Сommit
ca9f94b0bd
4 измененных файлов с 234 добавлено и 0 удалено
  1. 38 0
      src/components/FeedForm.tsx
  2. 55 0
      src/utils/rss.ts
  3. 3 0
      supabase/config.toml
  4. 138 0
      supabase/functions/fetch-website-rss/index.ts

+ 38 - 0
src/components/FeedForm.tsx

@@ -21,6 +21,7 @@ import {
   getChannelIdInstructions,
   isDirectRSSFeed
 } from '@/utils/youtube';
+import { fetchWebsiteRSS, isDirectRSSFeed as isDirectRSSUrl } from '@/utils/rss';
 import { feedTypeOptions } from './FeedTypeOptions';
 import { NewsCategory } from '@/types/news';
 import { AlertTriangle, Info } from 'lucide-react';
@@ -109,6 +110,39 @@ const FeedForm = ({ selectedType, onSubmit, onCancel, categories }: FeedFormProp
       
       setIsLoadingChannelName(false);
     }
+    
+    // If it's an RSS auto URL, try to automatically detect RSS feed
+    if (selectedType === 'rss-auto' && url && !isDirectRSSUrl(url)) {
+      setIsLoadingChannelName(true);
+      
+      try {
+        const result = await fetchWebsiteRSS(url);
+        
+        if (result) {
+          // Update the URL field with the detected RSS URL
+          form.setValue('url', result.rssUrl);
+          
+          // Set the site name if we don't have one yet
+          if (!form.getValues('name') && result.siteName) {
+            form.setValue('name', result.siteName);
+          }
+          
+          setUrlWarning(null);
+          
+          // If multiple feeds found, show info
+          if (result.feeds && result.feeds.length > 1) {
+            setUrlWarning(`✓ ${result.feeds.length} flux RSS détectés. Le premier a été sélectionné.`);
+          }
+        } else {
+          setUrlWarning('Aucun flux RSS détecté automatiquement sur ce site.');
+        }
+      } catch (error) {
+        console.error('Error fetching website RSS:', error);
+        setUrlWarning('Erreur lors de la détection automatique du flux RSS.');
+      }
+      
+      setIsLoadingChannelName(false);
+    }
   };
 
   const selectedTypeOption = feedTypeOptions.find(option => option.value === selectedType);
@@ -118,6 +152,7 @@ const FeedForm = ({ selectedType, onSubmit, onCancel, categories }: FeedFormProp
       case 'youtube':
         return 'https://www.youtube.com/channel/UCxxxxx ou https://www.youtube.com/feeds/videos.xml?channel_id=UCxxxxx';
       case 'rss-auto':
+        return 'https://example.com (le flux RSS sera détecté automatiquement)';
       case 'rss-manual':
         return 'https://example.com/feed.xml';
       default:
@@ -129,6 +164,9 @@ const FeedForm = ({ selectedType, onSubmit, onCancel, categories }: FeedFormProp
     if (selectedType === 'youtube') {
       return 'Utilisez de préférence l\'URL avec l\'ID de chaîne (UC...) ou l\'URL RSS directe pour éviter les erreurs';
     }
+    if (selectedType === 'rss-auto') {
+      return 'Entrez l\'URL du site web et le flux RSS sera automatiquement détecté';
+    }
     return null;
   };
 

+ 55 - 0
src/utils/rss.ts

@@ -0,0 +1,55 @@
+import { supabase } from '@/integrations/supabase/client';
+
+interface RSSFeed {
+  url: string;
+  title: string;
+  type: string;
+}
+
+interface FetchRSSResult {
+  rssUrl: string;
+  siteName: string;
+  feeds?: RSSFeed[];
+}
+
+export async function fetchWebsiteRSS(url: string): Promise<FetchRSSResult | null> {
+  try {
+    console.log('Fetching RSS for website:', url);
+
+    const { data, error } = await supabase.functions.invoke('fetch-website-rss', {
+      body: { url }
+    });
+
+    if (error) {
+      console.error('Error calling fetch-website-rss:', error);
+      return null;
+    }
+
+    if (!data.success) {
+      console.error('RSS detection failed:', data.error);
+      return null;
+    }
+
+    return {
+      rssUrl: data.rssUrl,
+      siteName: data.siteName,
+      feeds: data.feeds,
+    };
+  } catch (error) {
+    console.error('Error fetching website RSS:', error);
+    return null;
+  }
+}
+
+export function isValidWebsiteUrl(url: string): boolean {
+  try {
+    const urlObj = new URL(url);
+    return urlObj.protocol === 'http:' || urlObj.protocol === 'https:';
+  } catch {
+    return false;
+  }
+}
+
+export function isDirectRSSFeed(url: string): boolean {
+  return url.includes('.xml') || url.includes('/feed') || url.includes('/rss') || url.includes('/atom');
+}

+ 3 - 0
supabase/config.toml

@@ -4,4 +4,7 @@ project_id = "wftyukugedtojizgatwj"
 verify_jwt = false
 
 [functions.send-purge-report]
+verify_jwt = false
+
+[functions.fetch-website-rss]
 verify_jwt = false

+ 138 - 0
supabase/functions/fetch-website-rss/index.ts

@@ -0,0 +1,138 @@
+import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
+
+const corsHeaders = {
+  'Access-Control-Allow-Origin': '*',
+  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
+};
+
+interface RSSFeed {
+  url: string;
+  title: string;
+  type: string;
+}
+
+serve(async (req) => {
+  // Handle CORS preflight requests
+  if (req.method === 'OPTIONS') {
+    return new Response(null, { headers: corsHeaders });
+  }
+
+  try {
+    const { url } = await req.json();
+
+    if (!url) {
+      return new Response(
+        JSON.stringify({ error: 'URL is required' }),
+        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      );
+    }
+
+    console.log('Fetching website RSS for URL:', url);
+
+    // Fetch the webpage
+    const response = await fetch(url, {
+      headers: {
+        'User-Agent': 'Mozilla/5.0 (compatible; RSS Feed Detector/1.0)',
+      },
+    });
+
+    if (!response.ok) {
+      throw new Error(`Failed to fetch website: ${response.status}`);
+    }
+
+    const html = await response.text();
+    const feeds: RSSFeed[] = [];
+
+    // Extract site name from <title> tag
+    const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
+    const siteName = titleMatch ? titleMatch[1].trim() : new URL(url).hostname;
+
+    // Look for RSS/Atom links in <link> tags
+    const linkRegex = /<link[^>]+rel=["']alternate["'][^>]+>/gi;
+    const links = html.match(linkRegex) || [];
+
+    for (const link of links) {
+      const typeMatch = link.match(/type=["']([^"']+)["']/i);
+      const hrefMatch = link.match(/href=["']([^"']+)["']/i);
+      const titleMatch = link.match(/title=["']([^"']+)["']/i);
+
+      if (typeMatch && hrefMatch) {
+        const type = typeMatch[1];
+        const href = hrefMatch[1];
+        const title = titleMatch ? titleMatch[1] : 'RSS Feed';
+
+        if (
+          type.includes('application/rss+xml') ||
+          type.includes('application/atom+xml') ||
+          type.includes('application/xml')
+        ) {
+          // Convert relative URLs to absolute
+          const feedUrl = href.startsWith('http') ? href : new URL(href, url).toString();
+          
+          feeds.push({
+            url: feedUrl,
+            title,
+            type,
+          });
+        }
+      }
+    }
+
+    // Fallback: Check common RSS paths if no feeds found
+    if (feeds.length === 0) {
+      const commonPaths = ['/feed', '/rss', '/feed.xml', '/rss.xml', '/atom.xml', '/index.xml'];
+      const baseUrl = new URL(url);
+      
+      for (const path of commonPaths) {
+        const testUrl = `${baseUrl.origin}${path}`;
+        try {
+          const testResponse = await fetch(testUrl, { method: 'HEAD' });
+          if (testResponse.ok) {
+            feeds.push({
+              url: testUrl,
+              title: 'RSS Feed',
+              type: 'application/rss+xml',
+            });
+          }
+        } catch (error) {
+          // Ignore errors for fallback checks
+          console.log(`Failed to check ${testUrl}:`, error.message);
+        }
+      }
+    }
+
+    console.log('Found feeds:', feeds);
+
+    if (feeds.length === 0) {
+      return new Response(
+        JSON.stringify({ 
+          success: false,
+          error: 'No RSS feeds found on this website',
+          siteName 
+        }),
+        { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      );
+    }
+
+    // Return the first feed by default, or all feeds if multiple
+    return new Response(
+      JSON.stringify({
+        success: true,
+        rssUrl: feeds[0].url,
+        siteName,
+        feeds: feeds.length > 1 ? feeds : undefined,
+      }),
+      { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    );
+
+  } catch (error) {
+    console.error('Error fetching website RSS:', error);
+    return new Response(
+      JSON.stringify({ 
+        success: false,
+        error: error.message || 'Failed to detect RSS feed' 
+      }),
+      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    );
+  }
+});