import { parseHourMinute } from '../schedule-intent.mjs'; import { normalizePushWeatherCity } from './push-content/weather-delivery.mjs'; import { getPushSubscriptionById, PUSH_SUBSCRIPTION_ITEMS, resolvePushSubscriptionFromAlias, } from './push-subscription-catalog.mjs'; const SUBSCRIPTION_ID_PATTERN = /^([1-9]|10)$/u; const SUBSCRIPTION_ID_PREFIX_PATTERN = /^([1-9]|10)[\s::,,、-]+(.+)$/u; const SUBSCRIPTION_MODIFY_PATTERN = /^([1-9]|10)\s*(改|换|调整|设为|设置为|调到|改到|换成|改为)\s*(.+)$/u; const SUBSCRIPTION_CANCEL_ID_PATTERN = /^取消\s*([1-9]|10)$/u; const LIST_REQUEST_PATTERN = /^(我的订阅|订阅状态|订阅列表|已开通订阅)$/u; const HELP_REQUEST_PATTERN = /^(订阅帮助|订阅菜单|推送菜单|推送帮助)$/u; const CANCEL_ALL_PATTERN = /^(全部取消|取消全部|取消所有订阅)$/u; const WEATHER_CITY_UPDATE_PATTERN = /^([1-9]|10)\s*(改|换|调整|设为|设置为|调到|改到|换成|改为)\s*城市\s*(.+)$/u; const WEATHER_ALIAS_CITY_UPDATE_PATTERN = /^(天气|每日天气)\s*(改|换|调整|设为|设置为|调到|改到|换成|改为)\s*城市?\s*(.+)$/u; function parseCityToken(raw) { const text = String(raw ?? '') .trim() .replace(/^(城市|设为|设置为|改成|改为|换为|换成)/u, '') .replace(/\s+/g, '') .replace(/市$/u, ''); if (!text || text.length > 6) return null; if (!/^[\u4e00-\u9fff]{2,6}$/u.test(text)) return null; return normalizePushWeatherCity(text); } function parseWeatherConfirmSegment(segment) { const raw = String(segment ?? '').trim(); if (!raw) return { hour: null, minute: null, options: null }; const time = parseHourMinute(raw); if (time) { const remainder = raw .replace(/(?:今天|明天|后天|今晚|明晚|明早|今早)?(?:早上|上午|清晨|中午|下午|傍晚|晚上|凌晨)?(?:[0-9]{1,2}|[零一二两三四五六七八九十]{1,3})(?:点|:|:)(?:半|[0-9]{1,2}分?)?/u, '') .trim(); const city = parseCityToken(remainder); return { hour: time.hour, minute: time.minute, options: city ? { city } : null, }; } const city = parseCityToken(raw); if (city) { return { hour: null, minute: null, options: { city } }; } return { hour: null, minute: null, options: null }; } function parseSubscriptionIdToken(token) { const raw = String(token ?? '').trim(); if (SUBSCRIPTION_ID_PATTERN.test(raw)) return Number(raw); if (raw === '1') return 1; if (raw === '2') return 2; if (raw === '3') return 3; if (raw === '4') return 4; if (raw === '5') return 5; if (raw === '6') return 6; if (raw === '7') return 7; if (raw === '8') return 8; if (raw === '9') return 9; if (raw === '10') return 10; return null; } /** * 解析开通指令:`2` / `2 7点` / `2 7:30` * @returns {{ item: object, hour: number|null, minute: number|null }|null} */ export function parsePushSubscriptionConfirmReply( text, { defaultHourResolver = null } = {}, ) { const raw = String(text ?? '').trim(); if (!raw) return null; const compact = raw.replace(/\s+/g, ''); const bareId = parseSubscriptionIdToken(compact); if (bareId != null && compact.length <= 2) { const item = getPushSubscriptionById(bareId); if (!item) return null; return { item, hour: null, minute: null }; } const prefixed = raw.match(SUBSCRIPTION_ID_PREFIX_PATTERN); if (prefixed) { const item = getPushSubscriptionById(Number(prefixed[1])); if (!item) return null; if (item.key === 'weather') { const weather = parseWeatherConfirmSegment(prefixed[2]); return { item, hour: weather.hour, minute: weather.minute, options: weather.options, }; } const time = parseHourMinute(prefixed[2]); return { item, hour: time?.hour ?? null, minute: time?.minute ?? null, options: null }; } return null; } /** @returns {{ item: object, options: { city: string } }|null} */ export function parsePushSubscriptionCityUpdate(text) { const raw = String(text ?? '').trim(); if (!raw) return null; const byId = raw.match(WEATHER_CITY_UPDATE_PATTERN); if (byId) { const item = getPushSubscriptionById(Number(byId[1])); const city = parseCityToken(byId[3]); if (!item || item.key !== 'weather' || !city) return null; return { item, options: { city } }; } const byAlias = raw.replace(/\s+/g, '').match(WEATHER_ALIAS_CITY_UPDATE_PATTERN); if (byAlias) { const item = resolvePushSubscriptionFromAlias('天气'); const city = parseCityToken(byAlias[3]); if (!item || !city) return null; return { item, options: { city } }; } return null; } /** @returns {{ item: object }|{ all: true }|null} */ export function parsePushSubscriptionCancelRequest(text) { const raw = String(text ?? '').trim(); const compact = raw.replace(/\s+/g, ''); if (!compact) return null; if (CANCEL_ALL_PATTERN.test(compact)) return { all: true }; const byId = compact.match(SUBSCRIPTION_CANCEL_ID_PATTERN); if (byId) { const item = getPushSubscriptionById(Number(byId[1])); return item ? { item } : null; } const aliasItem = resolvePushSubscriptionFromAlias(compact); if (!aliasItem) return null; const hasCancelCue = /(取消|关闭|不要|停止)/u.test(compact); if (!hasCancelCue) return null; return { item: aliasItem }; } /** @returns {{ item: object, hour: number, minute: number }|null} */ export function parsePushSubscriptionModifyRequest(text) { const raw = String(text ?? '').trim(); if (!raw) return null; const byId = raw.match(SUBSCRIPTION_MODIFY_PATTERN); if (byId) { const item = getPushSubscriptionById(Number(byId[1])); const time = parseHourMinute(byId[3]); if (!item || !time) return null; return { item, hour: time.hour, minute: time.minute }; } const compact = raw.replace(/\s+/g, ''); const aliasItem = resolvePushSubscriptionFromAlias(compact); const hasModifyCue = /(改|换|调整|设为|设置为|调到|改到|换成|改为)/u.test(compact); if (!aliasItem || !hasModifyCue) return null; const time = parseHourMinute(raw); if (!time) return null; return { item: aliasItem, hour: time.hour, minute: time.minute }; } export function parsePushSubscriptionListRequest(text) { const compact = String(text ?? '').replace(/\s+/g, ''); return LIST_REQUEST_PATTERN.test(compact); } export function parsePushSubscriptionHelpRequest(text) { const compact = String(text ?? '').replace(/\s+/g, ''); return HELP_REQUEST_PATTERN.test(compact); } export function resolvePushSubscriptionConfirmTime( item, { hour = null, minute = null, defaultHourResolver = null } = {}, ) { if (Number.isInteger(hour) && hour >= 0 && hour <= 23 && Number.isInteger(minute) && minute >= 0 && minute <= 59) { return { hour, minute }; } if (typeof defaultHourResolver === 'function') { const resolved = defaultHourResolver(item); if (resolved) return resolved; } return { hour: item.defaultHour, minute: item.defaultMinute }; } export function isPushSubscriptionNumericReply(text) { return parsePushSubscriptionConfirmReply(text) != null; } export function listPushSubscriptionAliasKeys() { return PUSH_SUBSCRIPTION_ITEMS.flatMap((item) => item.aliases); }