koishi-plugin-stock 2.0.14 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/images/qi.jpeg +0 -0
- package/lib/commands/heart-commands.d.ts +4 -0
- package/lib/commands/heart-commands.js +64 -0
- package/lib/commands/stock-commands.d.ts +4 -0
- package/lib/commands/stock-commands.js +140 -0
- package/lib/commands/verify-card.d.ts +4 -0
- package/lib/commands/verify-card.js +63 -0
- package/lib/core/broadcast.d.ts +11 -0
- package/lib/core/broadcast.js +55 -0
- package/lib/core/heart-data.d.ts +13 -0
- package/lib/core/heart-data.js +88 -0
- package/lib/core/trading-day.d.ts +5 -0
- package/lib/core/trading-day.js +30 -0
- package/lib/index.backup.d.ts +32 -0
- package/lib/index.backup.js +812 -0
- package/lib/index.d.ts +136 -3
- package/lib/index.js +27 -689
- package/lib/utils/blacklist.d.ts +1 -0
- package/lib/utils/blacklist.js +92 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
- 用户发送"选股 [策略名称]"命令时,自动从 `dyq_select` API获取选股结果
|
|
12
12
|
- 用户发送"骑"命令时,自动返回 `images/qi.jpeg` 图片
|
|
13
13
|
- 用户发送"心法抽卡"命令时,随机抽取一条交易心法并播放对应音频
|
|
14
|
+
- 用户发送"我要验牌 [序号]"命令时,播放指定序号的心法语音
|
|
15
|
+
- 用户发送"我要验牌 全部"命令时,显示所有心法文本列表
|
|
14
16
|
- 支持定时广播任务,可自定义时间发送活跃市值、涨停/跌停看板(仅交易日广播)
|
|
15
17
|
- 支持为每个指令单独设置黑名单,可限制特定用户使用特定功能
|
|
16
18
|
- 自动格式化数据显示给用户
|
|
@@ -56,12 +58,35 @@ npm install koishi-plugin-stock
|
|
|
56
58
|
- 发送"选股 [策略名称或编号]"获取选股结果,支持的策略:N型(1)、填坑(2)、少妇(3)、突破(4)、补票(5)、少妇pro(6)
|
|
57
59
|
- 发送"骑"获取图片
|
|
58
60
|
- 发送"心法抽卡"随机抽取一条交易心法并播放对应音频
|
|
61
|
+
- 发送"我要验牌 全部"查看所有心法文本列表
|
|
62
|
+
- 发送"我要验牌 <序号>"播放指定序号的心法语音(1-58)
|
|
59
63
|
|
|
60
64
|
配置黑名单可在插件设置中进行,将特定用户ID添加到相应指令的黑名单中即可限制其使用权限。
|
|
61
65
|
|
|
62
66
|
## 更新日志
|
|
63
67
|
|
|
64
|
-
### v2.
|
|
68
|
+
### v2.1.3 (2026-02-28)
|
|
69
|
+
- 🐛 **紧急修复**:修复"骑"命令图片读取问题
|
|
70
|
+
- ✅ 改为从本地images/qi.jpeg读取图片而非网络请求
|
|
71
|
+
- 🔧 增强错误日志,提供更详细的调试信息
|
|
72
|
+
- 📦 保持所有其他功能不变
|
|
73
|
+
|
|
74
|
+
### v2.1.2 (2026-02-28)
|
|
75
|
+
- 🐛 **紧急修复**:修复插件指令注册问题
|
|
76
|
+
- ✅ 恢复所有股票相关指令:活跃市值、异动、涨停看板、跌停看板、选股、骑
|
|
77
|
+
- 🔧 将middleware方式改为ctx.command方式,确保指令在插件界面正确显示
|
|
78
|
+
- 📦 保持所有功能逻辑不变,仅修复指令注册方式
|
|
79
|
+
- 🐛 **紧急修复**:修复插件配置项声明问题
|
|
80
|
+
- ✅ 正确导出Config和ConfigSchema供Koishi识别
|
|
81
|
+
- 📦 保持所有功能不变,仅修复配置导出问题
|
|
82
|
+
- 🎉 **重大架构重构**:全面模块化设计,代码结构更清晰
|
|
83
|
+
- 📁 新增模块化目录结构:`core/`, `commands/`, `utils/`
|
|
84
|
+
- 🔧 核心功能解耦:心法数据、交易日判断、广播调度独立模块
|
|
85
|
+
- 🚀 提升可维护性:主入口文件从39KB精简至5.7KB
|
|
86
|
+
- 📦 更好的扩展性:新增功能模块更容易集成
|
|
87
|
+
- 新增"我要验牌"命令:支持查看指定序号的心法语音或查看全部心法文本
|
|
88
|
+
- 新增验牌功能黑白名单配置:支持为验牌功能单独设置用户和频道黑名单
|
|
89
|
+
- 增强用户体验:提供清晰的参数使用说明和错误提示
|
|
65
90
|
- 优化交易日判断:使用stock.svip886.com提供的专业交易日API替代timor.tech节假日接口
|
|
66
91
|
- 改进API调用:直接解析JSON响应,提高判断准确性
|
|
67
92
|
- 增强错误处理:API请求失败时默认跳过任务,确保安全性
|
package/images/qi.jpeg
CHANGED
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeartCommands = void 0;
|
|
4
|
+
const heart_data_1 = require("../core/heart-data");
|
|
5
|
+
const blacklist_1 = require("../utils/blacklist");
|
|
6
|
+
class HeartCommands {
|
|
7
|
+
static register(ctx, config) {
|
|
8
|
+
const logger = ctx.logger('stock');
|
|
9
|
+
// 心法抽卡命令
|
|
10
|
+
ctx.command('心法抽卡', '随机抽取一条交易心法')
|
|
11
|
+
.action(async ({ session }) => {
|
|
12
|
+
logger.info(`[心法抽卡] 收到命令请求,当前心法数据量: ${heart_data_1.HeartMethodManager.getCount()}`);
|
|
13
|
+
// 检查功能是否启用
|
|
14
|
+
if (!config.enableHeartMethod) {
|
|
15
|
+
logger.warn('[心法抽卡] 功能被禁用');
|
|
16
|
+
return '心法抽卡功能未启用';
|
|
17
|
+
}
|
|
18
|
+
// 检查黑名单
|
|
19
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '心法抽卡', config)) {
|
|
20
|
+
logger.info('[心法抽卡] 用户在黑名单中,静默处理');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// 检查是否有心法数据
|
|
24
|
+
if (heart_data_1.HeartMethodManager.getCount() === 0) {
|
|
25
|
+
logger.error('[心法抽卡] 心法数据为空');
|
|
26
|
+
return '暂无心法数据,请联系管理员';
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
// 随机选择一个心法
|
|
30
|
+
const randomIndex = Math.floor(Math.random() * heart_data_1.HeartMethodManager.getCount());
|
|
31
|
+
const selectedMethod = heart_data_1.HeartMethodManager.getByIndex(randomIndex);
|
|
32
|
+
if (!selectedMethod) {
|
|
33
|
+
logger.error('[心法抽卡] 无法获取选定的心法');
|
|
34
|
+
return '心法抽卡失败,请稍后重试';
|
|
35
|
+
}
|
|
36
|
+
logger.info(`[心法抽卡] 用户 ${session?.userId || '未知'} 抽中: ${selectedMethod.code} - ${selectedMethod.text}`);
|
|
37
|
+
// 读取音频文件
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const path = require('path');
|
|
40
|
+
const audioPath = path.resolve(__dirname, `../../audio/${selectedMethod.file}`);
|
|
41
|
+
logger.info(`[心法抽卡] 尝试读取音频文件: ${audioPath}`);
|
|
42
|
+
if (!fs.existsSync(audioPath)) {
|
|
43
|
+
logger.warn(`[心法抽卡] 音频文件不存在: ${audioPath}`);
|
|
44
|
+
// 如果音频文件不存在,只返回文本
|
|
45
|
+
return `🃏 心法抽卡\n\n${selectedMethod.text}\n\n(音频文件缺失: ${selectedMethod.file})`;
|
|
46
|
+
}
|
|
47
|
+
// 读取音频文件并转换为base64
|
|
48
|
+
const audioData = fs.readFileSync(audioPath);
|
|
49
|
+
const base64Audio = audioData.toString('base64');
|
|
50
|
+
logger.info(`[心法抽卡] 音频文件读取成功,大小: ${audioData.length} 字节`);
|
|
51
|
+
// 返回音频消息
|
|
52
|
+
const response = `<audio src="data:audio/mp3;base64,${base64Audio}" />${selectedMethod.text}`;
|
|
53
|
+
logger.info('[心法抽卡] 成功生成响应消息');
|
|
54
|
+
return response;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logger.error('[心法抽卡] 执行失败:', error);
|
|
58
|
+
logger.error('[心法抽卡] 错误堆栈:', error.stack);
|
|
59
|
+
return '心法抽卡失败,请稍后重试';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.HeartCommands = HeartCommands;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StockCommands = void 0;
|
|
4
|
+
const blacklist_1 = require("../utils/blacklist");
|
|
5
|
+
class StockCommands {
|
|
6
|
+
static register(ctx, config) {
|
|
7
|
+
const logger = ctx.logger('stock');
|
|
8
|
+
// 活跃市值命令
|
|
9
|
+
ctx.command('活跃市值', '获取活跃市值数据')
|
|
10
|
+
.action(async ({ session }) => {
|
|
11
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '活跃市值', config)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const responseText = await ctx.http.get('http://stock.svip886.com/api/indexes', { responseType: 'text' });
|
|
16
|
+
return `📊 指数看板:\n\n${responseText}`;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
logger.error('获取活跃市值数据失败:', error);
|
|
20
|
+
return '获取活跃市值数据失败,请稍后重试。';
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// 异动命令
|
|
24
|
+
ctx.command('异动 <stockCode:text>', '获取指定股票的异动分析数据')
|
|
25
|
+
.action(async ({ session }, stockCode) => {
|
|
26
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '异动', config)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!stockCode) {
|
|
30
|
+
return '请输入股票代码,格式:异动 [股票代码]';
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const responseText = await ctx.http.get(`http://stock.svip886.com/api/analyze?code=${stockCode}`, { responseType: 'text' });
|
|
34
|
+
return `📈 股票 ${stockCode} 异动分析:\n\n${responseText}`;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logger.error('获取股票异动数据失败:', error);
|
|
38
|
+
return `获取股票 ${stockCode} 异动数据失败,请稍后重试。`;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// 涨停看板命令
|
|
42
|
+
ctx.command('涨停看板', '获取涨停看板图片')
|
|
43
|
+
.action(async ({ session }) => {
|
|
44
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '涨停看板', config)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const imageUrl = 'http://stock.svip886.com/api/limit_up.png';
|
|
49
|
+
const imageBuffer = await ctx.http.get(imageUrl, { responseType: 'arraybuffer' });
|
|
50
|
+
const base64Image = Buffer.from(imageBuffer).toString('base64');
|
|
51
|
+
return `<img src="data:image/png;base64,${base64Image}" />`;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger.error('获取涨停看板图片失败:', error);
|
|
55
|
+
return '获取涨停看板图片失败,请稍后重试。';
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// 跌停看板命令
|
|
59
|
+
ctx.command('跌停看板', '获取跌停看板图片')
|
|
60
|
+
.action(async ({ session }) => {
|
|
61
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '跌停看板', config)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const imageUrl = 'http://stock.svip886.com/api/limit_down.png';
|
|
66
|
+
const imageBuffer = await ctx.http.get(imageUrl, { responseType: 'arraybuffer' });
|
|
67
|
+
const base64Image = Buffer.from(imageBuffer).toString('base64');
|
|
68
|
+
return `<img src="data:image/png;base64,${base64Image}" />`;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.error('获取跌停看板图片失败:', error);
|
|
72
|
+
return '获取跌停看板图片失败,请稍后重试。';
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// 选股命令
|
|
76
|
+
ctx.command('选股 <strategy:text>', '根据指定策略选股')
|
|
77
|
+
.action(async ({ session }, strategy) => {
|
|
78
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '选股', config)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!strategy) {
|
|
82
|
+
return '请输入选股策略,格式:选股 [策略名称或编号]\n支持的策略:N型(1)、填坑(2)、少妇(3)、突破(4)、补票(5)、少妇pro(6)';
|
|
83
|
+
}
|
|
84
|
+
// 映射策略名称到API端点
|
|
85
|
+
const strategyMap = {
|
|
86
|
+
'N型': 'dx',
|
|
87
|
+
'填坑': 'tk',
|
|
88
|
+
'少妇': 'sf',
|
|
89
|
+
'突破': 'tp',
|
|
90
|
+
'补票': 'bp',
|
|
91
|
+
'少妇pro': 'sfpro',
|
|
92
|
+
'1': 'dx',
|
|
93
|
+
'2': 'tk',
|
|
94
|
+
'3': 'sf',
|
|
95
|
+
'4': 'tp',
|
|
96
|
+
'5': 'bp',
|
|
97
|
+
'6': 'sfpro'
|
|
98
|
+
};
|
|
99
|
+
const apiEndpoint = strategyMap[strategy];
|
|
100
|
+
if (!apiEndpoint) {
|
|
101
|
+
return `不支持的选股策略: ${strategy}\n支持的策略:N型(1)、填坑(2)、少妇(3)、突破(4)、补票(5)、少妇pro(6)`;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const responseText = await ctx.http.get(`http://stock.svip886.com/api/dyq_${apiEndpoint}`, { responseType: 'text' });
|
|
105
|
+
return `🎯 选股结果 (${strategy}): \n\n${responseText}`;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
logger.error('获取选股数据失败:', error);
|
|
109
|
+
return '获取选股数据失败,请稍后重试。';
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// 骑命令
|
|
113
|
+
ctx.command('骑', '获取骑图片')
|
|
114
|
+
.action(async ({ session }) => {
|
|
115
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '骑', config)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
// 读取本地图片文件
|
|
120
|
+
const fs = require('fs');
|
|
121
|
+
const path = require('path');
|
|
122
|
+
const imagePath = path.resolve(__dirname, '../../images/qi.jpeg');
|
|
123
|
+
if (!fs.existsSync(imagePath)) {
|
|
124
|
+
logger.error('骑图片文件不存在:', imagePath);
|
|
125
|
+
return '骑图片文件缺失,请联系管理员';
|
|
126
|
+
}
|
|
127
|
+
// 读取图片文件并转换为base64
|
|
128
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
129
|
+
const base64Image = imageBuffer.toString('base64');
|
|
130
|
+
return `<img src="data:image/jpeg;base64,${base64Image}" />`;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
logger.error('获取骑图片失败:', error);
|
|
134
|
+
logger.error('错误详情:', error.stack);
|
|
135
|
+
return '获取骑图片失败,请稍后重试。';
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.StockCommands = StockCommands;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VerifyCardCommand = void 0;
|
|
4
|
+
const heart_data_1 = require("../core/heart-data");
|
|
5
|
+
const blacklist_1 = require("../utils/blacklist");
|
|
6
|
+
class VerifyCardCommand {
|
|
7
|
+
static register(ctx, config) {
|
|
8
|
+
const logger = ctx.logger('stock');
|
|
9
|
+
// 我要验牌命令
|
|
10
|
+
ctx.command('我要验牌 [param:text]', '验牌功能:查看指定序号的心法或查看全部心法')
|
|
11
|
+
.action(async ({ session }, param) => {
|
|
12
|
+
// 检查黑名单
|
|
13
|
+
if ((0, blacklist_1.isUserInSpecificBlacklist)(session, '验牌', config)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// 检查是否有心法数据
|
|
17
|
+
if (heart_data_1.HeartMethodManager.getCount() === 0) {
|
|
18
|
+
return '暂无心法数据';
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
// 处理"全部"参数
|
|
22
|
+
if (param && param.trim() === '全部') {
|
|
23
|
+
logger.info(`[验牌] 用户 ${session?.userId || '未知'} 请求查看全部心法`);
|
|
24
|
+
const result = heart_data_1.HeartMethodManager.getAllTextWithNumbers();
|
|
25
|
+
logger.info(`[验牌] 成功生成全部心法列表,共 ${heart_data_1.HeartMethodManager.getCount()} 条`);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
// 处理数字序号参数
|
|
29
|
+
if (param && /^\d+$/.test(param.trim())) {
|
|
30
|
+
const index = parseInt(param.trim()) - 1; // 转换为0基索引
|
|
31
|
+
if (index < 0 || index >= heart_data_1.HeartMethodManager.getCount()) {
|
|
32
|
+
return `序号超出范围,请输入 1-${heart_data_1.HeartMethodManager.getCount()} 之间的数字`;
|
|
33
|
+
}
|
|
34
|
+
const selectedMethod = heart_data_1.HeartMethodManager.getByIndex(index);
|
|
35
|
+
logger.info(`[验牌] 用户 ${session?.userId || '未知'} 查看序号 ${index + 1}: ${selectedMethod?.code}`);
|
|
36
|
+
// 读取对应的音频文件
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const audioPath = path.resolve(__dirname, `../../audio/${selectedMethod?.file}`);
|
|
40
|
+
if (!fs.existsSync(audioPath)) {
|
|
41
|
+
// 音频文件不存在,只返回文本
|
|
42
|
+
return `🃏 验牌结果 #${index + 1}\n\n${selectedMethod?.text}\n\n(音频文件缺失: ${selectedMethod?.file})`;
|
|
43
|
+
}
|
|
44
|
+
// 读取音频文件并转换为base64
|
|
45
|
+
const audioData = fs.readFileSync(audioPath);
|
|
46
|
+
const base64Audio = audioData.toString('base64');
|
|
47
|
+
// 返回音频消息
|
|
48
|
+
const response = `<audio src="data:audio/mp3;base64,${base64Audio}" />#${index + 1} ${selectedMethod?.text}`;
|
|
49
|
+
logger.info(`[验牌] 成功生成序号 ${index + 1} 的响应`);
|
|
50
|
+
return response;
|
|
51
|
+
}
|
|
52
|
+
// 参数格式错误
|
|
53
|
+
return '参数格式错误。请使用:\n"我要验牌 全部" - 查看所有心法\n"我要验牌 <序号>" - 查看指定序号的心法(1-58)';
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
logger.error('[验牌] 执行失败:', error);
|
|
57
|
+
logger.error('[验牌] 错误详情:', error.stack);
|
|
58
|
+
return '验牌失败,请稍后重试';
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.VerifyCardCommand = VerifyCardCommand;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
interface BroadcastTask {
|
|
3
|
+
times: string;
|
|
4
|
+
type: 'private' | 'channel';
|
|
5
|
+
targetIds: string;
|
|
6
|
+
content: '活跃市值' | '涨停看板' | '跌停看板';
|
|
7
|
+
}
|
|
8
|
+
export declare class BroadcastScheduler {
|
|
9
|
+
static setup(ctx: Context, config: any, tasks: BroadcastTask[]): void;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BroadcastScheduler = void 0;
|
|
4
|
+
const trading_day_1 = require("../core/trading-day");
|
|
5
|
+
class BroadcastScheduler {
|
|
6
|
+
static setup(ctx, config, tasks) {
|
|
7
|
+
const logger = ctx.logger('stock');
|
|
8
|
+
if (!tasks || tasks.length === 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// 简化版本:使用setInterval每分钟检查
|
|
12
|
+
ctx.setInterval(async () => {
|
|
13
|
+
const now = new Date();
|
|
14
|
+
const shanghaiTime = new Date(now.getTime() + 8 * 60 * 60 * 1000);
|
|
15
|
+
const currentTime = `${shanghaiTime.getHours().toString().padStart(2, '0')}:${shanghaiTime.getMinutes().toString().padStart(2, '0')}`;
|
|
16
|
+
for (const task of tasks) {
|
|
17
|
+
const times = task.times.split(',').map(t => t.trim());
|
|
18
|
+
if (times.includes(currentTime)) {
|
|
19
|
+
try {
|
|
20
|
+
// 判断是否为交易日
|
|
21
|
+
const tradingDay = await trading_day_1.TradingDayChecker.isTradingDay(ctx);
|
|
22
|
+
if (!tradingDay) {
|
|
23
|
+
logger.info('[定时任务跳过] 今日非交易日');
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// 执行广播任务
|
|
27
|
+
const targets = task.targetIds.split(',').map(id => id.trim()).filter(id => id);
|
|
28
|
+
if (task.type === 'private') {
|
|
29
|
+
targets.forEach(targetId => {
|
|
30
|
+
ctx.bots.forEach(bot => {
|
|
31
|
+
bot.sendPrivateMessage(targetId, `[${task.content}] 定时推送`).catch(e => {
|
|
32
|
+
logger.error(`私聊消息发送失败: ${e.message}`);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
targets.forEach(targetId => {
|
|
39
|
+
ctx.bots.forEach(bot => {
|
|
40
|
+
bot.sendMessage(targetId, `[${task.content}] 定时推送`).catch(e => {
|
|
41
|
+
logger.error(`频道消息发送失败: ${e.message}`);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger.error('[定时任务] 执行失败:', error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}, 60000); // 每分钟检查一次
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.BroadcastScheduler = BroadcastScheduler;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface HeartMethod {
|
|
2
|
+
code: string;
|
|
3
|
+
text: string;
|
|
4
|
+
file: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const HEART_METHODS: HeartMethod[];
|
|
7
|
+
export declare class HeartMethodManager {
|
|
8
|
+
static getAllMethods(): HeartMethod[];
|
|
9
|
+
static getByIndex(index: number): HeartMethod | undefined;
|
|
10
|
+
static getCount(): number;
|
|
11
|
+
static getTextByIndex(index: number): string;
|
|
12
|
+
static getAllTextWithNumbers(): string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeartMethodManager = exports.HEART_METHODS = void 0;
|
|
4
|
+
// 心法数据(直接嵌入避免编码问题)
|
|
5
|
+
exports.HEART_METHODS = [
|
|
6
|
+
{ code: 'M01', text: '谋定而后动。', file: 'M01.mp3' },
|
|
7
|
+
{ code: 'M02', text: '顺大势,逆小势', file: 'M02.mp3' },
|
|
8
|
+
{ code: 'M03A', text: '上联风吹哪页读哪页,下联是哪页难读撕哪页,横批是什么?去你大爷', file: 'M03A.mp3' },
|
|
9
|
+
{ code: 'M03B', text: '风吹哪页读哪页,记住,一切都是最好安排', file: 'M03B.mp3' },
|
|
10
|
+
{ code: 'M04', text: '你越想找好的,你越是接盘', file: 'M04.mp3' },
|
|
11
|
+
{ code: 'M05', text: '就是想赚大钱都是逆人性的', file: 'M05.mp3' },
|
|
12
|
+
{ code: 'M06', text: '一定要学会屏蔽掉这些噪音,同时建立自己的一个非常良好的心态', file: 'M06.mp3' },
|
|
13
|
+
{ code: 'M07', text: '当你觉得舒服的时候,恰恰是风险最大的时刻', file: 'M07.mp3' },
|
|
14
|
+
{ code: 'M08', text: '投资就是反人性的,就是你要学会守株待兔', file: 'M08.mp3' },
|
|
15
|
+
{ code: 'M09', text: '365 天看懂哪天做哪天', file: 'M09.mp3' },
|
|
16
|
+
{ code: 'M10', text: '所有挣钱的过程,等待的时间都是很煎熬的', file: 'M10.mp3' },
|
|
17
|
+
{ code: 'M11', text: '不追、不动、不慌、不乱摸', file: 'M11.mp3' },
|
|
18
|
+
{ code: 'M12', text: '股市里边赚的是什么钱?大富翁赚的什么钱?就是熬人的钱', file: 'M12.mp3' },
|
|
19
|
+
{ code: 'M13A', text: '曼城阵容拿住不动', file: 'M13A.mp3' },
|
|
20
|
+
{ code: 'M14', text: '赚了钱了拿出来消费', file: 'M14.mp3' },
|
|
21
|
+
{ code: 'M15', text: '与国家同在,只输时间不输钱。', file: 'M15.mp3' },
|
|
22
|
+
{ code: 'M16', text: '市场没有证明你是对的时候,你就是错了', file: 'M16.mp3' },
|
|
23
|
+
{ code: 'M17', text: '在这个市场里边,你不亏钱才叫赚钱,而不是不赚钱叫亏', file: 'M17.mp3' },
|
|
24
|
+
{ code: 'M18', text: '早上涨晚上涨早晚上涨,早下跌晚下跌早晚下跌', file: 'M18.mp3' },
|
|
25
|
+
{ code: 'M19', text: '现在赚钱还是以后赚大钱', file: 'M19.mp3' },
|
|
26
|
+
{ code: 'M20', text: '只有成功才是成功他妈', file: 'M20.mp3' },
|
|
27
|
+
{ code: 'M21', text: '永远不要对你期待的事情抱有不切实际的幻想', file: 'M21.mp3' },
|
|
28
|
+
{ code: 'M22B', text: '王德峰教授说过,如果一个男人过了四十岁还不相信有命的话,此人悟性极差', file: 'M22B.mp3' },
|
|
29
|
+
{ code: 'M23', text: '一定要放飞,把钱揣兜里', file: 'M23.mp3' },
|
|
30
|
+
{ code: 'M24A', text: '大 A 踏不了空、扽起来就卖!', file: 'M24A.mp3' },
|
|
31
|
+
{ code: 'M25', text: '不用害怕,怕你就会输一辈子', file: 'M25.mp3' },
|
|
32
|
+
{ code: 'M26', text: '一派胡言啊', file: 'M26.mp3' },
|
|
33
|
+
{ code: 'M27', text: '在这个世界上啊,该是你的绝逼是你的', file: 'M27.mp3' },
|
|
34
|
+
{ code: 'M28C', text: '踏踏实实的,别着急啊', file: 'M28C.mp3' },
|
|
35
|
+
{ code: 'M29', text: '你怎么认知慢就是快,这两个最重要的就是什么,什么时候慢,什么时候快', file: 'M29.mp3' },
|
|
36
|
+
{ code: 'M30', text: '能听懂就听,听不懂就反复听', file: 'M30.mp3' },
|
|
37
|
+
{ code: 'M31', text: '只选最美,只买最强,只拿最硬。', file: 'M31.mp3' },
|
|
38
|
+
{ code: 'M32', text: '别小路过载,也别熵增', file: 'M32.mp3' },
|
|
39
|
+
{ code: 'M33', text: '不要对票有感情,谁是县长不重要,重要的是我要当县长夫人', file: 'M33.mp3' },
|
|
40
|
+
{ code: 'M34', text: '不要猜,心无所住,物来则应,过去不留', file: 'M34.mp3' },
|
|
41
|
+
{ code: 'M35', text: '一切皆有可能,记住了吗?我们要做的是什么?是应对', file: 'M35.mp3' },
|
|
42
|
+
{ code: 'M36', text: '要信早信', file: 'M36.mp3' },
|
|
43
|
+
{ code: 'M37', text: '为你的知行合一账户充值。', file: 'M37.mp3' },
|
|
44
|
+
{ code: 'M38', text: '先慢后快,越来越快,坚持做对的事儿,方能行稳致远。世界上怕就怕认真二字', file: 'M38.mp3' },
|
|
45
|
+
{ code: 'M39', text: '拿不住就卖。卖完之后你的世界安静了,你的心就静下来了,你可以更多的去学习', file: 'M39.mp3' },
|
|
46
|
+
{ code: 'M40', text: '莫道今年春将近,明年春色倍还人', file: 'M40.mp3' },
|
|
47
|
+
{ code: 'M41', text: '市场总会奖励有信仰的人', file: 'M41.mp3' },
|
|
48
|
+
{ code: 'M42', text: '心无杂念,知行合一', file: 'M42.mp3' },
|
|
49
|
+
{ code: 'M43', text: '不要因为正确的决定带来坏结果就改变它', file: 'M43.mp3' },
|
|
50
|
+
{ code: 'M44', text: '完美图形干错也要干', file: 'M44.mp3' },
|
|
51
|
+
{ code: 'M45', text: '时刻要充满敬畏之心,如履薄冰', file: 'M45.mp3' },
|
|
52
|
+
{ code: 'M46A', text: '戒骄戒躁', file: 'M46A.mp3' },
|
|
53
|
+
{ code: 'M47', text: '物极必反', file: 'M47.mp3' },
|
|
54
|
+
{ code: 'M48', text: '一切以盘面为准', file: 'M48.mp3' },
|
|
55
|
+
{ code: 'M49', text: '积小胜为大胜', file: 'M49.mp3' },
|
|
56
|
+
{ code: 'M50', text: '闷声发大财', file: 'M50.mp3' },
|
|
57
|
+
{ code: 'M51', text: '只英雄救美,不锦上添花', file: 'M51.mp3' },
|
|
58
|
+
{ code: 'M52B', text: '就莫名其妙,人生就是莫名其妙', file: 'M52B.mp3' },
|
|
59
|
+
{ code: 'M53', text: '赚钱的票别做亏,以及设好止损', file: 'M53.mp3' },
|
|
60
|
+
{ code: 'M54', text: '就是你能赚多少钱不知道,没人知道,但是你能亏多少钱,那一定是你自己能决定的', file: 'M54.mp3' },
|
|
61
|
+
{ code: 'M55', text: '留得青山在,一定有下一个小米', file: 'M55.mp3' },
|
|
62
|
+
{ code: 'M56', text: '不要抱有偏见', file: 'M56.mp3' },
|
|
63
|
+
{ code: 'M57', text: '散户最重要的是心态,是不要和这个市场杠,要从自身找原因', file: 'M57.mp3' },
|
|
64
|
+
];
|
|
65
|
+
// 心法相关工具函数
|
|
66
|
+
class HeartMethodManager {
|
|
67
|
+
static getAllMethods() {
|
|
68
|
+
return exports.HEART_METHODS;
|
|
69
|
+
}
|
|
70
|
+
static getByIndex(index) {
|
|
71
|
+
return exports.HEART_METHODS[index];
|
|
72
|
+
}
|
|
73
|
+
static getCount() {
|
|
74
|
+
return exports.HEART_METHODS.length;
|
|
75
|
+
}
|
|
76
|
+
static getTextByIndex(index) {
|
|
77
|
+
const method = this.getByIndex(index);
|
|
78
|
+
return method ? method.text : '';
|
|
79
|
+
}
|
|
80
|
+
static getAllTextWithNumbers() {
|
|
81
|
+
let result = '🃏 全部心法列表:\n\n';
|
|
82
|
+
exports.HEART_METHODS.forEach((method, index) => {
|
|
83
|
+
result += `${index + 1}. ${method.text}\n`;
|
|
84
|
+
});
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.HeartMethodManager = HeartMethodManager;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TradingDayChecker = void 0;
|
|
4
|
+
class TradingDayChecker {
|
|
5
|
+
static async isTradingDay(ctx) {
|
|
6
|
+
if (!this.logger) {
|
|
7
|
+
this.logger = ctx.logger('stock');
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const tradingResponse = await ctx.http.get('https://stock.svip886.com/api/is_trading_day');
|
|
11
|
+
// 检查响应结构
|
|
12
|
+
if (tradingResponse && typeof tradingResponse === 'object' && tradingResponse.success === true) {
|
|
13
|
+
const isTradingDay = tradingResponse.is_trading_day === true;
|
|
14
|
+
this.logger.info(`[交易日判断] 日期: ${tradingResponse.date}, 是否交易日: ${isTradingDay}, 最近交易日: ${tradingResponse.latest_trade_date}`);
|
|
15
|
+
return isTradingDay;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this.logger.error('[交易日判断] API返回格式异常');
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
this.logger.error(`[交易日判断] API请求失败: ${e.message}`);
|
|
24
|
+
// 请求失败时,默认不执行任务以保证安全
|
|
25
|
+
this.logger.warn('[交易日判断] 请求失败,默认跳过任务');
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.TradingDayChecker = TradingDayChecker;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export interface BroadcastTask {
|
|
3
|
+
times: string;
|
|
4
|
+
type: 'private' | 'channel';
|
|
5
|
+
targetIds: string;
|
|
6
|
+
content: '活跃市值' | '涨停看板' | '跌停看板';
|
|
7
|
+
}
|
|
8
|
+
export interface Config {
|
|
9
|
+
enableDebugLog?: boolean;
|
|
10
|
+
allCommandsBlacklist?: string[];
|
|
11
|
+
activeMarketCapBlacklist?: string[];
|
|
12
|
+
stockAlertBlacklist?: string[];
|
|
13
|
+
limitUpBoardBlacklist?: string[];
|
|
14
|
+
limitDownBoardBlacklist?: string[];
|
|
15
|
+
stockSelectionBlacklist?: string[];
|
|
16
|
+
rideBlacklist?: string[];
|
|
17
|
+
allCommandsChannelBlacklist?: string[];
|
|
18
|
+
activeMarketCapChannelBlacklist?: string[];
|
|
19
|
+
stockAlertChannelBlacklist?: string[];
|
|
20
|
+
limitUpBoardChannelBlacklist?: string[];
|
|
21
|
+
limitDownBoardChannelBlacklist?: string[];
|
|
22
|
+
stockSelectionChannelBlacklist?: string[];
|
|
23
|
+
rideChannelBlacklist?: string[];
|
|
24
|
+
broadcastTasks?: BroadcastTask[];
|
|
25
|
+
enableHeartMethod?: boolean;
|
|
26
|
+
heartMethodBlacklist?: string[];
|
|
27
|
+
heartMethodChannelBlacklist?: string[];
|
|
28
|
+
verifyCardBlacklist?: string[];
|
|
29
|
+
verifyCardChannelBlacklist?: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare const Config: Schema<Config>;
|
|
32
|
+
export declare function apply(ctx: Context, config: Config): void;
|