mirror of
https://github.com/svemagie/indiekit-endpoint-microsub.git
synced 2026-04-02 15:35:00 +02:00
fix: improve fediverse detection and feed discovery
- fetcher.js: try <link rel="alternate"> HTML discovery before falling back to common feed paths (/feed, /rss.xml, etc.), fixing subscriptions to sites like Substack, econsoc.mpifg.de, and others whose feed URL is advertised via a link element but not at a predictable path - reader.js: extend detectProtocol to recognise more fediverse domains (troet., social., hachyderm., infosec.exchange, chaos.social) - reader.js: don't auto-check Mastodon syndication target for likes and reposts — those are handled natively by the AP endpoint; use service-name-aware target matching that works for any configured Mastodon instance even if its domain isn't in the hardcoded list Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -446,8 +446,11 @@ function detectProtocol(url) {
|
||||
if (!url || typeof url !== "string") return "web";
|
||||
const lower = url.toLowerCase();
|
||||
if (lower.includes("bsky.app") || lower.includes("bluesky")) return "atmosphere";
|
||||
// Well-known fediverse software domain patterns
|
||||
if (lower.includes("mastodon.") || lower.includes("mstdn.") || lower.includes("fosstodon.") ||
|
||||
lower.includes("pleroma.") || lower.includes("misskey.") || lower.includes("pixelfed.")) return "fediverse";
|
||||
lower.includes("troet.") || lower.includes("social.") || lower.includes("pleroma.") ||
|
||||
lower.includes("misskey.") || lower.includes("pixelfed.") || lower.includes("hachyderm.") ||
|
||||
lower.includes("infosec.exchange") || lower.includes("chaos.social")) return "fediverse";
|
||||
return "web";
|
||||
}
|
||||
|
||||
@@ -510,15 +513,36 @@ export async function compose(request, response) {
|
||||
? await getSyndicationTargets(application, token)
|
||||
: [];
|
||||
|
||||
// Auto-select syndication target based on interaction URL protocol
|
||||
// Auto-select syndication target based on interaction URL protocol.
|
||||
// Likes and reposts on fediverse are handled natively by the AP endpoint —
|
||||
// never auto-check Mastodon for those action types.
|
||||
const isLikeOrRepost = !!(likeOf || like || repostOf || repost);
|
||||
const interactionUrl = ensureString(replyTo || reply || likeOf || like || repostOf || repost);
|
||||
if (interactionUrl && syndicationTargets.length > 0) {
|
||||
if (interactionUrl && syndicationTargets.length > 0 && !isLikeOrRepost) {
|
||||
const protocol = detectProtocol(interactionUrl);
|
||||
|
||||
// Build set of Mastodon instance hostnames from configured targets so we
|
||||
// can match same-instance URLs even if not in the hardcoded pattern list.
|
||||
const mastodonHostnames = new Set();
|
||||
for (const t of syndicationTargets) {
|
||||
if (t.service?.name?.toLowerCase() === "mastodon" && t.service?.url) {
|
||||
try { mastodonHostnames.add(new URL(t.service.url).hostname.toLowerCase()); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
let interactionHostname = "";
|
||||
try { interactionHostname = new URL(interactionUrl).hostname.toLowerCase(); } catch { /* ignore */ }
|
||||
|
||||
for (const target of syndicationTargets) {
|
||||
const targetId = (target.uid || target.name || "").toLowerCase();
|
||||
// Identify a Mastodon target by service name (reliable) or legacy uid/name patterns
|
||||
const isMastodonTarget =
|
||||
target.service?.name?.toLowerCase() === "mastodon" ||
|
||||
targetId.includes("mastodon") ||
|
||||
targetId.includes("mstdn");
|
||||
|
||||
if (protocol === "atmosphere" && (targetId.includes("bluesky") || targetId.includes("bsky"))) {
|
||||
target.checked = true;
|
||||
} else if (protocol === "fediverse" && (targetId.includes("mastodon") || targetId.includes("mstdn"))) {
|
||||
} else if (isMastodonTarget && (protocol === "fediverse" || mastodonHostnames.has(interactionHostname))) {
|
||||
target.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +164,24 @@ export async function fetchAndParseFeed(url, options = {}) {
|
||||
// Check if we got a parseable feed
|
||||
const feedType = detectFeedType(result.content, result.contentType);
|
||||
|
||||
// If we got ActivityPub or unknown, try common feed paths
|
||||
// If we got ActivityPub or unknown, try link-based discovery then common paths
|
||||
if (feedType === "activitypub" || feedType === "unknown") {
|
||||
const fallbackFeed = await tryCommonFeedPaths(url, options);
|
||||
// 1. link-based discovery from HTML: parse <link rel="alternate" type="application/rss+xml|atom+xml">
|
||||
let discoveredFeedUrl;
|
||||
if (result.content) {
|
||||
const { discoverFeeds } = await import("./hfeed.js");
|
||||
const discovered = await discoverFeeds(result.content, url);
|
||||
const rssOrAtom = discovered.find(
|
||||
(f) => f.type === "rss" || f.type === "atom" || f.type === "jsonfeed",
|
||||
);
|
||||
if (rssOrAtom) discoveredFeedUrl = rssOrAtom.url;
|
||||
}
|
||||
|
||||
// 2. Fall back to common feed paths (/feed, /rss.xml, etc.)
|
||||
const fallbackFeed = discoveredFeedUrl
|
||||
? { url: discoveredFeedUrl }
|
||||
: await tryCommonFeedPaths(url, options);
|
||||
|
||||
if (fallbackFeed) {
|
||||
// Fetch and parse the discovered feed
|
||||
const feedResult = await fetchFeed(fallbackFeed.url, options);
|
||||
|
||||
Reference in New Issue
Block a user