|
|
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 每日运势推送 - 简化版
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能:
|
|
|
|
|
|
* 1. 使用 Tavily 搜索星座运势
|
|
|
|
|
|
* 2. 查询黄历信息
|
|
|
|
|
|
* 3. 生成运势报告
|
|
|
|
|
|
* 4. 通过 Telegram 发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* 用户信息:
|
|
|
|
|
|
* - 王院长
|
|
|
|
|
|
* - 生日:1984 年 5 月 16 日 23:00-24:00 (子时)
|
|
|
|
|
|
* - 星座:金牛座
|
|
|
|
|
|
* - 生肖:鼠
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const https = require('https');
|
|
|
|
|
|
const http = require('http');
|
|
|
|
|
|
|
|
|
|
|
|
// 配置
|
|
|
|
|
|
const CONFIG = {
|
|
|
|
|
|
tavilyApiKey: process.env.TAVILY_API_KEY || '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',
|
|
|
|
|
|
telegramBotToken: process.env.TELEGRAM_BOT_TOKEN || '7047245486:AAF504oCHZpfEIx3-3VXJYSSS9XelkV6o3g',
|
|
|
|
|
|
telegramChatId: '5237946060', // 王院长的 Telegram ID
|
|
|
|
|
|
timezone: 'Asia/Shanghai'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 用户信息
|
|
|
|
|
|
const USER = {
|
|
|
|
|
|
name: '王院长',
|
|
|
|
|
|
birthday: '1984-05-16',
|
|
|
|
|
|
birthTime: '23:00-24:00 (子时)',
|
|
|
|
|
|
zodiac: '金牛座',
|
|
|
|
|
|
chineseZodiac: '鼠'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Tavily 搜索
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function tavilySearch(query, searchDepth = 'basic') {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const postData = JSON.stringify({
|
|
|
|
|
|
query: query,
|
|
|
|
|
|
search_depth: searchDepth,
|
|
|
|
|
|
include_answer: true,
|
|
|
|
|
|
max_results: 3
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
|
|
hostname: 'api.tavily.com',
|
|
|
|
|
|
port: 443,
|
|
|
|
|
|
path: '/search',
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Content-Length': Buffer.byteLength(postData),
|
|
|
|
|
|
'Authorization': `Bearer ${CONFIG.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();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询黄历 (使用 web 接口)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function queryAlmanac(date) {
|
|
|
|
|
|
// 这里可以使用在线黄历 API 或网页抓取
|
|
|
|
|
|
// 简化版:返回基础信息
|
|
|
|
|
|
const d = new Date(date);
|
|
|
|
|
|
const lunarMonths = ['正月','二月','三月','四月','五月','六月','七月','八月','九月','十月','冬月','腊月'];
|
|
|
|
|
|
const lunarMonth = lunarMonths[d.getMonth()];
|
|
|
|
|
|
const lunarDay = d.getDate();
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
date: date,
|
|
|
|
|
|
lunarDate: `农历${lunarMonth}${getLunarDay(lunarDay)}`,
|
|
|
|
|
|
yi: ['开市', '交易', '纳财', '安床'], // 简化版,实际应该查询真实黄历
|
|
|
|
|
|
ji: ['嫁娶', '栽种', '安葬'],
|
|
|
|
|
|
chong: '冲鸡 煞西'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getLunarDay(day) {
|
|
|
|
|
|
const days = ['初一','初二','初三','初四','初五','初六','初七','初八','初九','初十',
|
|
|
|
|
|
'十一','十二','十三','十四','十五','十六','十七','十八','十九','二十',
|
|
|
|
|
|
'廿一','廿二','廿三','廿四','廿五','廿六','廿七','廿八','廿九','三十'];
|
|
|
|
|
|
return days[day - 1] || '未知';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 八字分析 (简化版)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function analyzeBazi(date, userBirthday) {
|
|
|
|
|
|
const d = new Date(date);
|
|
|
|
|
|
const dayOfWeek = d.getDay();
|
|
|
|
|
|
const elements = ['金', '木', '水', '火', '土'];
|
|
|
|
|
|
const tomorrowElement = elements[dayOfWeek % 5]; // 修正:确保索引在 0-4 范围内
|
|
|
|
|
|
|
|
|
|
|
|
// 简化的五行分析
|
|
|
|
|
|
const analysis = {
|
|
|
|
|
|
'金': '金旺之日,适合决断和执行',
|
|
|
|
|
|
'木': '木旺之日,适合学习和规划',
|
|
|
|
|
|
'水': '水旺之日,适合沟通和交流',
|
|
|
|
|
|
'火': '火旺之日,适合展示和表达',
|
|
|
|
|
|
'土': '土旺之日,适合稳定和积累'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const suggestions = {
|
|
|
|
|
|
'金': {
|
|
|
|
|
|
lucky: ['签署合同', '做决策', '执行计划'],
|
|
|
|
|
|
avoid: ['犹豫不决', '拖延']
|
|
|
|
|
|
},
|
|
|
|
|
|
'木': {
|
|
|
|
|
|
lucky: ['学习新知', '制定计划', '拜访贵人'],
|
|
|
|
|
|
avoid: ['冲动行事', '独自决策']
|
|
|
|
|
|
},
|
|
|
|
|
|
'水': {
|
|
|
|
|
|
lucky: ['商务谈判', '团队合作', '社交活动'],
|
|
|
|
|
|
avoid: ['与人争执', '情绪化']
|
|
|
|
|
|
},
|
|
|
|
|
|
'火': {
|
|
|
|
|
|
lucky: ['演讲展示', '创意工作', '运动健身'],
|
|
|
|
|
|
avoid: ['与人冲突', '过度消费']
|
|
|
|
|
|
},
|
|
|
|
|
|
'土': {
|
|
|
|
|
|
lucky: ['整理归纳', '储蓄理财', '陪伴家人'],
|
|
|
|
|
|
avoid: ['冒险投资', '大额支出']
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
element: tomorrowElement,
|
|
|
|
|
|
analysis: analysis[tomorrowElement],
|
|
|
|
|
|
suggestions: suggestions[tomorrowElement]
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成运势报告
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function generateFortuneReport() {
|
|
|
|
|
|
const tomorrow = new Date();
|
|
|
|
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
|
|
const dateStr = tomorrow.toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`[INFO] 开始生成 ${dateStr} 的运势报告...`);
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 搜索星座运势
|
|
|
|
|
|
let horoscopeText = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const horoscopeResult = await tavilySearch(`${dateStr} ${USER.zodiac} 运势 星座运程`, 'basic');
|
|
|
|
|
|
if (horoscopeResult.answer) {
|
|
|
|
|
|
horoscopeText = horoscopeResult.answer;
|
|
|
|
|
|
} else if (horoscopeResult.results && horoscopeResult.results.length > 0) {
|
|
|
|
|
|
horoscopeText = horoscopeResult.results[0].content;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
horoscopeText = `${USER.zodiac}明日运势平稳,保持积极心态。`;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('[ERROR] 星座运势搜索失败:', e.message);
|
|
|
|
|
|
horoscopeText = `${USER.zodiac}明日运势平稳。`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 查询黄历
|
|
|
|
|
|
let almanac = {};
|
|
|
|
|
|
try {
|
|
|
|
|
|
almanac = await queryAlmanac(dateStr);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('[ERROR] 黄历查询失败:', e.message);
|
|
|
|
|
|
almanac = {
|
|
|
|
|
|
lunarDate: '查询中',
|
|
|
|
|
|
yi: ['待查询'],
|
|
|
|
|
|
ji: ['待查询'],
|
|
|
|
|
|
chong: ''
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 八字分析
|
|
|
|
|
|
const bazi = analyzeBazi(dateStr, USER.birthday);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 生成报告
|
|
|
|
|
|
const report = {
|
|
|
|
|
|
date: dateStr,
|
|
|
|
|
|
user: USER.name,
|
|
|
|
|
|
zodiac: USER.zodiac,
|
|
|
|
|
|
chineseZodiac: USER.chineseZodiac,
|
|
|
|
|
|
almanac: almanac,
|
|
|
|
|
|
horoscope: horoscopeText,
|
|
|
|
|
|
bazi: bazi
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return report;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化消息
|
|
|
|
|
|
*/
|
|
|
|
|
|
function formatMessage(report) {
|
|
|
|
|
|
const { date, user, zodiac, chineseZodiac, almanac, horoscope, bazi } = report;
|
|
|
|
|
|
|
|
|
|
|
|
let message = `🌙 桐哥的每日运势提醒\n\n`;
|
|
|
|
|
|
message += `📅 明日:${date}\n`;
|
|
|
|
|
|
message += `♉ 星座:${zodiac}\n`;
|
|
|
|
|
|
message += `🐭 生肖:${chineseZodiac}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `✨ **星座运势**\n`;
|
|
|
|
|
|
message += `${horoscope}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `🏮 **黄历信息**\n`;
|
|
|
|
|
|
message += `${almanac.lunarDate}\n`;
|
|
|
|
|
|
message += `宜:${almanac.yi?.join('、') || '待查询'}\n`;
|
|
|
|
|
|
message += `忌:${almanac.ji?.join('、') || '待查询'}\n`;
|
|
|
|
|
|
if (almanac.chong) {
|
|
|
|
|
|
message += `冲煞:${almanac.chong}\n`;
|
|
|
|
|
|
}
|
|
|
|
|
|
message += `\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `🔮 **五行分析**\n`;
|
|
|
|
|
|
message += `明日五行:${bazi.element}\n`;
|
|
|
|
|
|
message += `${bazi.analysis}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `🎯 **趋吉避凶**\n`;
|
|
|
|
|
|
message += `✅ 宜:${bazi.suggestions?.lucky?.join('、') || '顺其自然'}\n`;
|
|
|
|
|
|
message += `❌ 忌:${bazi.suggestions?.avoid?.join('、') || '谨慎行事'}\n\n`;
|
|
|
|
|
|
|
|
|
|
|
|
message += `_桐哥祝您明日顺心如意!_ ❤️`;
|
|
|
|
|
|
|
|
|
|
|
|
return message;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发送 Telegram 消息
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function sendTelegramMessage(message) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const postData = JSON.stringify({
|
|
|
|
|
|
chat_id: CONFIG.telegramChatId,
|
|
|
|
|
|
text: message,
|
|
|
|
|
|
parse_mode: 'Markdown'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
|
|
hostname: 'api.telegram.org',
|
|
|
|
|
|
port: 443,
|
|
|
|
|
|
path: `/bot${CONFIG.telegramBotToken}/sendMessage`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Content-Length': Buffer.byteLength(postData)
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const req = https.request(options, (res) => {
|
|
|
|
|
|
let data = '';
|
|
|
|
|
|
res.on('data', (chunk) => data += chunk);
|
|
|
|
|
|
res.on('end', () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = JSON.parse(data);
|
|
|
|
|
|
if (result.ok) {
|
|
|
|
|
|
console.log('[INFO] Telegram 消息发送成功');
|
|
|
|
|
|
resolve(result);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('[ERROR] Telegram API 错误:', result);
|
|
|
|
|
|
reject(new Error(result.description));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
reject(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
req.on('error', reject);
|
|
|
|
|
|
req.write(postData);
|
|
|
|
|
|
req.end();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 主函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function main() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('[INFO] 开始执行每日运势推送...');
|
|
|
|
|
|
|
|
|
|
|
|
// 生成报告
|
|
|
|
|
|
const report = await generateFortuneReport();
|
|
|
|
|
|
console.log('[INFO] 运势报告生成完成');
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化消息
|
|
|
|
|
|
const message = formatMessage(report);
|
|
|
|
|
|
console.log('[INFO] 消息格式化完成');
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息
|
|
|
|
|
|
await sendTelegramMessage(message);
|
|
|
|
|
|
console.log('[INFO] 消息发送完成');
|
|
|
|
|
|
|
|
|
|
|
|
console.log('\n=== 运势报告预览 ===\n');
|
|
|
|
|
|
console.log(message);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[FATAL] 执行失败:', error);
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行
|
|
|
|
|
|
if (require.main === module) {
|
|
|
|
|
|
main();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { main, generateFortuneReport, formatMessage, sendTelegramMessage };
|