koishi-plugin-share-links-analysis 0.15.2 → 0.15.4
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 +1 -1
- package/lib/index.js +113 -38
- package/lib/parsers/bilibili.js +1 -1
- package/lib/parsers/github.d.ts +2 -2
- package/lib/parsers/github.js +567 -99
- package/lib/parsers/twitter.js +71 -70
- package/lib/parsers/xiaoheihe.js +222 -288
- package/lib/parsers/xiaohongshu.d.ts +1 -0
- package/lib/parsers/xiaohongshu.js +184 -54
- package/lib/types.d.ts +52 -19
- package/lib/utils.d.ts +13 -1
- package/lib/utils.js +89 -11
- package/package.json +47 -47
package/README.md
CHANGED
package/lib/index.js
CHANGED
|
@@ -98,6 +98,10 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
98
98
|
parseLimit: koishi_1.Schema.number().default(3).description("单对话多链接解析上限"),
|
|
99
99
|
useNumeral: koishi_1.Schema.boolean().default(true).description("使用格式化数字 (如 10000 -> 1万)"),
|
|
100
100
|
showError: koishi_1.Schema.boolean().default(false).description("当链接被阻止时提醒发送者"),
|
|
101
|
+
sensitiveMessage: koishi_1.Schema.string().role('textarea').default(`内容包含敏感信息,已停止解析
|
|
102
|
+
如果你是管理员,你可以通过 help share 指令获取更多帮助`).description("内容被判定为敏感且当前会话不允许时返回的提示文案。"),
|
|
103
|
+
blockedMessage: koishi_1.Schema.string().role('textarea').default(`根据策略,该链接已被阻止解析:平台:{platform}
|
|
104
|
+
如果你是管理员,你可以通过 help share 指令获取更多帮助`).description("链接被会话策略阻止时返回的提示文案。<br/>可用占位符: `{platform}`"),
|
|
101
105
|
}).description("高级解析设置"),
|
|
102
106
|
koishi_1.Schema.object({
|
|
103
107
|
cookieCloud: koishi_1.Schema.object({
|
|
@@ -123,6 +127,58 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
123
127
|
netease_apiUrl: koishi_1.Schema.string().role('link').description('网易云音乐 API (NeteaseCloudMusicApi) 的地址<br>' +
|
|
124
128
|
'<a href="https://docs-neteasecloudmusicapi.focalors.ltd/#/?id=neteasecloudmusicapienhanced" target="_blank">点击此处查看部署方式</a>').default('http://127.0.0.1:3000'),
|
|
125
129
|
}).description('网易云音乐设置'),
|
|
130
|
+
koishi_1.Schema.intersect([
|
|
131
|
+
koishi_1.Schema.object({
|
|
132
|
+
githubToken: koishi_1.Schema.string().role('secret').description('GitHub API Token(可选)。留空时匿名请求,速率上限为 60 次/小时;填写后提升到 5000 次/小时,<br>' +
|
|
133
|
+
'同时可以读取私有仓库与私有发行版。<br>' +
|
|
134
|
+
'在 GitHub Settings → Developer settings → Personal access tokens 创建,读取公开数据无需勾选任何权限。').default(''),
|
|
135
|
+
githubCover: koishi_1.Schema.union([
|
|
136
|
+
koishi_1.Schema.const('off').description('不生成封面图'),
|
|
137
|
+
koishi_1.Schema.const('opengraph').description('GitHub 官方社交预览图:1200×600,包含仓库头像、简介、Stars/Fork 等'),
|
|
138
|
+
koishi_1.Schema.const('socialify').description('Socialify 生成:1280×640,主题、字体、纹理与徽标可自定义'),
|
|
139
|
+
]).role('radio').default('off').description('封面图来源'),
|
|
140
|
+
}),
|
|
141
|
+
koishi_1.Schema.union([
|
|
142
|
+
koishi_1.Schema.object({
|
|
143
|
+
githubCover: koishi_1.Schema.const('socialify').required(),
|
|
144
|
+
socialifyTheme: koishi_1.Schema.union([
|
|
145
|
+
koishi_1.Schema.const('Light').description('浅色'),
|
|
146
|
+
koishi_1.Schema.const('Dark').description('深色'),
|
|
147
|
+
]).default('Light').description('配色主题'),
|
|
148
|
+
socialifyFont: koishi_1.Schema.union([
|
|
149
|
+
koishi_1.Schema.const('Inter'),
|
|
150
|
+
koishi_1.Schema.const('JetBrains Mono'),
|
|
151
|
+
koishi_1.Schema.const('KoHo'),
|
|
152
|
+
koishi_1.Schema.const('Crimson Pro'),
|
|
153
|
+
koishi_1.Schema.const('Bitter'),
|
|
154
|
+
koishi_1.Schema.const('Roboto'),
|
|
155
|
+
koishi_1.Schema.const('Source Code Pro'),
|
|
156
|
+
koishi_1.Schema.const('IBM Plex Mono'),
|
|
157
|
+
koishi_1.Schema.const('Raleway'),
|
|
158
|
+
koishi_1.Schema.const('Poppins'),
|
|
159
|
+
]).default('Inter').description('字体'),
|
|
160
|
+
socialifyPattern: koishi_1.Schema.union([
|
|
161
|
+
koishi_1.Schema.const('Plus').description('加号点阵(Socialify 默认)'),
|
|
162
|
+
koishi_1.Schema.const('Signal').description('砖块'),
|
|
163
|
+
koishi_1.Schema.const('Charlie Brown').description('斜纹'),
|
|
164
|
+
koishi_1.Schema.const('Formal Invitation').description('菱形网格'),
|
|
165
|
+
koishi_1.Schema.const('Circuit Board').description('电路板'),
|
|
166
|
+
koishi_1.Schema.const('Overlapping Hexagons').description('六边形'),
|
|
167
|
+
koishi_1.Schema.const('Brick Wall').description('砖墙'),
|
|
168
|
+
koishi_1.Schema.const('Hide').description('无纹理'),
|
|
169
|
+
]).default('Plus').description('背景纹理'),
|
|
170
|
+
socialifyLanguage: koishi_1.Schema.boolean().default(true).description('显示语言徽标'),
|
|
171
|
+
socialifyOwner: koishi_1.Schema.boolean().default(true).description('显示仓库头像'),
|
|
172
|
+
socialifyName: koishi_1.Schema.boolean().default(true).description('显示仓库名'),
|
|
173
|
+
socialifyDescription: koishi_1.Schema.boolean().default(false).description('显示仓库简介'),
|
|
174
|
+
socialifyStars: koishi_1.Schema.boolean().default(true).description('显示 Stars 徽标'),
|
|
175
|
+
socialifyForks: koishi_1.Schema.boolean().default(true).description('显示 Forks 徽标'),
|
|
176
|
+
socialifyIssues: koishi_1.Schema.boolean().default(false).description('显示 Issues 徽标'),
|
|
177
|
+
socialifyPulls: koishi_1.Schema.boolean().default(false).description('显示 Pull Requests 徽标'),
|
|
178
|
+
}),
|
|
179
|
+
koishi_1.Schema.object({}),
|
|
180
|
+
]),
|
|
181
|
+
]).description('GitHub 解析设置'),
|
|
126
182
|
koishi_1.Schema.object({
|
|
127
183
|
onebotReadDir: koishi_1.Schema.string().description('OneBot 实现 (如 NapCat) 所在的容器或环境提供的路径前缀。').default("/app/.config/QQ/NapCat/temp"),
|
|
128
184
|
localDownloadDir: koishi_1.Schema.string().description('与上述路径对应的、Koishi 所在的容器或主机可以访问的路径前缀。').default("/koishi/data/temp"),
|
|
@@ -739,52 +795,71 @@ function apply(ctx, config) {
|
|
|
739
795
|
ctx.setInterval(cleanExpiredCache, config.autoCleanInterval * 60 * 60 * 1000);
|
|
740
796
|
}
|
|
741
797
|
// 注册指令
|
|
798
|
+
/**
|
|
799
|
+
* 会话设置的管理权限:群聊要求管理员,私聊里会话本身就是自己的作用域
|
|
800
|
+
*/
|
|
801
|
+
const canManageSettings = async (session) => {
|
|
802
|
+
if (!session.guildId)
|
|
803
|
+
return true;
|
|
804
|
+
if (!session.userId)
|
|
805
|
+
return false;
|
|
806
|
+
return await (0, utils_1.isUserAdmin)(session, session.userId);
|
|
807
|
+
};
|
|
742
808
|
const cmd = ctx.command('share', '分享解析插件配置', { authority: 1 })
|
|
743
809
|
.action(async ({ session }) => {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
810
|
+
const scope = session && (0, utils_1.getSettingsScope)(session);
|
|
811
|
+
if (!scope)
|
|
812
|
+
return '当前会话不可用。';
|
|
813
|
+
const { parsers, nsfw } = await (0, utils_1.getEffectiveSettings)(ctx, scope, config);
|
|
747
814
|
const parserList = Object.entries(parsers)
|
|
748
815
|
.map(([name, enabled]) => `${enabled ? '✅' : '❌'} ${name}`)
|
|
749
816
|
.join('\n');
|
|
750
817
|
return `当前解析器状态:\n${parserList}\nNSFW 内容:${nsfw ? '✅' : '❌'}`.trim();
|
|
751
818
|
});
|
|
752
|
-
cmd.subcommand('.
|
|
819
|
+
cmd.subcommand('.parser [parser:string] [mode:string]', '查看/管理当前会话的解析器状态', { authority: 1 })
|
|
820
|
+
.alias('.parsers')
|
|
753
821
|
.action(async ({ session }, parser, value) => {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
822
|
+
const scope = session && (0, utils_1.getSettingsScope)(session);
|
|
823
|
+
if (!scope)
|
|
824
|
+
return '当前会话不可用。';
|
|
825
|
+
if (!await canManageSettings(session))
|
|
757
826
|
return '权限不足';
|
|
758
827
|
const isValidParser = (name) => core_1.parsers_str.includes(name);
|
|
759
828
|
if (!isValidParser(parser))
|
|
760
829
|
return '请输入正确的解析器名称';
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const data = await ctx.database.get('sla_group_settings',
|
|
830
|
+
const mode = (0, utils_1.parseBoolean)(value);
|
|
831
|
+
if (mode === undefined)
|
|
832
|
+
return '请输入正确的模式(如 true/false、enable/disable、on/off)';
|
|
833
|
+
const data = await ctx.database.get('sla_group_settings', scope);
|
|
765
834
|
const final_parsers = { ...data[0]?.custom_parsers, ...{ [parser]: mode } };
|
|
766
|
-
const record = { guildId:
|
|
835
|
+
const record = { guildId: scope, custom_parsers: final_parsers };
|
|
767
836
|
await ctx.database.upsert('sla_group_settings', [record]);
|
|
837
|
+
await session.execute('share');
|
|
768
838
|
});
|
|
769
839
|
cmd.subcommand('.nsfw [value:string]', '设置是否允许 NSFW 内容', { authority: 1 })
|
|
770
840
|
.action(async ({ session }, value) => {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
841
|
+
const scope = session && (0, utils_1.getSettingsScope)(session);
|
|
842
|
+
if (!scope)
|
|
843
|
+
return '当前会话不可用。';
|
|
844
|
+
if (!await canManageSettings(session))
|
|
774
845
|
return '权限不足';
|
|
775
|
-
const mode =
|
|
776
|
-
|
|
846
|
+
const mode = (0, utils_1.parseBoolean)(value);
|
|
847
|
+
if (mode === undefined)
|
|
848
|
+
return '请输入正确的值(如 true/false、enable/disable、on/off)';
|
|
849
|
+
const record = { guildId: scope, nsfw_enabled: mode };
|
|
777
850
|
await ctx.database.upsert('sla_group_settings', [record]);
|
|
778
851
|
await session.execute('share');
|
|
779
852
|
});
|
|
780
853
|
cmd.subcommand('.reset', '重置为全局默认设置', { authority: 1 })
|
|
781
854
|
.action(async ({ session }) => {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
855
|
+
const scope = session && (0, utils_1.getSettingsScope)(session);
|
|
856
|
+
if (!scope)
|
|
857
|
+
return '当前会话不可用。';
|
|
858
|
+
if (!await canManageSettings(session))
|
|
785
859
|
return '权限不足';
|
|
786
|
-
await ctx.database.remove('sla_group_settings', { guildId:
|
|
787
|
-
|
|
860
|
+
await ctx.database.remove('sla_group_settings', { guildId: scope });
|
|
861
|
+
await session.send('已重置为全局默认设置。');
|
|
862
|
+
await session.execute('share');
|
|
788
863
|
});
|
|
789
864
|
cmd.subcommand('.migratecache', '迁移旧解析缓存到新时间线并校验', { authority: 4 })
|
|
790
865
|
.action(async () => {
|
|
@@ -1110,22 +1185,13 @@ function apply(ctx, config) {
|
|
|
1110
1185
|
return next();
|
|
1111
1186
|
let linkCount = 0;
|
|
1112
1187
|
for (const link of links) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
else {
|
|
1123
|
-
if (!config.default_parsers[link.platform]) {
|
|
1124
|
-
logger.debug(`根据策略,该链接已被阻止解析:平台:${link.platform},链接:${link.url}`);
|
|
1125
|
-
if (config.showError)
|
|
1126
|
-
await session.send(`根据策略,该链接已被阻止解析:平台:${link.platform}`);
|
|
1127
|
-
continue;
|
|
1128
|
-
}
|
|
1188
|
+
// 会话策略(群组自定义或全局默认)在解析前后都要用到,这里取一次
|
|
1189
|
+
const settings = await (0, utils_1.getEffectiveSettings)(ctx, (0, utils_1.getSettingsScope)(session), config);
|
|
1190
|
+
if (!settings.parsers[link.platform]) {
|
|
1191
|
+
logger.debug(`根据策略,该链接已被阻止解析:平台:${link.platform},链接:${link.url}`);
|
|
1192
|
+
if (config.showError)
|
|
1193
|
+
await session.send(config.blockedMessage.replace(/\{platform\}/g, link.platform));
|
|
1194
|
+
continue;
|
|
1129
1195
|
}
|
|
1130
1196
|
if (linkCount >= config.parseLimit) {
|
|
1131
1197
|
await session.send("已达到单次解析上限…");
|
|
@@ -1292,6 +1358,15 @@ function apply(ctx, config) {
|
|
|
1292
1358
|
}
|
|
1293
1359
|
if (result) {
|
|
1294
1360
|
lastProcessedUrls[channelId][link.url] = Date.now();
|
|
1361
|
+
// 敏感内容在会话层统一拦截:解析结果来自缓存时同样要过这一关
|
|
1362
|
+
if (result.sensitive && !settings.nsfw) {
|
|
1363
|
+
result = {
|
|
1364
|
+
...result,
|
|
1365
|
+
mainbody: (0, utils_1.escapeHtml)(config.sensitiveMessage),
|
|
1366
|
+
coverUrl: undefined,
|
|
1367
|
+
files: []
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1295
1370
|
await (0, utils_1.sendResult)(ctx, session, config, result, logger, sendStats);
|
|
1296
1371
|
}
|
|
1297
1372
|
else {
|
package/lib/parsers/bilibili.js
CHANGED
|
@@ -444,7 +444,7 @@ async function processArticle(ctx, config, link, logger) {
|
|
|
444
444
|
async function processOpus(ctx, config, link, logger) {
|
|
445
445
|
const opusId = link.id;
|
|
446
446
|
// B站新版动态必须传递 timezone_offset,否则部分动态获取为空或报错
|
|
447
|
-
const apiUrl = `https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?timezone_offset=-480&id=${opusId}`;
|
|
447
|
+
const apiUrl = `https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?timezone_offset=-480&features=itemOpusStyle&id=${opusId}`;
|
|
448
448
|
try {
|
|
449
449
|
// 获取 Cookie
|
|
450
450
|
const cookie = await (0, utils_1.getCookie)(ctx, config, exports.name);
|
package/lib/parsers/github.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { Context, Session } from 'koishi';
|
|
|
2
2
|
import { Link, ParsedInfo, PluginConfig } from '../types';
|
|
3
3
|
export declare const name = "github";
|
|
4
4
|
/**
|
|
5
|
-
* 在文本中匹配 GitHub
|
|
5
|
+
* 在文本中匹配 GitHub 链接
|
|
6
6
|
*/
|
|
7
7
|
export declare function match(content: string, ctx: Context, config: PluginConfig): Promise<Link[]>;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* 解析单个 GitHub 链接
|
|
10
10
|
*/
|
|
11
11
|
export declare function process(ctx: Context, config: PluginConfig, link: Link, session: Session): Promise<ParsedInfo | null>;
|