src / net / academicResolver.ts
src / net / academicResolver.ts
const BLOCKED_ACADEMIC_HOSTS =
/\b(academia\.edu|researchgate\.net|sciencedirect\.com|jstor\.org|springer\.com|tandfonline\.com|wiley\.com)\b/i;
/**
* Intercepts unsolvable/Cloudflare-locked academic URLs (including Wayback mirrors of academia.edu)
* and resolves their peer-reviewed abstract + DOI directly via OpenAlex JSON API in <800ms.
*/
export async function resolveBlockedAcademicUrl(
url: string,
serpTitle: string,
log?: (msg: string) => void
): Promise<{ title: string; content: string; resolvedUrl: string } | null> {
if (!BLOCKED_ACADEMIC_HOSTS.test(url)) return null;
// Clean title or extract slug from URL if title is generic
let searchTitle = serpTitle
.replace(/\s*[-|]\s*(Academia\.edu|ResearchGate|ScienceDirect).*$/i, "")
.replace(/^\(PDF\)\s*/i, "")
.trim();
if (searchTitle.length < 12) {
const slugMatch = url.match(/\/([a-zA-Z0-9_-]{15,})(?:\/|$)/);
if (slugMatch) {
searchTitle = slugMatch[1].replace(/[_-]+/g, " ");
}
}
if (searchTitle.length < 10) return null;
const apiUrl = `https://api.openalex.org/works?search=${encodeURIComponent(
searchTitle
)}&per-page=1&select=id,doi,title,publication_year,abstract_inverted_index,primary_location`;
try {
const res = await fetch(apiUrl, {
headers: { Accept: "application/json", "User-Agent": "DeepSwarmResearch/5.9.2" },
signal: AbortSignal.timeout(2500),
});
if (!res.ok) return null;
const data: any = await res.json();
const work = data?.results?.[0];
if (!work?.title || !work?.abstract_inverted_index) return null;
const words: string[] = [];
for (const [word, positions] of Object.entries(
work.abstract_inverted_index as Record<string, number[]>
)) {
for (const pos of positions) words[pos] = word;
}
const abstract = words.filter(Boolean).join(" ");
if (abstract.length < 100) return null;
const resolvedUrl = work.doi || work.primary_location?.landing_page_url || work.id;
log?.(
`[ACADEMIC RESOLVER] Replaced blocked ${new URL(url).hostname} URL with OpenAlex record: "${work.title.slice(0, 55)}..."`
);
return {
title: `${work.title} (${work.publication_year ?? "n.d."})`,
content: `[OpenAlex Resolved Paper]\nTitle: ${work.title}\nYear: ${work.publication_year}\nDOI: ${work.doi ?? "N/A"}\nAbstract: ${abstract}`,
resolvedUrl: String(resolvedUrl),
};
} catch {
return null;
}
}
const BLOCKED_ACADEMIC_HOSTS =
/\b(academia\.edu|researchgate\.net|sciencedirect\.com|jstor\.org|springer\.com|tandfonline\.com|wiley\.com)\b/i;
/**
* Intercepts unsolvable/Cloudflare-locked academic URLs (including Wayback mirrors of academia.edu)
* and resolves their peer-reviewed abstract + DOI directly via OpenAlex JSON API in <800ms.
*/
export async function resolveBlockedAcademicUrl(
url: string,
serpTitle: string,
log?: (msg: string) => void
): Promise<{ title: string; content: string; resolvedUrl: string } | null> {
if (!BLOCKED_ACADEMIC_HOSTS.test(url)) return null;
// Clean title or extract slug from URL if title is generic
let searchTitle = serpTitle
.replace(/\s*[-|]\s*(Academia\.edu|ResearchGate|ScienceDirect).*$/i, "")
.replace(/^\(PDF\)\s*/i, "")
.trim();
if (searchTitle.length < 12) {
const slugMatch = url.match(/\/([a-zA-Z0-9_-]{15,})(?:\/|$)/);
if (slugMatch) {
searchTitle = slugMatch[1].replace(/[_-]+/g, " ");
}
}
if (searchTitle.length < 10) return null;
const apiUrl = `https://api.openalex.org/works?search=${encodeURIComponent(
searchTitle
)}&per-page=1&select=id,doi,title,publication_year,abstract_inverted_index,primary_location`;
try {
const res = await fetch(apiUrl, {
headers: { Accept: "application/json", "User-Agent": "DeepSwarmResearch/5.9.2" },
signal: AbortSignal.timeout(2500),
});
if (!res.ok) return null;
const data: any = await res.json();
const work = data?.results?.[0];
if (!work?.title || !work?.abstract_inverted_index) return null;
const words: string[] = [];
for (const [word, positions] of Object.entries(
work.abstract_inverted_index as Record<string, number[]>
)) {
for (const pos of positions) words[pos] = word;
}
const abstract = words.filter(Boolean).join(" ");
if (abstract.length < 100) return null;
const resolvedUrl = work.doi || work.primary_location?.landing_page_url || work.id;
log?.(
`[ACADEMIC RESOLVER] Replaced blocked ${new URL(url).hostname} URL with OpenAlex record: "${work.title.slice(0, 55)}..."`
);
return {
title: `${work.title} (${work.publication_year ?? "n.d."})`,
content: `[OpenAlex Resolved Paper]\nTitle: ${work.title}\nYear: ${work.publication_year}\nDOI: ${work.doi ?? "N/A"}\nAbstract: ${abstract}`,
resolvedUrl: String(resolvedUrl),
};
} catch {
return null;
}
}