|
|
/** |
|
|
* 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 };
|
|
|
|