diff --git a/wechat-news-morning-searxng-fallback.mjs b/wechat-news-morning-searxng-fallback.mjs index 911432d..5256c78 100644 --- a/wechat-news-morning-searxng-fallback.mjs +++ b/wechat-news-morning-searxng-fallback.mjs @@ -223,6 +223,32 @@ function pickSearchCandidates(groupMap, poolIds, seenTitles, { requireImage = tr return picked; } +/** 先取带图候选,不足时再放宽为无图(后续 enrichment 会从来源页补 og:image)。 */ +function pickSearchCandidatesWithFallback(groupMap, poolIds, seenTitles, needCount) { + if (needCount <= 0) return []; + const workingSeen = [...seenTitles]; + const withImage = pickSearchCandidates(groupMap, poolIds, workingSeen, { requireImage: true }); + let merged = withImage; + if (merged.length < needCount) { + const withoutImage = pickSearchCandidates(groupMap, poolIds, workingSeen, { requireImage: false }); + const usedUrls = new Set(merged.map((item) => item.url)); + for (const item of withoutImage) { + if (merged.length >= needCount) break; + if (usedUrls.has(item.url)) continue; + merged.push(item); + usedUrls.add(item.url); + } + } + const picked = merged.slice(0, needCount); + for (const item of picked) { + seenTitles.push({ + urlKey: normalizeNewsMorningUrl(item.url), + title: normalizeNewsMorningTitle(item.title), + }); + } + return picked; +} + function buildLeadCardHtml(item, eventKey) { const title = escapeHtml(item.title); const snippet = escapeHtml(truncateText(item.snippet || item.title, 120)); @@ -340,17 +366,15 @@ export function supplementNews002HtmlFromSearxngGroups( || countLeadCardsInHeadlines(output) < 3; let remainingBudget = Math.max(0, maxAdds - existingCards.length); - const leadPool = pickSearchCandidates( - groupMap, - SECTION_SEARCH_POOLS.headlines, - [...seenTitles], - { requireImage: true }, - ); - if (needLeads && remainingBudget > 0) { const currentLeads = countLeadCardsInHeadlines(output); const needCount = Math.max(0, 3 - currentLeads); - const toAdd = leadPool.splice(0, Math.min(needCount, remainingBudget)); + const toAdd = pickSearchCandidatesWithFallback( + groupMap, + SECTION_SEARCH_POOLS.headlines, + seenTitles, + Math.min(needCount, remainingBudget), + ); if (toAdd.length) { const cardsHtml = toAdd.map((item) => ( buildLeadCardHtml(item, buildNewsMorningEventKey(item.title, usedEventKeys)) @@ -394,9 +418,17 @@ export function supplementNews002HtmlFromSearxngGroups( if (!needCount && hasNews002Section(output, sectionId)) continue; const pool = SECTION_SEARCH_POOLS[sectionId] ?? [SEARXNG_GROUP_TO_NEWS002_SECTION[sectionId]].filter(Boolean); - const candidates = pickSearchCandidates(groupMap, pool, [...seenTitles], { requireImage: true }); - const toAdd = candidates.slice(0, Math.min(needCount || meta.min, remainingBudget)); - if (!toAdd.length) continue; + const targetCount = Math.min(needCount || meta.min, remainingBudget); + const toAdd = pickSearchCandidatesWithFallback(groupMap, pool, seenTitles, targetCount); + const sectionMissing = missingSections.includes(sectionId) || !hasNews002Section(output, sectionId); + + if (!toAdd.length) { + if (sectionMissing) { + output = createAndInsertSection(output, sectionId, ''); + stats.addedSections.push(sectionId); + } + continue; + } const cardsHtml = toAdd.map((item) => ( buildStoryCardHtml(item, buildNewsMorningEventKey(item.title, usedEventKeys)) diff --git a/wechat-news-morning-searxng-fallback.test.mjs b/wechat-news-morning-searxng-fallback.test.mjs index b69f987..bd43fd1 100644 --- a/wechat-news-morning-searxng-fallback.test.mjs +++ b/wechat-news-morning-searxng-fallback.test.mjs @@ -75,6 +75,37 @@ test('buildNewsMorningEventKey deduplicates keys', () => { assert.equal(buildNewsMorningEventKey('Same Title', used), 'same-title-2'); }); +test('supplementNews002HtmlFromSearxngGroups fills sections without prefetch images', () => { + const groupsNoImage = [ + { + id: 'world', + title: '国际', + results: [ + { title: '国际无图一', url: 'https://news.example/w-no1', snippet: '国际一' }, + { title: '国际无图二', url: 'https://news.example/w-no2', snippet: '国际二' }, + ], + }, + { + id: 'tech', + title: '科技', + results: [ + { title: '科技无图一', url: 'https://news.example/t-no1', snippet: '科技一' }, + { title: '科技无图二', url: 'https://news.example/t-no2', snippet: '科技二' }, + ], + }, + ]; + const result = supplementNews002HtmlFromSearxngGroups( + THIN_AGENT_HTML, + groupsNoImage, + { violations: ['missing-sections:world|tech'] }, + ); + assert.equal(result.changed, true); + assert.match(result.html, /id="world"/u); + assert.match(result.html, /id="tech"/u); + assert.match(result.html, /国际无图一/u); + assert.match(result.html, /科技无图一/u); +}); + test('supplementNews002HtmlFromSearxngGroups fills missing core sections and leads', () => { const beforeAudit = auditNews002ImageCoverage(THIN_AGENT_HTML, { requireTemplateStructure: true }); assert.equal(beforeAudit.ok, false);