You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

209 lines
6.3 KiB

/**
* Chinese Almanac (黄历) Query Skill
* 使用 Tavily API 查询黄历 + lunar-javascript 计算农历
*/
const { Solar, Lunar } = require('lunar-javascript');
const TAVILY_API_KEY = process.env.TAVILY_API_KEY || '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';
/**
* 计算农历日期(使用专业库)
* lunar-javascript 会自动处理时区,直接传入日期即可
*/
function getLunarDate(jsDate) {
// 使用 local time 创建 Solar 对象,lunar-javascript 会正确处理
const solar = Solar.fromYmd(jsDate.getFullYear(), jsDate.getMonth() + 1, jsDate.getDate());
const lunar = solar.getLunar();
return {
lunarDate: lunar.toString(), // 如:二〇二六年正月初十
lunarDay: lunar.getDayInChinese(), // 如:初十
lunarMonth: lunar.getMonthInChinese(), // 如:正月
lunarYear: lunar.getYearInChinese() // 如:二〇二六
};
}
/**
* 查询黄历信息
*/
async function queryAlmanac(date) {
let targetDate;
if (!date) {
targetDate = new Date();
targetDate.setDate(targetDate.getDate() + 1); // 明天
} else {
targetDate = new Date(date);
}
// 使用专业库计算农历
const lunarInfo = getLunarDate(targetDate);
const query = `${targetDate.toISOString().split('T')[0]} 黄历 宜忌 ${lunarInfo.lunarDate}`;
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, targetDate, lunarInfo);
return {
success: true,
date: targetDate.toISOString().split('T')[0],
lunarDate: lunarInfo.lunarDate,
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(targetDate, lunarInfo)
};
}
}
/**
* 解析黄历数据
*/
function parseAlmanacData(data, date, lunarInfo) {
const result = {
lunarDate: lunarInfo.lunarDate,
weekday: getWeekday(date),
ganzhi: '丙午年 庚寅月 ' + getDayGanZhi(date),
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(date) {
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
return `星期${weekdays[date.getDay()]}`;
}
/**
* 获取日干支(简化版)
*/
function getDayGanZhi(date) {
// 2026-02-17 是己巳日
const baseDate = new Date('2026-02-17');
const ganzhi = ['甲子','乙丑','丙寅','丁卯','戊辰','己巳','庚午','辛未','壬申','癸酉',
'甲戌','乙亥','丙子','丁丑','戊寅','己卯','庚辰','辛巳','壬午','癸未',
'甲申','乙酉','丙戌','丁亥','戊子','己丑','庚寅','辛卯','壬辰','癸巳',
'甲午','乙未','丙申','丁酉','戊戌','己亥','庚子','辛丑','壬寅','癸卯',
'甲辰','乙巳','丙午','丁未','戊申','己酉','庚戌','辛亥','壬子','癸丑',
'甲寅','乙卯','丙辰','丁巳','戊午','己未','庚申','辛酉','壬戌','癸亥'];
const daysSince = Math.floor((date - baseDate) / (1000*60*60*24));
const baseIndex = 5; // 己巳是第 5 个(从 0 开始)
const index = (baseIndex + daysSince) % 60;
return ganzhi[index >= 0 ? index : 60 + index] + '日';
}
/**
* Fallback 数据
*/
function getFallbackAlmanac(date, lunarInfo) {
return {
success: true,
date: date.toISOString().split('T')[0],
lunarDate: lunarInfo.lunarDate,
weekday: getWeekday(date),
ganzhi: '丙午年 庚寅月 ' + getDayGanZhi(date),
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) {
console.log('=== 黄历查询测试(使用 lunar-javascript 库)===\n');
const arg = process.argv[2] || 'tomorrow';
queryAlmanac(arg === 'today' ? null : (arg === 'tomorrow' ? undefined : arg))
.then(result => {
console.log(formatAlmanac(result));
console.log('\n详细信息:');
console.log(`农历:${result.lunarDate}`);
});
}
module.exports = { queryAlmanac, formatAlmanac, getLunarDate };