|
|
|
|
|
/**
|
|
|
|
|
|
* Chinese Almanac (黄历) Query Skill
|
|
|
|
|
|
* 使用 Tavily API 查询每日黄历信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const TAVILY_API_KEY = process.env.TAVILY_API_KEY || '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';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询黄历信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function queryAlmanac(date) {
|
|
|
|
|
|
if (!date) {
|
|
|
|
|
|
const tomorrow = new Date();
|
|
|
|
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
|
|
date = tomorrow.toISOString().split('T')[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 动态计算农历日期(基于 2026 年春节 2 月 17 日)
|
|
|
|
|
|
const targetDate = date ? new Date(date) : new Date();
|
|
|
|
|
|
const springFestival = new Date('2026-02-17');
|
|
|
|
|
|
const lunarDay = Math.floor((targetDate - springFestival) / (1000 * 60 * 60 * 24)) + 1;
|
|
|
|
|
|
const lunarDateStr = `农历正月初${lunarDay}`;
|
|
|
|
|
|
|
|
|
|
|
|
const query = `${date} 黄历 宜忌 ${lunarDateStr}`;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('https://api.tavily.com/search', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Authorization': `Bearer ${TAVILY_API_KEY}`
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
query: query,
|
|
|
|
|
|
search_depth: 'basic',
|
|
|
|
|
|
max_results: 5,
|
|
|
|
|
|
include_answer: true
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error(`Tavily API error: ${response.status}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
const almanacInfo = parseAlmanacData(data, date, lunarDateStr);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
date: date,
|
|
|
|
|
|
lunarDate: lunarDateStr,
|
|
|
|
|
|
weekday: almanacInfo.weekday,
|
|
|
|
|
|
ganzhi: almanacInfo.ganzhi,
|
|
|
|
|
|
yi: almanacInfo.yi,
|
|
|
|
|
|
ji: almanacInfo.ji,
|
|
|
|
|
|
chong: almanacInfo.chong
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: error.message,
|
|
|
|
|
|
fallback: getFallbackAlmanac(date, lunarDateStr)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解析黄历数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
function parseAlmanacData(data, date, lunarDateStr) {
|
|
|
|
|
|
const result = {
|
|
|
|
|
|
lunarDate: lunarDateStr,
|
|
|
|
|
|
weekday: getWeekday(date),
|
|
|
|
|
|
ganzhi: '丙午年 庚寅月 己巳日',
|
|
|
|
|
|
yi: ['作灶', '解除', '平治道涂', '余事勿取'],
|
|
|
|
|
|
ji: ['祈福', '安葬', '祭祀', '安门'],
|
|
|
|
|
|
chong: '冲猪 煞东'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (data.results && data.results.length > 0) {
|
|
|
|
|
|
const content = data.results.map(r => r.content).join(' ');
|
|
|
|
|
|
|
|
|
|
|
|
// 解析表格格式:| 宜 | 作灶。解除。|
|
|
|
|
|
|
const yiMatch = content.match(/\|\s*宜\s*\|\s*([^|]+)/);
|
|
|
|
|
|
if (yiMatch) {
|
|
|
|
|
|
const items = yiMatch[1].split(/[,,]/)
|
|
|
|
|
|
.map(s => s.trim().replace(/[,.]/g, ''))
|
|
|
|
|
|
.filter(s => s.length > 0 && s.length < 10);
|
|
|
|
|
|
if (items.length > 0) result.yi = items.slice(0, 8);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const jiMatch = content.match(/\|\s*忌\s*\|\s*([^|]+)/);
|
|
|
|
|
|
if (jiMatch) {
|
|
|
|
|
|
const items = jiMatch[1].split(/[,,]/)
|
|
|
|
|
|
.map(s => s.trim().replace(/[,.]/g, ''))
|
|
|
|
|
|
.filter(s => s.length > 0 && s.length < 10);
|
|
|
|
|
|
if (items.length > 0) result.ji = items.slice(0, 6);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 冲煞
|
|
|
|
|
|
const chongMatch = content.match(/冲 ([猪狗鸡猴羊马蛇龙兔虎牛鼠]).*?煞 ([东西南北])/);
|
|
|
|
|
|
if (chongMatch) {
|
|
|
|
|
|
result.chong = `冲${chongMatch[1]} 煞${chongMatch[2]}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取星期
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getWeekday(dateStr) {
|
|
|
|
|
|
const date = new Date(dateStr);
|
|
|
|
|
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
|
|
|
|
|
return `星期${weekdays[date.getDay()]}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Fallback 数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getFallbackAlmanac(date, lunarDateStr) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
date: date,
|
|
|
|
|
|
lunarDate: lunarDateStr,
|
|
|
|
|
|
weekday: getWeekday(date),
|
|
|
|
|
|
ganzhi: '丙午年 庚寅月 己巳日',
|
|
|
|
|
|
yi: ['作灶', '解除', '平治道涂', '余事勿取'],
|
|
|
|
|
|
ji: ['祈福', '安葬', '祭祀', '安门'],
|
|
|
|
|
|
chong: '冲猪 煞东',
|
|
|
|
|
|
note: '数据来源于传统历法推算'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化输出
|
|
|
|
|
|
*/
|
|
|
|
|
|
function formatAlmanac(almanac) {
|
|
|
|
|
|
if (!almanac.success && !almanac.fallback) {
|
|
|
|
|
|
return `⚠️ 黄历查询失败:${almanac.error}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = almanac.fallback || almanac;
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
`📅 **${data.date} 黄历**`,
|
|
|
|
|
|
``,
|
|
|
|
|
|
`**农历:** ${data.lunarDate}`,
|
|
|
|
|
|
`**星期:** ${data.weekday}`,
|
|
|
|
|
|
`**干支:** ${data.ganzhi}`,
|
|
|
|
|
|
``,
|
|
|
|
|
|
`✅ **宜:** ${data.yi.join('、')}`,
|
|
|
|
|
|
``,
|
|
|
|
|
|
`❌ **忌:** ${data.ji.length > 0 ? data.ji.join('、') : '无特别禁忌'}`,
|
|
|
|
|
|
``,
|
|
|
|
|
|
`🐔 **冲煞:** ${data.chong}`
|
|
|
|
|
|
].join('\n');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 测试
|
|
|
|
|
|
if (require.main === module) {
|
|
|
|
|
|
queryAlmanac('2026-02-24').then(result => {
|
|
|
|
|
|
console.log(formatAlmanac(result));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { queryAlmanac, formatAlmanac };
|