offline-first, p2p synced, atproto enabled, feed reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add cors and reader proxies

+98 -1
+35
src/feedline/api/cors-proxy.ts
··· 1 + import {RequestHandler} from 'express' 2 + 3 + export const corsProxy: RequestHandler = async (req, res) => { 4 + const url = req.query.url 5 + if (typeof url !== 'string' || !url) { 6 + res.status(400).json({error: 'Missing or invalid url parameter'}) 7 + return 8 + } 9 + 10 + try { 11 + const response = await fetch(url, { 12 + headers: { 13 + 'User-Agent': 'Mozilla/5.0 (compatible; Skypod/1.0; +https://skypod.accidental.cc)', 14 + }, 15 + }) 16 + 17 + if (!response.ok) { 18 + res.status(response.status).json({error: `Failed to fetch feed: ${response.statusText}`}) 19 + return 20 + } 21 + 22 + const contentType = response.headers.get('content-type') || 'application/xml' 23 + const content = await response.text() 24 + 25 + res.setHeader('Content-Type', contentType) 26 + res.setHeader('Cache-Control', 'public, max-age=300') // 5 min cache 27 + res.send(content) 28 + } catch (error) { 29 + console.error('Feed proxy error:', error) 30 + res.status(500).json({ 31 + error: 'Failed to fetch feed', 32 + message: error instanceof Error ? error.message : 'Unknown error', 33 + }) 34 + } 35 + }
+58
src/feedline/api/reader-proxy.ts
··· 1 + import {Readability} from '@mozilla/readability' 2 + import {RequestHandler} from 'express' 3 + import {parseHTML} from 'linkedom' 4 + 5 + export const readabilityProxy: RequestHandler = async (req, res) => { 6 + const url = req.query.url 7 + if (typeof url !== 'string' || !url) { 8 + res.status(400).json({error: 'Missing or invalid url parameter'}) 9 + return 10 + } 11 + 12 + try { 13 + const response = await fetch(url, { 14 + headers: { 15 + 'User-Agent': 'Mozilla/5.0 (compatible; Skypod/1.0; +https://skypod.accidental.cc)', 16 + }, 17 + }) 18 + 19 + if (!response.ok) { 20 + res.status(response.status).json({error: `Failed to fetch page: ${response.statusText}`}) 21 + return 22 + } 23 + 24 + const html = await response.text() 25 + const {document} = parseHTML(html) 26 + 27 + const reader = new Readability(document, { 28 + keepClasses: false, 29 + }) 30 + 31 + const article = reader.parse() 32 + 33 + if (!article) { 34 + res.status(422).json({error: 'Could not extract readable content'}) 35 + return 36 + } 37 + 38 + res.setHeader('Content-Type', 'application/json; charset=utf-8') 39 + res.send( 40 + JSON.stringify({ 41 + title: article.title, 42 + byline: article.byline, 43 + content: article.content, 44 + textContent: article.textContent, 45 + excerpt: article.excerpt, 46 + siteName: article.siteName, 47 + length: article.length, 48 + url, 49 + }), 50 + ) 51 + } catch (error) { 52 + console.error('Reader mode error:', error) 53 + res.status(500).json({ 54 + error: 'Failed to extract content', 55 + message: error instanceof Error ? error.message : 'Unknown error', 56 + }) 57 + } 58 + }
+5 -1
src/feedline/main.ts
··· 7 7 8 8 import {driveSocket} from '#realm/server/driver' 9 9 import {closeRealms, listRealms} from '#realm/server/storage' 10 + import { corsProxy } from './api/cors-proxy' 11 + import { readabilityProxy } from './api/reader-proxy' 10 12 11 13 const __filename = fileURLToPath(import.meta.url) 12 14 const __dirname = path.dirname(__filename) ··· 29 31 // host API on /api 30 32 31 33 app.use('/api', router) 32 - router.get('/', (_, res) => res.send('hi')) 34 + 35 + router.get('/cors', corsProxy) 36 + router.get('/reader', readabilityProxy) 33 37 34 38 // host websockets on /stream 35 39