koishi-plugin-imx 1.1.3 → 2.0.0
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/lib/constants/index.d.ts +17 -0
- package/lib/constants/index.js +48 -0
- package/lib/index.d.ts +39 -3
- package/lib/index.js +65 -10
- package/lib/modules/bilibili.d.ts +1 -2
- package/lib/modules/bilibili.js +65 -87
- package/lib/modules/github.d.ts +5 -2
- package/lib/modules/github.js +27 -140
- package/lib/modules/mx-space.d.ts +10 -2
- package/lib/modules/mx-space.js +88 -81
- package/lib/shared/commands/tool.d.ts +6 -2
- package/lib/shared/commands/tool.js +229 -43
- package/lib/shared/index.d.ts +17 -2
- package/lib/shared/index.js +46 -4
- package/lib/shared/repeater.d.ts +9 -2
- package/lib/shared/repeater.js +36 -9
- package/lib/types/bilibili/live.d.ts +36 -0
- package/lib/types/bilibili/live.js +2 -0
- package/lib/types/bilibili/room.d.ts +33 -0
- package/lib/types/bilibili/room.js +2 -0
- package/lib/types/bilibili/user.d.ts +17 -0
- package/lib/types/bilibili/user.js +2 -0
- package/lib/types/github/check-run.d.ts +95 -0
- package/lib/types/github/check-run.js +2 -0
- package/lib/types/github/issue.d.ts +67 -0
- package/lib/types/github/issue.js +2 -0
- package/lib/types/github/pull-request.d.ts +180 -0
- package/lib/types/github/pull-request.js +2 -0
- package/lib/types/github/push.d.ts +71 -0
- package/lib/types/github/push.js +2 -0
- package/lib/types/github/workflow.d.ts +111 -0
- package/lib/types/github/workflow.js +2 -0
- package/lib/types/mx-space/activity.d.ts +12 -0
- package/lib/types/mx-space/activity.js +2 -0
- package/lib/types/mx-space/tg-query.d.ts +3 -0
- package/lib/types/mx-space/tg-query.js +7 -0
- package/lib/utils/helper.d.ts +20 -0
- package/lib/utils/helper.js +47 -0
- package/lib/utils/hitokoto.d.ts +15 -0
- package/lib/utils/hitokoto.js +33 -0
- package/lib/utils/time.d.ts +5 -0
- package/lib/utils/time.js +26 -0
- package/lib/utils/webhook-handler.d.ts +18 -0
- package/lib/utils/webhook-handler.js +54 -0
- package/package.json +15 -7
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 常见的机器人账号列表
|
|
3
|
+
* 用于过滤自动化提交、PR等
|
|
4
|
+
*/
|
|
5
|
+
export declare const botList: string[];
|
|
6
|
+
/**
|
|
7
|
+
* 检查是否为机器人账号
|
|
8
|
+
*/
|
|
9
|
+
export declare function isBot(username: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 用户代理字符串
|
|
12
|
+
*/
|
|
13
|
+
export declare const userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
|
|
14
|
+
/**
|
|
15
|
+
* 开发环境检测
|
|
16
|
+
*/
|
|
17
|
+
export declare const isDev: boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isDev = exports.userAgent = exports.botList = void 0;
|
|
4
|
+
exports.isBot = isBot;
|
|
5
|
+
/**
|
|
6
|
+
* 常见的机器人账号列表
|
|
7
|
+
* 用于过滤自动化提交、PR等
|
|
8
|
+
*/
|
|
9
|
+
exports.botList = [
|
|
10
|
+
'dependabot[bot]',
|
|
11
|
+
'github-actions[bot]',
|
|
12
|
+
'renovate[bot]',
|
|
13
|
+
'allcontributors[bot]',
|
|
14
|
+
'codecov[bot]',
|
|
15
|
+
'dependabot-preview[bot]',
|
|
16
|
+
'snyk-bot',
|
|
17
|
+
'greenkeeper[bot]',
|
|
18
|
+
'sonarcloud[bot]',
|
|
19
|
+
'deepsource-autofix[bot]',
|
|
20
|
+
'gitpod-io[bot]',
|
|
21
|
+
'mergify[bot]',
|
|
22
|
+
'semantic-release-bot',
|
|
23
|
+
'stale[bot]',
|
|
24
|
+
'wakatime[bot]',
|
|
25
|
+
'vercel[bot]',
|
|
26
|
+
'netlify[bot]',
|
|
27
|
+
'actions-user',
|
|
28
|
+
'github-pages[bot]',
|
|
29
|
+
'whitesource-bolt[bot]',
|
|
30
|
+
'circleci[bot]',
|
|
31
|
+
'travis[bot]',
|
|
32
|
+
'appveyor[bot]',
|
|
33
|
+
'azure-pipelines[bot]',
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* 检查是否为机器人账号
|
|
37
|
+
*/
|
|
38
|
+
function isBot(username) {
|
|
39
|
+
return username.endsWith('[bot]') || exports.botList.includes(username);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 用户代理字符串
|
|
43
|
+
*/
|
|
44
|
+
exports.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36';
|
|
45
|
+
/**
|
|
46
|
+
* 开发环境检测
|
|
47
|
+
*/
|
|
48
|
+
exports.isDev = process.env.NODE_ENV === 'development';
|
package/lib/index.d.ts
CHANGED
|
@@ -4,16 +4,52 @@ export interface Config {
|
|
|
4
4
|
mxSpace?: {
|
|
5
5
|
baseUrl?: string;
|
|
6
6
|
token?: string;
|
|
7
|
+
webhook?: {
|
|
8
|
+
secret?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
watchChannels?: string[];
|
|
11
|
+
};
|
|
12
|
+
greeting?: {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
channels?: string[];
|
|
15
|
+
morningTime?: string;
|
|
16
|
+
eveningTime?: string;
|
|
17
|
+
};
|
|
18
|
+
commands?: {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
replyPrefix?: string;
|
|
21
|
+
};
|
|
7
22
|
};
|
|
8
23
|
bilibili?: {
|
|
9
24
|
enabled?: boolean;
|
|
25
|
+
liveRoom?: {
|
|
26
|
+
roomId?: string;
|
|
27
|
+
watchChannels?: string[];
|
|
28
|
+
checkInterval?: number;
|
|
29
|
+
};
|
|
30
|
+
userAgent?: string;
|
|
10
31
|
};
|
|
11
32
|
github?: {
|
|
12
33
|
enabled?: boolean;
|
|
13
|
-
|
|
34
|
+
webhook?: {
|
|
35
|
+
secret?: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
watchChannels?: string[];
|
|
38
|
+
};
|
|
14
39
|
};
|
|
15
|
-
|
|
16
|
-
|
|
40
|
+
shared?: {
|
|
41
|
+
errorNotify?: {
|
|
42
|
+
enabled?: boolean;
|
|
43
|
+
channels?: string[];
|
|
44
|
+
};
|
|
45
|
+
repeater?: {
|
|
46
|
+
enabled?: boolean;
|
|
47
|
+
threshold?: number;
|
|
48
|
+
chance?: number;
|
|
49
|
+
};
|
|
50
|
+
tools?: {
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
};
|
|
17
53
|
};
|
|
18
54
|
}
|
|
19
55
|
export declare const Config: Schema<Config>;
|
package/lib/index.js
CHANGED
|
@@ -43,31 +43,86 @@ const shared = __importStar(require("./shared"));
|
|
|
43
43
|
exports.name = 'imx';
|
|
44
44
|
exports.Config = koishi_1.Schema.object({
|
|
45
45
|
mxSpace: koishi_1.Schema.object({
|
|
46
|
-
baseUrl: koishi_1.Schema.string().description('MX Space API 地址'),
|
|
46
|
+
baseUrl: koishi_1.Schema.string().description('MX Space API 地址').required(),
|
|
47
47
|
token: koishi_1.Schema.string().description('MX Space API Token').role('secret'),
|
|
48
|
+
webhook: koishi_1.Schema.object({
|
|
49
|
+
secret: koishi_1.Schema.string().description('MX Space Webhook Secret').role('secret'),
|
|
50
|
+
path: koishi_1.Schema.string().description('Webhook 路径').default('/mx-space/webhook'),
|
|
51
|
+
watchChannels: koishi_1.Schema.array(koishi_1.Schema.string()).description('监听的频道ID列表').default([]),
|
|
52
|
+
}).description('Webhook 配置'),
|
|
53
|
+
greeting: koishi_1.Schema.object({
|
|
54
|
+
enabled: koishi_1.Schema.boolean().description('启用问候功能').default(true),
|
|
55
|
+
channels: koishi_1.Schema.array(koishi_1.Schema.string()).description('问候消息发送的频道').default([]),
|
|
56
|
+
morningTime: koishi_1.Schema.string().description('早安时间 (cron格式)').default('0 0 6 * * *'),
|
|
57
|
+
eveningTime: koishi_1.Schema.string().description('晚安时间 (cron格式)').default('0 0 22 * * *'),
|
|
58
|
+
}).description('问候功能配置'),
|
|
59
|
+
commands: koishi_1.Schema.object({
|
|
60
|
+
enabled: koishi_1.Schema.boolean().description('启用命令功能').default(true),
|
|
61
|
+
replyPrefix: koishi_1.Schema.string().description('回复前缀').default('来自 Mix Space 的'),
|
|
62
|
+
}).description('命令功能配置'),
|
|
48
63
|
}).description('MX Space 配置'),
|
|
49
64
|
bilibili: koishi_1.Schema.object({
|
|
50
|
-
enabled: koishi_1.Schema.boolean().
|
|
65
|
+
enabled: koishi_1.Schema.boolean().description('启用 Bilibili 功能').default(false),
|
|
66
|
+
liveRoom: koishi_1.Schema.object({
|
|
67
|
+
roomId: koishi_1.Schema.string().description('B站直播间房间号'),
|
|
68
|
+
watchChannels: koishi_1.Schema.array(koishi_1.Schema.string()).description('监听的频道ID列表').default([]),
|
|
69
|
+
checkInterval: koishi_1.Schema.number().description('检查间隔(分钟)').default(1).min(1).max(10),
|
|
70
|
+
}).description('直播间监控配置'),
|
|
71
|
+
userAgent: koishi_1.Schema.string().description('User-Agent').default('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'),
|
|
51
72
|
}).description('Bilibili 配置'),
|
|
52
73
|
github: koishi_1.Schema.object({
|
|
53
|
-
enabled: koishi_1.Schema.boolean().
|
|
54
|
-
|
|
74
|
+
enabled: koishi_1.Schema.boolean().description('启用 GitHub 功能').default(false),
|
|
75
|
+
webhook: koishi_1.Schema.object({
|
|
76
|
+
secret: koishi_1.Schema.string().description('GitHub Webhook Secret').role('secret'),
|
|
77
|
+
path: koishi_1.Schema.string().description('Webhook 路径').default('/github/webhook'),
|
|
78
|
+
watchChannels: koishi_1.Schema.array(koishi_1.Schema.string()).description('监听的频道ID列表').default([]),
|
|
79
|
+
}).description('GitHub Webhook 配置'),
|
|
55
80
|
}).description('GitHub 配置'),
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
81
|
+
shared: koishi_1.Schema.object({
|
|
82
|
+
errorNotify: koishi_1.Schema.object({
|
|
83
|
+
enabled: koishi_1.Schema.boolean().description('启用错误通知').default(true),
|
|
84
|
+
channels: koishi_1.Schema.array(koishi_1.Schema.string()).description('错误通知发送的频道').default([]),
|
|
85
|
+
}).description('错误通知配置'),
|
|
86
|
+
repeater: koishi_1.Schema.object({
|
|
87
|
+
enabled: koishi_1.Schema.boolean().description('启用复读机').default(false),
|
|
88
|
+
threshold: koishi_1.Schema.number().description('触发复读的次数').default(3).min(2).max(10),
|
|
89
|
+
chance: koishi_1.Schema.number().description('复读概率 (0-1)').default(0.5).min(0).max(1),
|
|
90
|
+
}).description('复读机配置'),
|
|
91
|
+
tools: koishi_1.Schema.object({
|
|
92
|
+
enabled: koishi_1.Schema.boolean().description('启用工具命令').default(true),
|
|
93
|
+
}).description('工具命令配置'),
|
|
94
|
+
}).description('共享功能配置'),
|
|
59
95
|
});
|
|
60
96
|
function apply(ctx, config) {
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
const logger = ctx.logger('imx');
|
|
98
|
+
// 注册 MX Space 模块
|
|
99
|
+
if (config.mxSpace?.baseUrl) {
|
|
63
100
|
ctx.plugin(mxSpace, config.mxSpace);
|
|
101
|
+
logger.info('MX Space 模块已加载');
|
|
64
102
|
}
|
|
103
|
+
else {
|
|
104
|
+
logger.warn('MX Space 模块未配置,跳过加载');
|
|
105
|
+
}
|
|
106
|
+
// 注册 Bilibili 模块
|
|
65
107
|
if (config.bilibili?.enabled) {
|
|
66
108
|
ctx.plugin(bilibili, config.bilibili);
|
|
109
|
+
logger.info('Bilibili 模块已加载');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
logger.debug('Bilibili 模块未启用');
|
|
67
113
|
}
|
|
114
|
+
// 注册 GitHub 模块
|
|
68
115
|
if (config.github?.enabled) {
|
|
69
116
|
ctx.plugin(github, config.github);
|
|
117
|
+
logger.info('GitHub 模块已加载');
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
logger.debug('GitHub 模块未启用');
|
|
70
121
|
}
|
|
71
122
|
// 注册共享功能
|
|
72
|
-
|
|
123
|
+
if (config.shared) {
|
|
124
|
+
ctx.plugin(shared, config.shared);
|
|
125
|
+
logger.info('共享功能模块已加载');
|
|
126
|
+
}
|
|
127
|
+
logger.info('IMX 插件加载完成');
|
|
73
128
|
}
|
|
@@ -2,10 +2,9 @@ import { Context, Schema } from 'koishi';
|
|
|
2
2
|
export declare const name = "bilibili";
|
|
3
3
|
export interface Config {
|
|
4
4
|
enabled?: boolean;
|
|
5
|
-
|
|
5
|
+
roomIds?: number[];
|
|
6
6
|
watchChannels?: string[];
|
|
7
7
|
checkInterval?: number;
|
|
8
|
-
atAll?: boolean;
|
|
9
8
|
}
|
|
10
9
|
export declare const Config: Schema<Config>;
|
|
11
10
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/modules/bilibili.js
CHANGED
|
@@ -9,110 +9,88 @@ const koishi_1 = require("koishi");
|
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
10
|
exports.name = 'bilibili';
|
|
11
11
|
exports.Config = koishi_1.Schema.object({
|
|
12
|
-
enabled: koishi_1.Schema.boolean().
|
|
13
|
-
|
|
12
|
+
enabled: koishi_1.Schema.boolean().description('启用 Bilibili 直播监控').default(false),
|
|
13
|
+
roomIds: koishi_1.Schema.array(koishi_1.Schema.number()).description('监控的直播间ID列表').default([]),
|
|
14
14
|
watchChannels: koishi_1.Schema.array(koishi_1.Schema.string()).description('推送通知的频道ID列表').default([]),
|
|
15
|
-
checkInterval: koishi_1.Schema.number().
|
|
16
|
-
atAll: koishi_1.Schema.boolean().default(false).description('开播时是否@全体成员'),
|
|
15
|
+
checkInterval: koishi_1.Schema.number().description('检查间隔(分钟)').default(5).min(1).max(60),
|
|
17
16
|
});
|
|
17
|
+
const liveStatusCache = new Map();
|
|
18
18
|
function apply(ctx, config) {
|
|
19
19
|
const logger = ctx.logger('bilibili');
|
|
20
|
-
if (!config.enabled || !config.
|
|
20
|
+
if (!config.enabled || !config.roomIds?.length) {
|
|
21
|
+
logger.info('Bilibili 模块未启用或未配置房间ID');
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
// 定时检查直播状态
|
|
25
|
+
const interval = setInterval(async () => {
|
|
26
|
+
await checkLiveStatus(ctx, config, logger);
|
|
27
|
+
}, config.checkInterval * 60 * 1000);
|
|
28
|
+
// 插件停止时清理定时器
|
|
29
|
+
ctx.on('dispose', () => {
|
|
30
|
+
clearInterval(interval);
|
|
31
|
+
logger.info('Bilibili 监控已停止');
|
|
32
|
+
});
|
|
33
|
+
// 注册命令
|
|
34
|
+
ctx.command('bili.status', '查看直播状态')
|
|
35
|
+
.action(async ({ session }) => {
|
|
36
|
+
if (!config.roomIds?.length) {
|
|
37
|
+
return '未配置监控房间';
|
|
38
|
+
}
|
|
39
|
+
const statusList = [];
|
|
40
|
+
for (const roomId of config.roomIds) {
|
|
41
|
+
try {
|
|
42
|
+
const isLive = await getRoomLiveStatus(roomId);
|
|
43
|
+
statusList.push(`房间 ${roomId}: ${isLive ? '🔴 直播中' : '⚫ 未直播'}`);
|
|
41
44
|
}
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
catch (error) {
|
|
46
|
+
statusList.push(`房间 ${roomId}: ❌ 获取失败`);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
return statusList.join('\n');
|
|
50
|
+
});
|
|
51
|
+
logger.info(`Bilibili 直播监控已启动,监控 ${config.roomIds.length} 个房间`);
|
|
52
|
+
}
|
|
53
|
+
async function checkLiveStatus(ctx, config, logger) {
|
|
54
|
+
for (const roomId of config.roomIds) {
|
|
51
55
|
try {
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const isLive = await getRoomLiveStatus(roomId);
|
|
57
|
+
const wasLive = liveStatusCache.get(roomId) || false;
|
|
58
|
+
if (isLive && !wasLive) {
|
|
59
|
+
// 开播通知
|
|
60
|
+
const roomInfo = await getRoomInfo(roomId);
|
|
61
|
+
const message = formatLiveMessage(roomInfo);
|
|
62
|
+
await sendToChannels(ctx, config.watchChannels, message, logger);
|
|
57
63
|
}
|
|
58
|
-
|
|
59
|
-
const roomInfoResponse = await axios_1.default.get(`https://api.live.bilibili.com/xlive/web-room/v1/index/getRoomBaseInfo?room_ids=${config.liveRoomId}&req_biz=link-center`, { headers, timeout: 10000 });
|
|
60
|
-
const roomInfo = roomInfoResponse.data?.data?.by_room_ids?.[config.liveRoomId];
|
|
61
|
-
let coverImage = null;
|
|
62
|
-
if (roomInfo?.cover) {
|
|
63
|
-
try {
|
|
64
|
-
const imageResponse = await axios_1.default.get(roomInfo.cover, {
|
|
65
|
-
headers,
|
|
66
|
-
responseType: 'arraybuffer',
|
|
67
|
-
timeout: 10000
|
|
68
|
-
});
|
|
69
|
-
coverImage = Buffer.from(imageResponse.data);
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
logger.warn('获取直播间封面失败:', error);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const message = [
|
|
76
|
-
coverImage ? `<image data="base64://${coverImage.toString('base64')}"/>` : '',
|
|
77
|
-
config.atAll ? '<at type="all"/>' : '',
|
|
78
|
-
`${userInfo.uname}(${userInfo.uid}) 开播了!\n\n`,
|
|
79
|
-
roomInfo?.title ? `标题: ${roomInfo.title}\n` : '',
|
|
80
|
-
`直播间: https://live.bilibili.com/${config.liveRoomId}`
|
|
81
|
-
].filter(Boolean).join('');
|
|
82
|
-
// 向所有监控频道发送通知
|
|
83
|
-
for (const channelId of config.watchChannels || []) {
|
|
84
|
-
try {
|
|
85
|
-
const bot = ctx.bots[0];
|
|
86
|
-
if (bot) {
|
|
87
|
-
await bot.sendMessage(channelId, message);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
logger.error(`向频道 ${channelId} 发送开播通知失败:`, error);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
logger.info(`发送开播通知: ${userInfo.uname}`);
|
|
64
|
+
liveStatusCache.set(roomId, isLive);
|
|
95
65
|
}
|
|
96
66
|
catch (error) {
|
|
97
|
-
logger.error(
|
|
67
|
+
logger.error(`检查房间 ${roomId} 状态失败:`, error);
|
|
98
68
|
}
|
|
99
69
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
70
|
+
}
|
|
71
|
+
async function getRoomLiveStatus(roomId) {
|
|
72
|
+
const response = await axios_1.default.get(`https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${roomId}`);
|
|
73
|
+
return response.data.data.live_status === 1;
|
|
74
|
+
}
|
|
75
|
+
async function getRoomInfo(roomId) {
|
|
76
|
+
const response = await axios_1.default.get(`https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${roomId}`);
|
|
77
|
+
return response.data.data;
|
|
78
|
+
}
|
|
79
|
+
function formatLiveMessage(roomInfo) {
|
|
80
|
+
return [
|
|
81
|
+
`🔴 ${roomInfo.uname} 开播了!`,
|
|
82
|
+
`📺 ${roomInfo.title}`,
|
|
83
|
+
`👥 观看人数: ${roomInfo.online}`,
|
|
84
|
+
`🔗 https://live.bilibili.com/${roomInfo.room_id}`,
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
87
|
+
async function sendToChannels(ctx, channels, message, logger) {
|
|
88
|
+
for (const channelId of channels) {
|
|
106
89
|
try {
|
|
107
|
-
|
|
108
|
-
const playInfo = response.data?.data;
|
|
109
|
-
const isCurrentlyLive = playInfo?.live_status === 1 && !!playInfo?.playurl_info;
|
|
110
|
-
return session?.send(`直播间 ${config.liveRoomId} 当前状态: ${isCurrentlyLive ? '直播中' : '未直播'}`);
|
|
90
|
+
await ctx.broadcast([channelId], message);
|
|
111
91
|
}
|
|
112
92
|
catch (error) {
|
|
113
|
-
logger.error(
|
|
114
|
-
return session?.send('查询直播状态失败');
|
|
93
|
+
logger.error(`发送消息到频道 ${channelId} 失败:`, error);
|
|
115
94
|
}
|
|
116
|
-
}
|
|
117
|
-
logger.info(`Bilibili 直播监控已启动,监控房间: ${config.liveRoomId}`);
|
|
95
|
+
}
|
|
118
96
|
}
|
package/lib/modules/github.d.ts
CHANGED
|
@@ -2,9 +2,12 @@ import { Context, Schema } from 'koishi';
|
|
|
2
2
|
export declare const name = "github";
|
|
3
3
|
export interface Config {
|
|
4
4
|
enabled?: boolean;
|
|
5
|
-
|
|
6
|
-
webhookPort?: number;
|
|
5
|
+
repositories?: string[];
|
|
7
6
|
watchChannels?: string[];
|
|
7
|
+
webhook?: {
|
|
8
|
+
secret?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
};
|
|
8
11
|
}
|
|
9
12
|
export declare const Config: Schema<Config>;
|
|
10
13
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/modules/github.js
CHANGED
|
@@ -6,158 +6,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.Config = exports.name = void 0;
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
const koishi_1 = require("koishi");
|
|
9
|
-
const
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
10
|
exports.name = 'github';
|
|
11
11
|
exports.Config = koishi_1.Schema.object({
|
|
12
|
-
enabled: koishi_1.Schema.boolean().
|
|
13
|
-
|
|
14
|
-
webhookPort: koishi_1.Schema.number().default(3000).description('Webhook 监听端口'),
|
|
12
|
+
enabled: koishi_1.Schema.boolean().description('启用 GitHub 功能').default(false),
|
|
13
|
+
repositories: koishi_1.Schema.array(koishi_1.Schema.string()).description('监控的仓库列表(格式:owner/repo)').default([]),
|
|
15
14
|
watchChannels: koishi_1.Schema.array(koishi_1.Schema.string()).description('推送通知的频道ID列表').default([]),
|
|
15
|
+
webhook: koishi_1.Schema.object({
|
|
16
|
+
secret: koishi_1.Schema.string().description('Webhook Secret').role('secret'),
|
|
17
|
+
path: koishi_1.Schema.string().description('Webhook 路径').default('/github/webhook'),
|
|
18
|
+
}).description('Webhook 配置'),
|
|
16
19
|
});
|
|
17
20
|
function apply(ctx, config) {
|
|
18
21
|
const logger = ctx.logger('github');
|
|
19
|
-
if (!config.enabled
|
|
22
|
+
if (!config.enabled) {
|
|
23
|
+
logger.info('GitHub 模块未启用');
|
|
20
24
|
return;
|
|
21
25
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
// 注册命令
|
|
27
|
+
ctx.command('github', 'GitHub 相关功能');
|
|
28
|
+
ctx.command('github.status', '查看仓库状态')
|
|
29
|
+
.action(async ({ session }) => {
|
|
30
|
+
if (!config.repositories?.length) {
|
|
31
|
+
return '未配置监控仓库';
|
|
32
|
+
}
|
|
33
|
+
const statusList = [];
|
|
34
|
+
for (const repo of config.repositories) {
|
|
25
35
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
await bot.sendMessage(channelId, message);
|
|
29
|
-
}
|
|
36
|
+
const repoInfo = await getRepoInfo(repo);
|
|
37
|
+
statusList.push(`${repo}: ⭐ ${repoInfo.stargazers_count} | 🍴 ${repoInfo.forks_count}`);
|
|
30
38
|
}
|
|
31
39
|
catch (error) {
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function handlePush(payload) {
|
|
37
|
-
const { repository, ref, commits, pusher } = payload;
|
|
38
|
-
// 忽略机器人推送
|
|
39
|
-
if (pusher.name.endsWith('[bot]')) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const branch = ref.replace('refs/heads/', '');
|
|
43
|
-
const isPushToMain = branch === 'main' || branch === 'master';
|
|
44
|
-
if (!commits.length) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const commitMessages = commits.slice(0, 5).map(commit => `• ${commit.message.split('\n')[0]} (${commit.id.substring(0, 7)})`).join('\n');
|
|
48
|
-
const moreCommits = commits.length > 5 ? `\n...以及其他 ${commits.length - 5} 个提交` : '';
|
|
49
|
-
const message = `📦 ${repository?.full_name} ${isPushToMain ? '主分支' : branch + ' 分支'}收到推送
|
|
50
|
-
|
|
51
|
-
👤 推送者: ${pusher.name}
|
|
52
|
-
📝 ${commits.length} 个新提交:
|
|
53
|
-
${commitMessages}${moreCommits}
|
|
54
|
-
|
|
55
|
-
🔗 查看: ${repository?.html_url}/commits/${branch}`;
|
|
56
|
-
sendNotification(message);
|
|
57
|
-
}
|
|
58
|
-
function handleIssue(payload) {
|
|
59
|
-
const { action, issue, repository, sender } = payload;
|
|
60
|
-
if (!action || !issue || !repository || !sender)
|
|
61
|
-
return;
|
|
62
|
-
const actionText = {
|
|
63
|
-
opened: '创建了',
|
|
64
|
-
closed: '关闭了',
|
|
65
|
-
reopened: '重新打开了'
|
|
66
|
-
}[action] || action;
|
|
67
|
-
const message = `🐛 ${repository.full_name} 议题更新
|
|
68
|
-
|
|
69
|
-
👤 ${sender.login} ${actionText}议题 #${issue.number}
|
|
70
|
-
📝 ${issue.title}
|
|
71
|
-
|
|
72
|
-
🔗 查看: ${issue.html_url}`;
|
|
73
|
-
sendNotification(message);
|
|
74
|
-
}
|
|
75
|
-
function handlePullRequest(payload) {
|
|
76
|
-
const { action, pull_request, repository, sender } = payload;
|
|
77
|
-
if (!action || !pull_request || !repository || !sender)
|
|
78
|
-
return;
|
|
79
|
-
const actionText = {
|
|
80
|
-
opened: '创建了',
|
|
81
|
-
closed: pull_request.merged ? '合并了' : '关闭了',
|
|
82
|
-
reopened: '重新打开了'
|
|
83
|
-
}[action] || action;
|
|
84
|
-
const message = `🔀 ${repository.full_name} 拉取请求更新
|
|
85
|
-
|
|
86
|
-
👤 ${sender.login} ${actionText}拉取请求 #${pull_request.number}
|
|
87
|
-
📝 ${pull_request.title}
|
|
88
|
-
|
|
89
|
-
🔗 查看: ${pull_request.html_url}`;
|
|
90
|
-
sendNotification(message);
|
|
91
|
-
}
|
|
92
|
-
function startWebhookServer() {
|
|
93
|
-
server = http_1.default.createServer((req, res) => {
|
|
94
|
-
if (req.method === 'POST' && req.url === '/webhook') {
|
|
95
|
-
let body = '';
|
|
96
|
-
req.on('data', chunk => {
|
|
97
|
-
body += chunk.toString();
|
|
98
|
-
});
|
|
99
|
-
req.on('end', () => {
|
|
100
|
-
try {
|
|
101
|
-
const payload = JSON.parse(body);
|
|
102
|
-
const eventType = req.headers['x-github-event'];
|
|
103
|
-
// 简单的验证(生产环境应该使用更安全的验证方式)
|
|
104
|
-
const signature = req.headers['x-hub-signature-256'];
|
|
105
|
-
if (!signature) {
|
|
106
|
-
res.statusCode = 401;
|
|
107
|
-
res.end('Unauthorized');
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
switch (eventType) {
|
|
111
|
-
case 'push':
|
|
112
|
-
handlePush(payload);
|
|
113
|
-
break;
|
|
114
|
-
case 'issues':
|
|
115
|
-
handleIssue(payload);
|
|
116
|
-
break;
|
|
117
|
-
case 'pull_request':
|
|
118
|
-
handlePullRequest(payload);
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
res.statusCode = 200;
|
|
122
|
-
res.end('OK');
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
logger.error('处理 GitHub Webhook 失败:', error);
|
|
126
|
-
res.statusCode = 400;
|
|
127
|
-
res.end('Bad Request');
|
|
128
|
-
}
|
|
129
|
-
});
|
|
40
|
+
statusList.push(`${repo}: ❌ 获取失败`);
|
|
130
41
|
}
|
|
131
|
-
else {
|
|
132
|
-
res.statusCode = 404;
|
|
133
|
-
res.end('Not Found');
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
server.listen(config.webhookPort, () => {
|
|
137
|
-
logger.info(`GitHub Webhook 服务器启动在端口 ${config.webhookPort}`);
|
|
138
|
-
});
|
|
139
|
-
server.on('error', (error) => {
|
|
140
|
-
logger.error('GitHub Webhook 服务器错误:', error);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
ctx.on('ready', () => {
|
|
144
|
-
startWebhookServer();
|
|
145
|
-
});
|
|
146
|
-
ctx.on('dispose', () => {
|
|
147
|
-
if (server) {
|
|
148
|
-
server.close();
|
|
149
|
-
logger.info('GitHub Webhook 服务器已关闭');
|
|
150
42
|
}
|
|
43
|
+
return statusList.join('\n');
|
|
151
44
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
⚙️ Webhook 地址: http://your-server:${config.webhookPort}/webhook`;
|
|
159
|
-
await sendNotification(testMessage);
|
|
160
|
-
return session?.send('测试通知已发送');
|
|
161
|
-
});
|
|
162
|
-
logger.info('GitHub Webhook 模块已加载');
|
|
45
|
+
logger.info('GitHub 模块已启动');
|
|
46
|
+
}
|
|
47
|
+
async function getRepoInfo(repo) {
|
|
48
|
+
const response = await axios_1.default.get(`https://api.github.com/repos/${repo}`);
|
|
49
|
+
return response.data;
|
|
163
50
|
}
|
|
@@ -3,8 +3,16 @@ export declare const name = "mx-space";
|
|
|
3
3
|
export interface Config {
|
|
4
4
|
baseUrl?: string;
|
|
5
5
|
token?: string;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
greeting?: {
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
channels?: string[];
|
|
9
|
+
morningTime?: string;
|
|
10
|
+
eveningTime?: string;
|
|
11
|
+
};
|
|
12
|
+
commands?: {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
replyPrefix?: string;
|
|
15
|
+
};
|
|
8
16
|
}
|
|
9
17
|
export declare const Config: Schema<Config>;
|
|
10
18
|
export declare function apply(ctx: Context, config: Config): void;
|