/* * Copyright (c) 2024-2026 Fabian Freund. * * This file is part of WebLibre * (see https://weblibre.eu). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import 'package:html/dom.dart'; class FeedFinder { final Uri url; final Document document; late final String _base; FeedFinder({required this.url, required this.document}) { final uri = url.removeFragment(); _base = '${uri.scheme}://${uri.authority}'; } // Future> _verifyCandidates(Set candidates) async { // final results = {}; // final client = http.Client(); // try { // for (final candidate in candidates) { // try { // await client.get(Uri.parse(candidate)); // } catch (e) { // continue; // } // results.add(candidate); // } // } finally { // client.close(); // } // return results; // } void _parseBody(Set candidates) { for (final a in document.querySelectorAll('a')) { var href = a.attributes['href']; if (href != null) { if (href.contains('rss') || href.contains('xml') || href.contains('feed')) { // Fix relative URLs href = href.startsWith('/') ? _base + href : href; href = href.endsWith('/') ? href.substring(0, href.length - 2) : href; // Fix naked URLs href = !href.startsWith('http') ? '$_base/$href' : href; if (Uri.tryParse(href) case final Uri uri) { candidates.add(uri); } } } } } void _parseHead(Set candidates) { for (final link in document.querySelectorAll("link[rel='alternate']")) { final type = link.attributes['type']; if (type != null) { if (type.contains('rss') || type.contains('xml')) { var href = link.attributes['href']; if (href != null) { // Fix relative URLs href = href.startsWith('/') ? _base + href : href; if (Uri.tryParse(href) case final Uri uri) { candidates.add(uri); } } } } } } Future> parse({ bool parseHead = true, bool parseBody = true, // bool verifyCandidates = true, }) async { final candidates = {}; // Look for feed candidates in head if (parseHead) { _parseHead(candidates); } // Look for feed candidates in body if (parseBody) { _parseBody(candidates); } // Verify candidates // if (verifyCandidates) { // return _verifyCandidates(candidates); // } return candidates; } }