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 CHANGED
@@ -169,7 +169,7 @@ http://127.0.0.1:12001
169
169
  | Netease Music | 歌曲、节目,依赖 NeteaseCloudMusicApi 服务 |
170
170
  | QQ Music | 常见歌曲分享链接 |
171
171
  | Kugou Music | 常见歌曲分享链接 |
172
- | GitHub | 仓库链接基础信息 |
172
+ | GitHub | 仓库、分支/标签、发行版、用户与组织主页 |
173
173
 
174
174
  ## 指令
175
175
 
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
- if (!session?.guildId || !session?.userId)
745
- return '该指令只能在群组中使用。';
746
- const { parsers, nsfw } = await (0, utils_1.getEffectiveSettings)(ctx, session.guildId, config);
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('.parsers [parser:string] [mode:string]', '查看/管理当前群的解析器状态', { authority: 1 })
819
+ cmd.subcommand('.parser [parser:string] [mode:string]', '查看/管理当前会话的解析器状态', { authority: 1 })
820
+ .alias('.parsers')
753
821
  .action(async ({ session }, parser, value) => {
754
- if (!session?.guildId || !session?.userId)
755
- return '该指令只能在群组中使用。';
756
- if (!await (0, utils_1.isUserAdmin)(session, session.userId))
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
- if (!value)
762
- return '请输入正确的模式';
763
- const mode = value.trim().toLowerCase() === 'true';
764
- const data = await ctx.database.get('sla_group_settings', session.guildId);
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: session.guildId, custom_parsers: final_parsers };
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
- if (!session?.guildId || !session?.userId)
772
- return '该指令只能在群组中使用。';
773
- if (!await (0, utils_1.isUserAdmin)(session, session.userId))
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 = value.trim().toLowerCase() === 'true';
776
- const record = { guildId: session.guildId, nsfw_enabled: mode };
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
- if (!session?.guildId || !session?.userId)
783
- return '该指令只能在群组中使用。';
784
- if (!await (0, utils_1.isUserAdmin)(session, session.userId))
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: session.guildId });
787
- return '已重置为全局默认设置。';
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
- if (session.guildId) {
1114
- const settings = await (0, utils_1.getEffectiveSettings)(ctx, session.guildId, config);
1115
- if (!settings.parsers[link.platform]) {
1116
- logger.debug(`根据策略,该链接已被阻止解析:平台:${link.platform},链接:${link.url}`);
1117
- if (config.showError)
1118
- await session.send(`根据策略,该链接已被阻止解析:平台:${link.platform}\n如果你是管理员,你可以通过#help share指令获取更多帮助`);
1119
- continue;
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 {
@@ -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);
@@ -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
- * 处理单个 GitHub 链接
9
+ * 解析单个 GitHub 链接
10
10
  */
11
11
  export declare function process(ctx: Context, config: PluginConfig, link: Link, session: Session): Promise<ParsedInfo | null>;