koishi-plugin-youtube-notifier 1.0.7
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.
Potentially problematic release.
This version of koishi-plugin-youtube-notifier might be problematic. Click here for more details.
- package/README.md +70 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +138 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# koishi-plugin-youtube-notifier
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-youtube-notifier)
|
|
4
|
+
[](https://github.com/svip886/koishi-plugin-youtube-notifier/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
YouTube 频道动态与直播开播提醒插件,支持 Puppeteer 抓取、数据库持久化及自定义代理。
|
|
7
|
+
|
|
8
|
+
## 功能特性
|
|
9
|
+
|
|
10
|
+
- **多频道监控**:支持同时监控多个 YouTube 频道的社区动态和直播状态。
|
|
11
|
+
- **多平台推送**:支持将消息推送到自定义的群组。
|
|
12
|
+
- **Puppeteer 驱动**:使用 Puppeteer 进行页面解析,支持复杂的页面渲染。
|
|
13
|
+
- **自定义代理**:支持配置 HTTP 代理,解决国内环境网络访问问题。
|
|
14
|
+
- **Notifier 集成**:支持 Koishi 的 `notifier` 服务,提供更好的通知展示。
|
|
15
|
+
- **状态持久化**:使用数据库记录上次推送状态,防止重复通知。
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install koishi-plugin-youtube-notifier
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 配置项
|
|
24
|
+
|
|
25
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
26
|
+
| :--- | :--- | :--- | :--- |
|
|
27
|
+
| `channels` | `array` | `[]` | 订阅频道列表,包含 `id` (频道ID) 和 `targets` (推送群组ID列表) |
|
|
28
|
+
| `interval` | `number` | `300000` | 轮询间隔(毫秒),默认为 5 分钟 |
|
|
29
|
+
| `proxy` | `string` | - | 代理服务器地址,例如 `http://127.0.0.1:7890` |
|
|
30
|
+
|
|
31
|
+
## 依赖项
|
|
32
|
+
|
|
33
|
+
本插件需要以下服务支持:
|
|
34
|
+
- `puppeteer`: 用于网页抓取
|
|
35
|
+
- `database`: 用于状态持久化
|
|
36
|
+
- `notifier` (可选): 用于结构化通知
|
|
37
|
+
|
|
38
|
+
## 开源协议
|
|
39
|
+
|
|
40
|
+
MIT License.
|
|
41
|
+
|
|
42
|
+
## 更新日志
|
|
43
|
+
|
|
44
|
+
### 1.0.7
|
|
45
|
+
|
|
46
|
+
- **修复**:显式声明 `"type": "commonjs"` 以彻底解决 ESM 加载冲突。
|
|
47
|
+
|
|
48
|
+
### 1.0.6
|
|
49
|
+
|
|
50
|
+
- **回退**:将项目回退为 CommonJS 模式,以解决部分环境下的 `ERR_REQUIRE_ESM` 加载错误。
|
|
51
|
+
|
|
52
|
+
### 1.0.5
|
|
53
|
+
|
|
54
|
+
- **重要更新**:项目切换为 ESM (ECMAScript Module) 模式。
|
|
55
|
+
- **修复**:修正了 Puppeteer 浏览器启动逻辑,支持正确的 `executablePath` 获取。
|
|
56
|
+
- **修复**:修正了 HTTP 代理配置属性名为 `proxyAgent`。
|
|
57
|
+
- **优化**:优化了 npm 发布包体积,移除了不必要的源码、配置和缓存文件。
|
|
58
|
+
- **安全**:移除了仓库中的 `package-lock.json`。
|
|
59
|
+
|
|
60
|
+
### 1.0.0
|
|
61
|
+
|
|
62
|
+
- 初始版本发布。
|
|
63
|
+
- 支持 YouTube 频道社区动态监控。
|
|
64
|
+
- 支持 YouTube 直播状态监控。
|
|
65
|
+
- 支持 Puppeteer 抓取与自定义代理配置。
|
|
66
|
+
- 集成 Koishi 数据库与通知服务。
|
|
67
|
+
|
|
68
|
+
## 仓库地址
|
|
69
|
+
|
|
70
|
+
[GitHub Repository](https://github.com/svip886/koishi-plugin-youtube-notifier.git)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "youtube-notifier";
|
|
3
|
+
export declare const inject: {
|
|
4
|
+
required: string[];
|
|
5
|
+
optional: string[];
|
|
6
|
+
};
|
|
7
|
+
export interface Config {
|
|
8
|
+
channels: {
|
|
9
|
+
id: string;
|
|
10
|
+
targets: string[];
|
|
11
|
+
}[];
|
|
12
|
+
interval: number;
|
|
13
|
+
proxy?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const Config: Schema<Config>;
|
|
16
|
+
declare module 'koishi' {
|
|
17
|
+
interface Tables {
|
|
18
|
+
youtube_status: YoutubeStatus;
|
|
19
|
+
}
|
|
20
|
+
interface Context {
|
|
21
|
+
notifier: Notifier;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export interface Notifier {
|
|
25
|
+
create(options: {
|
|
26
|
+
title: string;
|
|
27
|
+
content: string;
|
|
28
|
+
target: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export interface YoutubeStatus {
|
|
32
|
+
id: string;
|
|
33
|
+
lastPostId: string;
|
|
34
|
+
lastLiveId: string;
|
|
35
|
+
isLive: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Config = exports.inject = exports.name = void 0;
|
|
7
|
+
exports.apply = apply;
|
|
8
|
+
const koishi_1 = require("koishi");
|
|
9
|
+
const puppeteer_core_1 = __importDefault(require("puppeteer-core"));
|
|
10
|
+
exports.name = 'youtube-notifier';
|
|
11
|
+
exports.inject = {
|
|
12
|
+
required: ['puppeteer', 'database'],
|
|
13
|
+
optional: ['notifier'],
|
|
14
|
+
};
|
|
15
|
+
exports.Config = koishi_1.Schema.object({
|
|
16
|
+
channels: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
17
|
+
id: koishi_1.Schema.string().required().description('YouTube 频道 ID (例如 UC-hM6YJuNYV19LAJg37K9Bw)'),
|
|
18
|
+
targets: koishi_1.Schema.array(koishi_1.Schema.string()).description('推送目标群组 ID'),
|
|
19
|
+
})).description('订阅频道列表'),
|
|
20
|
+
interval: koishi_1.Schema.number().default(300000).description('轮询间隔 (毫秒)'),
|
|
21
|
+
proxy: koishi_1.Schema.string().description('代理服务器地址 (例如 http://127.0.0.1:7890)'),
|
|
22
|
+
});
|
|
23
|
+
async function getChannelStatus(ctx, channelId, proxy) {
|
|
24
|
+
const browser = await puppeteer_core_1.default.launch({
|
|
25
|
+
executablePath: ctx.puppeteer.executable,
|
|
26
|
+
args: proxy ? [`--proxy-server=${proxy}`] : [],
|
|
27
|
+
});
|
|
28
|
+
const page = await browser.newPage();
|
|
29
|
+
try {
|
|
30
|
+
// 检查社区帖子
|
|
31
|
+
await page.goto(`https://www.youtube.com/channel/${channelId}/community`, { waitUntil: 'networkidle2' });
|
|
32
|
+
const lastPostId = await page.evaluate(() => {
|
|
33
|
+
const element = document.querySelector('ytd-backstage-post-thread-renderer');
|
|
34
|
+
return element?.getAttribute('id') || '';
|
|
35
|
+
});
|
|
36
|
+
// 检查直播状态
|
|
37
|
+
await page.goto(`https://www.youtube.com/channel/${channelId}/live`, { waitUntil: 'networkidle2' });
|
|
38
|
+
const isLive = await page.evaluate(() => {
|
|
39
|
+
return !!document.querySelector('meta[itemprop="isLiveBroadcast"][content="True"]');
|
|
40
|
+
});
|
|
41
|
+
let lastLiveId = '';
|
|
42
|
+
if (isLive) {
|
|
43
|
+
lastLiveId = await page.evaluate(() => {
|
|
44
|
+
const link = document.querySelector('link[rel="canonical"]');
|
|
45
|
+
return link?.getAttribute('href')?.split('v=')[1] || '';
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return { lastPostId, isLive, lastLiveId };
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await browser.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function apply(ctx, config) {
|
|
55
|
+
ctx.model.extend('youtube_status', {
|
|
56
|
+
id: 'string',
|
|
57
|
+
lastPostId: 'string',
|
|
58
|
+
lastLiveId: 'string',
|
|
59
|
+
isLive: 'boolean',
|
|
60
|
+
}, {
|
|
61
|
+
primary: 'id',
|
|
62
|
+
});
|
|
63
|
+
ctx.on('ready', async () => {
|
|
64
|
+
// 代理可用性检测
|
|
65
|
+
if (config.proxy) {
|
|
66
|
+
try {
|
|
67
|
+
await ctx.http.get('https://www.youtube.com', {
|
|
68
|
+
proxyAgent: config.proxy,
|
|
69
|
+
timeout: 10000,
|
|
70
|
+
});
|
|
71
|
+
ctx.logger('youtube-notifier').info(`代理检测成功: ${config.proxy}`);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
ctx.logger('youtube-notifier').warn(`代理检测失败: ${config.proxy}, 请检查代理配置或网络环境`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const timer = setInterval(async () => {
|
|
78
|
+
for (const channelConfig of config.channels) {
|
|
79
|
+
try {
|
|
80
|
+
const current = await getChannelStatus(ctx, channelConfig.id, config.proxy);
|
|
81
|
+
const [saved] = await ctx.database.get('youtube_status', { id: channelConfig.id });
|
|
82
|
+
if (!saved) {
|
|
83
|
+
// 初次运行,仅保存当前状态不推送
|
|
84
|
+
await ctx.database.create('youtube_status', {
|
|
85
|
+
id: channelConfig.id,
|
|
86
|
+
...current
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// 新动态提醒
|
|
91
|
+
if (current.lastPostId && current.lastPostId !== saved.lastPostId) {
|
|
92
|
+
const title = `YouTube 新动态`;
|
|
93
|
+
const content = `频道 ${channelConfig.id} 发布了新动态:https://www.youtube.com/post/${current.lastPostId}`;
|
|
94
|
+
if (ctx.notifier) {
|
|
95
|
+
for (const target of channelConfig.targets) {
|
|
96
|
+
await ctx.notifier.create({
|
|
97
|
+
title,
|
|
98
|
+
content,
|
|
99
|
+
target,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
for (const target of channelConfig.targets) {
|
|
105
|
+
ctx.broadcast([target], `[${title}] ${content}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 开播状态变更提醒
|
|
110
|
+
if (current.isLive && (!saved.isLive || current.lastLiveId !== saved.lastLiveId)) {
|
|
111
|
+
const title = `YouTube 开播提醒`;
|
|
112
|
+
const content = `频道 ${channelConfig.id} 正在直播!\n传送门:https://www.youtube.com/watch?v=${current.lastLiveId}`;
|
|
113
|
+
if (ctx.notifier) {
|
|
114
|
+
for (const target of channelConfig.targets) {
|
|
115
|
+
await ctx.notifier.create({
|
|
116
|
+
title,
|
|
117
|
+
content,
|
|
118
|
+
target,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
for (const target of channelConfig.targets) {
|
|
124
|
+
ctx.broadcast([target], `[${title}] ${content}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// 更新数据库记录
|
|
129
|
+
await ctx.database.set('youtube_status', channelConfig.id, current);
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
ctx.logger('youtube-notifier').error(`检查频道 ${channelConfig.id} 失败:`, e);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}, config.interval);
|
|
136
|
+
ctx.on('dispose', () => clearInterval(timer));
|
|
137
|
+
});
|
|
138
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-youtube-notifier",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "YouTube channel post and live notifier for Koishi",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"koishi",
|
|
13
|
+
"plugin",
|
|
14
|
+
"youtube",
|
|
15
|
+
"notifier",
|
|
16
|
+
"puppeteer"
|
|
17
|
+
],
|
|
18
|
+
"author": "Qoder",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/svip886/koishi-plugin-youtube-notifier.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/svip886/koishi-plugin-youtube-notifier/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/svip886/koishi-plugin-youtube-notifier#readme",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"koishi": {
|
|
33
|
+
"description": {
|
|
34
|
+
"zh": "YouTube 频道动态与直播开播提醒"
|
|
35
|
+
},
|
|
36
|
+
"service": {
|
|
37
|
+
"required": [
|
|
38
|
+
"puppeteer",
|
|
39
|
+
"database"
|
|
40
|
+
],
|
|
41
|
+
"optional": [
|
|
42
|
+
"notifier"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"koishi-plugin-puppeteer": "^3.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^20.0.0",
|
|
51
|
+
"koishi": "^4.17.0",
|
|
52
|
+
"typescript": "^5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"koishi": "^4.17.0"
|
|
56
|
+
}
|
|
57
|
+
}
|