koishi-plugin-stock 2.0.13 → 2.0.15
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 +14 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +84 -12
- 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,11 +58,23 @@ 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
|
|
|
68
|
+
### v2.0.15 (2026-02-28)
|
|
69
|
+
- 新增"我要验牌"命令:支持查看指定序号的心法语音或查看全部心法文本
|
|
70
|
+
- 新增验牌功能黑白名单配置:支持为验牌功能单独设置用户和频道黑名单
|
|
71
|
+
- 增强用户体验:提供清晰的参数使用说明和错误提示
|
|
72
|
+
|
|
73
|
+
### v2.0.14 (2026-02-28)
|
|
74
|
+
- 优化交易日判断:使用stock.svip886.com提供的专业交易日API替代timor.tech节假日接口
|
|
75
|
+
- 改进API调用:直接解析JSON响应,提高判断准确性
|
|
76
|
+
- 增强错误处理:API请求失败时默认跳过任务,确保安全性
|
|
77
|
+
|
|
64
78
|
### v2.0.13 (2026-02-25)
|
|
65
79
|
- 根治编码问题:将心法数据直接嵌入TypeScript代码,避免JSON文件跨平台编码兼容性问题
|
|
66
80
|
- 简化部署:不再依赖外部JSON文件,减少文件读取和解析开销
|
package/lib/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface Config {
|
|
|
25
25
|
enableHeartMethod?: boolean;
|
|
26
26
|
heartMethodBlacklist?: string[];
|
|
27
27
|
heartMethodChannelBlacklist?: string[];
|
|
28
|
+
verifyCardBlacklist?: string[];
|
|
29
|
+
verifyCardChannelBlacklist?: string[];
|
|
28
30
|
}
|
|
29
31
|
export declare const Config: Schema<Config>;
|
|
30
32
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -28,6 +28,7 @@ exports.Config = koishi_1.Schema.object({
|
|
|
28
28
|
stockSelectionBlacklist: koishi_1.Schema.array(String).description('选股指令黑名单用户ID'),
|
|
29
29
|
rideBlacklist: koishi_1.Schema.array(String).description('骑指令黑名单用户ID'),
|
|
30
30
|
heartMethodBlacklist: koishi_1.Schema.array(String).description('心法抽卡指令黑名单用户ID'),
|
|
31
|
+
verifyCardBlacklist: koishi_1.Schema.array(String).description('验牌指令黑名单用户ID'),
|
|
31
32
|
// --- 频道黑名单 ---
|
|
32
33
|
allCommandsChannelBlacklist: koishi_1.Schema.array(String).description('全部指令黑名单频道ID'),
|
|
33
34
|
activeMarketCapChannelBlacklist: koishi_1.Schema.array(String).description('活跃市值指令黑名单频道ID'),
|
|
@@ -37,6 +38,7 @@ exports.Config = koishi_1.Schema.object({
|
|
|
37
38
|
stockSelectionChannelBlacklist: koishi_1.Schema.array(String).description('选股指令黑名单频道ID'),
|
|
38
39
|
rideChannelBlacklist: koishi_1.Schema.array(String).description('骑指令黑名单频道ID'),
|
|
39
40
|
heartMethodChannelBlacklist: koishi_1.Schema.array(String).description('心法抽卡指令黑名单频道ID'),
|
|
41
|
+
verifyCardChannelBlacklist: koishi_1.Schema.array(String).description('验牌指令黑名单频道ID'),
|
|
40
42
|
// --- 定时广播 ---
|
|
41
43
|
broadcastTasks: koishi_1.Schema.array(BroadcastTask).description('定时广播任务列表'),
|
|
42
44
|
// --- 心法抽卡 ---
|
|
@@ -265,24 +267,28 @@ function apply(ctx, config) {
|
|
|
265
267
|
logger.info(`[任务触发] 命中 ${activeTasks.length} 个广播任务 (时间: ${currentTime})`);
|
|
266
268
|
try {
|
|
267
269
|
const shanghaiDate = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Shanghai' }));
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const month = (shanghaiDate.getMonth() + 1).toString().padStart(2, '0');
|
|
271
|
-
const day = shanghaiDate.getDate().toString().padStart(2, '0');
|
|
272
|
-
const dateStr = `${year}-${month}-${day}`;
|
|
273
|
-
let tradingDay = !isWeekend;
|
|
270
|
+
//判断是否为交易日
|
|
271
|
+
let tradingDay = false;
|
|
274
272
|
try {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
tradingDay =
|
|
273
|
+
const tradingResponse = await ctx.http.get('https://stock.svip886.com/api/is_trading_day');
|
|
274
|
+
//检查响应结构
|
|
275
|
+
if (tradingResponse && typeof tradingResponse === 'object' && tradingResponse.success === true) {
|
|
276
|
+
tradingDay = tradingResponse.is_trading_day === true;
|
|
277
|
+
logger.info(`[交易日判断] 日期: ${tradingResponse.date}, 是否交易日: ${tradingDay}, 最近交易日: ${tradingResponse.latest_trade_date}`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
logger.error('[交易日判断] API返回格式异常');
|
|
281
|
+
return;
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
catch (e) {
|
|
282
|
-
logger.
|
|
285
|
+
logger.error(`[交易日判断] API请求失败: ${e.message}`);
|
|
286
|
+
//请求失败时,默认不执行任务以保证安全
|
|
287
|
+
logger.warn('[交易日判断] 请求失败,默认跳过任务');
|
|
288
|
+
return;
|
|
283
289
|
}
|
|
284
290
|
if (!tradingDay) {
|
|
285
|
-
logger.info(`[定时任务跳过]
|
|
291
|
+
logger.info(`[定时任务跳过] 今日非交易日`);
|
|
286
292
|
return;
|
|
287
293
|
}
|
|
288
294
|
for (const task of activeTasks) {
|
|
@@ -343,6 +349,11 @@ function apply(ctx, config) {
|
|
|
343
349
|
return true;
|
|
344
350
|
}
|
|
345
351
|
break;
|
|
352
|
+
case '验牌':
|
|
353
|
+
if (config.verifyCardBlacklist?.includes(userId)) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
346
357
|
}
|
|
347
358
|
// 检查特定指令的频道黑名单
|
|
348
359
|
switch (commandName) {
|
|
@@ -381,6 +392,11 @@ function apply(ctx, config) {
|
|
|
381
392
|
return true;
|
|
382
393
|
}
|
|
383
394
|
break;
|
|
395
|
+
case '验牌':
|
|
396
|
+
if (config.verifyCardChannelBlacklist?.includes(channelId)) {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
384
400
|
}
|
|
385
401
|
// 检查全局用户黑名单
|
|
386
402
|
if (config.allCommandsBlacklist?.includes(userId)) {
|
|
@@ -591,6 +607,62 @@ function apply(ctx, config) {
|
|
|
591
607
|
return '心法抽卡失败,请稍后重试';
|
|
592
608
|
}
|
|
593
609
|
});
|
|
610
|
+
// 我要验牌命令
|
|
611
|
+
ctx.command('我要验牌 [param:text]', '验牌功能:查看指定序号的心法或查看全部心法')
|
|
612
|
+
.action(async ({ session }, param) => {
|
|
613
|
+
// 检查黑名单
|
|
614
|
+
if (isUserInSpecificBlacklist(session, '验牌')) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
// 检查是否有心法数据
|
|
618
|
+
if (heartMethods.length === 0) {
|
|
619
|
+
return '暂无心法数据';
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
// 处理"全部"参数
|
|
623
|
+
if (param && param.trim() === '全部') {
|
|
624
|
+
logger.info(`[验牌] 用户 ${session?.userId || '未知'} 请求查看全部心法`);
|
|
625
|
+
// 生成带序号的完整列表
|
|
626
|
+
let result = '🃏 全部心法列表:\n\n';
|
|
627
|
+
heartMethods.forEach((method, index) => {
|
|
628
|
+
result += `${index + 1}. ${method.text}\n`;
|
|
629
|
+
});
|
|
630
|
+
logger.info(`[验牌] 成功生成全部心法列表,共 ${heartMethods.length} 条`);
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
// 处理数字序号参数
|
|
634
|
+
if (param && /^\d+$/.test(param.trim())) {
|
|
635
|
+
const index = parseInt(param.trim()) - 1; // 转换为0基索引
|
|
636
|
+
if (index < 0 || index >= heartMethods.length) {
|
|
637
|
+
return `序号超出范围,请输入 1-${heartMethods.length} 之间的数字`;
|
|
638
|
+
}
|
|
639
|
+
const selectedMethod = heartMethods[index];
|
|
640
|
+
logger.info(`[验牌] 用户 ${session?.userId || '未知'} 查看序号 ${index + 1}: ${selectedMethod.code}`);
|
|
641
|
+
// 读取对应的音频文件
|
|
642
|
+
const fs = require('fs');
|
|
643
|
+
const path = require('path');
|
|
644
|
+
const audioPath = path.resolve(__dirname, `../audio/${selectedMethod.file}`);
|
|
645
|
+
if (!fs.existsSync(audioPath)) {
|
|
646
|
+
// 音频文件不存在,只返回文本
|
|
647
|
+
return `🃏 验牌结果 #${index + 1}\n\n${selectedMethod.text}\n\n(音频文件缺失: ${selectedMethod.file})`;
|
|
648
|
+
}
|
|
649
|
+
// 读取音频文件并转换为base64
|
|
650
|
+
const audioData = fs.readFileSync(audioPath);
|
|
651
|
+
const base64Audio = audioData.toString('base64');
|
|
652
|
+
// 返回音频消息
|
|
653
|
+
const response = `<audio src="data:audio/mp3;base64,${base64Audio}" />#${index + 1} ${selectedMethod.text}`;
|
|
654
|
+
logger.info(`[验牌] 成功生成序号 ${index + 1} 的响应`);
|
|
655
|
+
return response;
|
|
656
|
+
}
|
|
657
|
+
// 参数格式错误
|
|
658
|
+
return '参数格式错误。请使用:\n"我要验牌 全部" - 查看所有心法\n"我要验牌 <序号>" - 查看指定序号的心法(1-58)';
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
logger.error('[验牌] 执行失败:', error);
|
|
662
|
+
logger.error('[验牌] 错误详情:', error.stack);
|
|
663
|
+
return '验牌失败,请稍后重试';
|
|
664
|
+
}
|
|
665
|
+
});
|
|
594
666
|
// 使用中间件方式监听特定关键词(作为备用方案)
|
|
595
667
|
ctx.middleware(async (session, next) => {
|
|
596
668
|
const content = session.content?.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-stock",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.15",
|
|
4
4
|
"description": "A Koishi plugin that fetches stock data and provides market analysis, including active market cap, stock alerts, limit-up board, stock selection features, and heart method card drawing with configurable blacklists for each command.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|