|
|
|
|
|
/**
|
|
|
|
|
|
* Daily Horoscope & Fortune Analysis
|
|
|
|
|
|
*
|
|
|
|
|
|
* 查询每日星座运势,结合黄历和八字分析
|
|
|
|
|
|
* 为桐哥 Agent 提供每日运势推送功能
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const https = require('https');
|
|
|
|
|
|
|
|
|
|
|
|
// 王院长生辰信息
|
|
|
|
|
|
const USER_BIRTH_INFO = {
|
|
|
|
|
|
name: '王院长',
|
|
|
|
|
|
birthday: '1984-05-16',
|
|
|
|
|
|
birthTime: '23:00-24:00', // 子时
|
|
|
|
|
|
lunarBirthday: '甲子年 四月十六',
|
|
|
|
|
|
zodiac: '鼠',
|
|
|
|
|
|
westernZodiac: '金牛座',
|
|
|
|
|
|
bazi: {
|
|
|
|
|
|
year: '甲子',
|
|
|
|
|
|
month: '己巳',
|
|
|
|
|
|
day: '丙午',
|
|
|
|
|
|
hour: '戊子',
|
|
|
|
|
|
dayMaster: '丙火' // 日主
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 十二星座
|
|
|
|
|
|
const ZODIAC_SIGNS = [
|
|
|
|
|
|
'白羊座', '金牛座', '双子座', '巨蟹座',
|
|
|
|
|
|
'狮子座', '处女座', '天秤座', '天蝎座',
|
|
|
|
|
|
'射手座', '摩羯座', '水瓶座', '双鱼座'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 使用 Tavily 搜索运势信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function searchHoroscope(date, sign) {
|
|
|
|
|
|
const tomorrow = new Date(date);
|
|
|
|
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
|
|
const dateStr = tomorrow.toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
|
|
const query = `${dateStr} ${sign} 运势 星座运程`;
|
|
|
|
|
|
|
|
|
|
|
|
// 调用 Tavily API
|
|
|
|
|
|
const tavilyApiKey = process.env.TAVILY_API_KEY || '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';
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const postData = JSON.stringify({
|
|
|
|
|
|
query: query,
|
|
|
|
|
|
search_depth: 'basic',
|
|
|
|
|
|
include_answer: true,
|
|
|
|
|
|
max_results: 3,
|
|
|
|
|
|
topic: 'general'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
|
|
hostname: 'api.tavily.com',
|
|
|
|
|
|
port: 443,
|
|
|
|
|
|
path: '/search',
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Content-Length': Buffer.byteLength(postData),
|
|
|
|
|
|
'Authorization': `Bearer ${tavilyApiKey}`
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const req = https.request(options, (res) => {
|
|
|
|
|
|
let data = '';
|
|
|
|
|
|
res.on('data', (chunk) => data += chunk);
|
|
|
|
|
|
res.on('end', () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = JSON.parse(data);
|
|
|
|
|
|
resolve(result);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
reject(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
req.on('error', reject);
|
|
|
|
|
|
req.write(postData);
|
|
|
|
|
|
req.end();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询黄历
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function queryAlmanac(date) {
|
|
|
|
|
|
// 调用 chinese-almanac skill
|
|
|
|
|
|
const almanacPath = '/root/.openclaw/workspace/skills/chinese-almanac/almanac.js';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const almanac = require(almanacPath);
|
|
|
|
|
|
const result = await almanac.queryAlmanac(date);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('黄历查询失败:', e.message);
|
|
|
|
|
|
// Fallback: 基础信息
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
date: date,
|
|
|
|
|
|
lunarDate: '查询失败',
|
|
|
|
|
|
yi: [],
|
|
|
|
|
|
ji: [],
|
|
|
|
|
|
chong: ''
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 八字分析 - 根据日主和明日五行
|
|
|
|
|
|
*/
|
|
|
|
|
|
function analyzeBazi(targetDate, userBazi) {
|
|
|
|
|
|
const date = new Date(targetDate);
|
|
|
|
|
|
const dayMaster = userBazi.dayMaster; // 丙火
|
|
|
|
|
|
|
|
|
|
|
|
// 简化的五行分析(实际应该用更精确的干支计算)
|
|
|
|
|
|
const dayOfWeek = date.getDay();
|
|
|
|
|
|
const elements = ['金', '木', '水', '火', '土'];
|
|
|
|
|
|
const tomorrowElement = elements[dayOfWeek % 5];
|
|
|
|
|
|
|
|
|
|
|
|
// 五行生克关系
|
|
|
|
|
|
const shengKe = {
|
|
|
|
|
|
'丙火': {
|
|
|
|
|
|
'金': '火克金 - 财星,利于求财',
|
|
|
|
|
|
'木': '木生火 - 印星,贵人相助',
|
|
|
|
|
|
'水': '水克火 - 官杀,压力较大',
|
|
|
|
|
|
'火': '火比和 - 兄弟,竞争激烈',
|
|
|
|
|
|
'土': '火生土 - 食伤,才华展现'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const analysis = shengKe[dayMaster]?.[tomorrowElement] || '五行平衡';
|
|
|
|
|
|
|
|
|
|
|
|
// 根据分析生成建议
|
|
|
|
|
|
const suggestions = {
|
|
|
|
|
|
'财星': {
|
|
|
|
|
|
lucky: ['商务谈判', '投资决策', '签订合同'],
|
|
|
|
|
|
avoid: ['大额支出', '冒险投资']
|
|
|
|
|
|
},
|
|
|
|
|
|
'印星': {
|
|
|
|
|
|
lucky: ['学习新知', '拜访长辈', '规划未来'],
|
|
|
|
|
|
avoid: ['独自决策', '匆忙行动']
|
|
|
|
|
|
},
|
|
|
|
|
|
'官杀': {
|
|
|
|
|
|
lucky: ['处理公务', '解决问题', '锻炼身体'],
|
|
|
|
|
|
avoid: ['与人争执', '高风险活动']
|
|
|
|
|
|
},
|
|
|
|
|
|
'比和': {
|
|
|
|
|
|
lucky: ['团队合作', '朋友聚会', '交流沟通'],
|
|
|
|
|
|
avoid: ['单打独斗', '固执己见']
|
|
|
|
|
|
},
|
|
|
|
|
|
'食伤': {
|
|
|
|
|
|
lucky: ['创意工作', '表达展示', '休闲娱乐'],
|
|
|
|
|
|
avoid: ['批评他人', '过度消费']
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const key = Object.keys(suggestions).find(k => analysis.includes(k)) || '比和';
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
dayMaster: dayMaster,
|
|
|
|
|
|
tomorrowElement: tomorrowElement,
|
|
|
|
|
|
analysis: analysis,
|
|
|
|
|
|
suggestions: suggestions[key]
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成运势报告
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function generateFortuneReport(targetDate) {
|
|
|
|
|
|
const tomorrow = new Date(targetDate);
|
|
|
|
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
|
|
const dateStr = tomorrow.toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`[Horoscope] 查询 ${dateStr} 的运势...`);
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 查询黄历
|
|
|
|
|
|
const almanac = await queryAlmanac(dateStr);
|
|
|
|
|
|
console.log('[Horoscope] 黄历查询完成');
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 搜索金牛座运势(王院长是金牛座)
|
|
|
|
|
|
let horoscopeText = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const horoscopeResult = await searchHoroscope(dateStr, '金牛座');
|
|
|
|
|
|
if (horoscopeResult.answer) {
|
|
|
|
|
|
horoscopeText = horoscopeResult.answer;
|
|
|
|
|
|
} else if (horoscopeResult.results && horoscopeResult.results.length > 0) {
|
|
|
|
|
|
horoscopeText = horoscopeResult.results[0].content;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('星座运势搜索失败:', e.message);
|
|
|
|
|
|
horoscopeText = '今日金牛座运势平稳,保持积极心态。';
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[Horoscope] 星座运势查询完成');
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 八字分析
|
|
|
|
|
|
const baziAnalysis = analyzeBazi(dateStr, USER_BIRTH_INFO.bazi);
|
|
|
|
|
|
console.log('[Horoscope] 八字分析完成');
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 生成综合报告
|
|
|
|
|
|
const report = {
|
|
|
|
|
|
date: dateStr,
|
|
|
|
|
|
user: USER_BIRTH_INFO.name,
|
|
|
|
|
|
zodiac: USER_BIRTH_INFO.westernZodiac,
|
|
|
|
|
|
chineseZodiac: USER_BIRTH_INFO.zodiac,
|
|
|
|
|
|
almanac: almanac,
|
|
|
|
|
|
horoscope: {
|
|
|
|
|
|
text: horoscopeText,
|
|
|
|
|
|
source: 'Tavily AI Search'
|
|
|
|
|
|
},
|
|
|
|
|
|
bazi: baziAnalysis,
|
|
|
|
|
|
overallLuck: calculateOverallLuck(almanac, baziAnalysis)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return report;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算整体运势指数
|
|
|
|
|
|
*/
|
|
|
|
|
|
function calculateOverallLuck(almanac, bazi) {
|
|
|
|
|
|
let score = 3; // 基础 3 星
|
|
|
|
|
|
|
|
|
|
|
|
// 黄历加分项
|
|
|
|
|
|
if (almanac.yi && almanac.yi.length > 3) score += 0.5;
|
|
|
|
|
|
if (almanac.chong && !almanac.chong.includes('鼠')) score += 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
// 八字加分项
|
|
|
|
|
|
if (baziAnalysis.analysis.includes('贵人') || baziAnalysis.analysis.includes('财星')) {
|
|
|
|
|
|
score += 0.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const stars = Math.min(5, Math.max(1, Math.round(score)));
|
|
|
|
|
|
return '★'.repeat(stars) + '☆'.repeat(5 - stars);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化运势报告为消息
|
|
|
|
|
|
*/
|
|
|
|
|
|
function formatFortuneMessage(report) {
|
|
|
|
|
|
const { date, user, zodiac, almanac, horoscope, bazi, overallLuck } = report;
|
|
|
|
|
|
|
|
|
|
|
|
let message = `📅 **${user} · 明日运势**\n`;
|
|
|
|
|
|
message += `${date} | ${almanac.lunarDate || '农历查询中'}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `✨ **整体运势**: ${overallLuck}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `🗓️ **黄历宜忌**\n`;
|
|
|
|
|
|
message += `宜:${almanac.yi?.join('、') || '查询中'}\n`;
|
|
|
|
|
|
message += `忌:${almanac.ji?.join('、') || '查询中'}\n`;
|
|
|
|
|
|
if (almanac.chong) {
|
|
|
|
|
|
message += `冲煞:${almanac.chong}\n`;
|
|
|
|
|
|
}
|
|
|
|
|
|
message += '\n';
|
|
|
|
|
|
|
|
|
|
|
|
message += `♉ **${zodiac}运势**\n`;
|
|
|
|
|
|
message += `${horoscope.text}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `🔮 **八字分析**\n`;
|
|
|
|
|
|
message += `日主:${bazi.dayMaster}\n`;
|
|
|
|
|
|
message += `明日五行:${bazi.tomorrowElement}\n`;
|
|
|
|
|
|
message += `${bazi.analysis}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `💡 **趋吉避凶建议**\n`;
|
|
|
|
|
|
message += `✅ 宜:${bazi.suggestions?.lucky?.join('、') || '顺其自然'}\n`;
|
|
|
|
|
|
message += `❌ 忌:${bazi.suggestions?.avoid?.join('、') || '谨慎行事'}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `_桐哥祝您明日顺利!_ 🌟`;
|
|
|
|
|
|
|
|
|
|
|
|
return message;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 主函数 - 获取明日运势
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function getDailyHoroscope(options = {}) {
|
|
|
|
|
|
const targetDate = options.date || new Date().toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const report = await generateFortuneReport(targetDate);
|
|
|
|
|
|
const message = formatFortuneMessage(report);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
report: report,
|
|
|
|
|
|
message: message
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('运势查询失败:', e);
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: e.message,
|
|
|
|
|
|
message: '⚠️ 运势查询暂时失败,请稍后再试。'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出函数
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
getDailyHoroscope,
|
|
|
|
|
|
generateFortuneReport,
|
|
|
|
|
|
formatFortuneMessage,
|
|
|
|
|
|
searchHoroscope,
|
|
|
|
|
|
queryAlmanac,
|
|
|
|
|
|
analyzeBazi,
|
|
|
|
|
|
USER_BIRTH_INFO,
|
|
|
|
|
|
ZODIAC_SIGNS
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 测试运行
|
|
|
|
|
|
if (require.main === module) {
|
|
|
|
|
|
getDailyHoroscope().then(result => {
|
|
|
|
|
|
console.log('\n=== 运势报告 ===\n');
|
|
|
|
|
|
console.log(result.message);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|