| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
- import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
- interface RSSItem {
- title: string;
- description: string;
- link: string;
- pubDate: string;
- guid?: string;
- content?: string;
- image?: string;
- }
- interface RSSFeed {
- title: string;
- description: string;
- items: RSSItem[];
- }
- const corsHeaders = {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
- }
- serve(async (req) => {
- if (req.method === 'OPTIONS') {
- return new Response('ok', { headers: corsHeaders })
- }
- try {
- const supabaseClient = createClient(
- Deno.env.get('SUPABASE_URL') ?? '',
- Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
- )
- const { feedId, feedUrl } = await req.json()
- console.log(`Fetching RSS for feed: ${feedId}, URL: ${feedUrl}`)
- // Fetch RSS content
- const response = await fetch(feedUrl)
- if (!response.ok) {
- throw new Error(`Failed to fetch RSS: ${response.statusText}`)
- }
- const rssText = await response.text()
-
- // Parse RSS using DOMParser
- const parser = new DOMParser()
- const doc = parser.parseFromString(rssText, 'application/xml')
-
- if (!doc) {
- throw new Error('Failed to parse RSS XML')
- }
- // Extract RSS items
- const items = Array.from(doc.querySelectorAll('item, entry')).map(item => {
- const title = item.querySelector('title')?.textContent?.trim() || ''
- const description = item.querySelector('description, summary')?.textContent?.trim() || ''
- const link = item.querySelector('link')?.textContent?.trim() ||
- item.querySelector('link')?.getAttribute('href') || ''
- const pubDate = item.querySelector('pubDate, published')?.textContent?.trim() || ''
- const guid = item.querySelector('guid')?.textContent?.trim() || link
-
- // Try to extract image from content or enclosure
- let image = ''
- const enclosure = item.querySelector('enclosure[type^="image"]')
- if (enclosure) {
- image = enclosure.getAttribute('url') || ''
- } else {
- // Try to find image in content
- const content = item.querySelector('content\\:encoded, content')?.textContent
- if (content) {
- const imgMatch = content.match(/<img[^>]+src="([^">]+)"/i)
- if (imgMatch) {
- image = imgMatch[1]
- }
- }
- }
- return {
- title,
- description,
- link,
- pubDate,
- guid,
- image,
- content: description
- }
- }).filter(item => item.title && item.guid)
- console.log(`Found ${items.length} items`)
- // Save articles to database
- const articlesToInsert = items.map(item => {
- // Calculate read time (rough estimate: 200 words per minute)
- const wordCount = (item.description || '').split(' ').length
- const readTime = Math.max(1, Math.ceil(wordCount / 200))
- return {
- feed_id: feedId,
- title: item.title,
- description: item.description,
- content: item.content || item.description,
- url: item.link,
- image_url: item.image || null,
- published_at: item.pubDate ? new Date(item.pubDate).toISOString() : new Date().toISOString(),
- guid: item.guid,
- read_time: readTime
- }
- })
- // Insert articles (on conflict do nothing to avoid duplicates)
- const { error: insertError } = await supabaseClient
- .from('articles')
- .upsert(articlesToInsert, {
- onConflict: 'feed_id,guid',
- ignoreDuplicates: true
- })
- if (insertError) {
- console.error('Error inserting articles:', insertError)
- throw insertError
- }
- // Update feed's last_fetched_at
- const { error: updateError } = await supabaseClient
- .from('feeds')
- .update({
- last_fetched_at: new Date().toISOString(),
- status: 'active'
- })
- .eq('id', feedId)
- if (updateError) {
- console.error('Error updating feed:', updateError)
- throw updateError
- }
- console.log(`Successfully processed ${articlesToInsert.length} articles for feed ${feedId}`)
- return new Response(
- JSON.stringify({
- success: true,
- articlesProcessed: articlesToInsert.length
- }),
- {
- headers: { ...corsHeaders, 'Content-Type': 'application/json' }
- }
- )
- } catch (error) {
- console.error('Error in fetch-rss function:', error)
- return new Response(
- JSON.stringify({
- error: error.message,
- success: false
- }),
- {
- status: 500,
- headers: { ...corsHeaders, 'Content-Type': 'application/json' }
- }
- )
- }
- })
|