([\s\S]*?)<\/div>/i)?.[1]
?? '',
);
const temp = stripInlineText(
cardHtml.match(/
([\s\S]*?)<\/span>/i)?.[1]
?? cardHtml.match(/([\s\S]*?)<\/div>/i)?.[1]
?? '',
);
if (!city) return null;
return { city, cond, temp };
}
function extractWeatherCardsFromHtml(html) {
const grid = extractDivBlock(html, 'weather-grid');
const cards = extractClassDivBlocks(grid, 'weather-card')
.map(parseWeatherCard)
.filter(Boolean);
if (cards.length > 0) return cards;
const { selectTouristWeatherCards } = wechatDailyNewsInlineInternals;
return selectTouristWeatherCards(extractClassDivBlocks(html, 'weather-card'))
.map(parseWeatherCard)
.filter(Boolean);
}
function extractWeatherTodaySummary(html) {
const todayBlock = extractDivBlock(html, 'weather-today');
if (!todayBlock) return '';
return stripInlineText(todayBlock).slice(0, 120);
}
export function normalizePushWeatherCity(raw, { defaultCity = DEFAULT_WEATHER_CITY } = {}) {
const city = normalizeWeatherCity(raw);
if (!city) return defaultCity;
if (wechatDailyNewsInlineInternals.isPopularTouristCityName(city)) return city;
if (/^[\u4e00-\u9fff]{2,6}$/u.test(city)) return city;
return defaultCity;
}
function buildDressingAdvice({ cond = '', temp = '' }) {
const text = `${cond} ${temp}`;
if (/雨|雪/u.test(text)) return '建议带伞,穿防水外套。';
if (/冷|冰|0°|1°|2°|3°|4°|5°/u.test(text)) return '偏冷,注意保暖,外套加厚。';
if (/30|31|32|33|34|35|36/u.test(text)) return '偏热,轻薄透气,注意防晒补水。';
if (/晴/u.test(text)) return '晴好天气,适合户外,记得防晒。';
if (/阴|云/u.test(text)) return '温差可能较大,建议洋葱式穿衣。';
return '根据体感增减衣物,出门前再看一眼实时预报。';
}
export function buildWeatherPushTextFromHtml(
html,
{ city = DEFAULT_WEATHER_CITY, manageHint = '回复「取消3」关闭推送' } = {},
) {
const normalizedCity = normalizePushWeatherCity(city);
const cards = extractWeatherCardsFromHtml(html);
const matched = cards.find((item) => normalizeWeatherCity(item.city) === normalizedCity)
?? cards[0]
?? null;
const todaySummary = extractWeatherTodaySummary(html);
const lines = [`🌤 每日天气 · ${normalizedCity}`];
if (matched) {
const detail = [matched.cond, matched.temp].filter(Boolean).join(' · ');
lines.push('', detail || '详见下方概览');
lines.push('', `穿衣建议:${buildDressingAdvice(matched)}`);
} else if (todaySummary) {
lines.push('', todaySummary);
} else {
lines.push('', '今日天气数据正在更新,请稍后再看。');
}
if (cards.length > 1 && matched) {
const others = cards
.filter((item) => normalizeWeatherCity(item.city) !== normalizedCity)
.slice(0, 3)
.map((item) => `${item.city} ${[item.cond, item.temp].filter(Boolean).join(' ')}`.trim());
if (others.length) {
lines.push('', `周边速览:${others.join(' · ')}`);
}
}
if (manageHint) lines.push('', manageHint);
return lines.join('\n');
}
export async function formatWeatherPushDelivery({
city = DEFAULT_WEATHER_CITY,
now = Date.now(),
timezone = 'Asia/Shanghai',
env = process.env,
h5Root = process.cwd(),
pool = null,
} = {}) {
const normalizedCity = normalizePushWeatherCity(city);
const config = await resolveNewsMorningPushConfig({ pool, env });
const effectiveTimezone = config.timezone || timezone;
if (!config.sourceUserId) {
return {
text: `🌤 每日天气 · ${normalizedCity}\n\n天气数据暂不可用,请稍后再试。\n\n回复「取消3」关闭推送`,
verifiedHtmlUrls: [],
};
}
const page = findLatestNewsMorningPage({
h5Root,
userId: config.sourceUserId,
slugPattern: config.pageSlugPattern,
date: new Date(now),
timezone: effectiveTimezone,
});
if (
!page?.localPath
|| !fs.existsSync(page.localPath)
|| !isNewsMorningPageForToday(page, { date: new Date(now), timezone: effectiveTimezone })
) {
return {
text: `🌤 每日天气 · ${normalizedCity}\n\n今日天气正在整理中,稍后会推送到本服务号。\n\n回复「取消3」关闭推送`,
verifiedHtmlUrls: [],
};
}
const html = fs.readFileSync(page.localPath, 'utf8');
return {
text: buildWeatherPushTextFromHtml(html, { city: normalizedCity }),
verifiedHtmlUrls: [],
};
}