qq-bot-roost 0.9.7 → 0.9.9

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/web/app.js CHANGED
@@ -1066,6 +1066,21 @@ function fillForm(c) {
1066
1066
  f.logLevel.value = c.logLevel || 'info';
1067
1067
 
1068
1068
  f.groupInvited.checked = Boolean(c.groupManage?.invited);
1069
+
1070
+ /**
1071
+ * 插件来源校验开关。`pubkeySet: false` 时必须说清楚「开了也没用」——
1072
+ * 否则用户打开开关、以为已经受保护,实际什么都没发生,这比没有开关更糟。
1073
+ */
1074
+ const trust = c.security?.trust ?? {};
1075
+ f.trustEnforce.checked = Boolean(trust.enforce);
1076
+ const trustHint = $('#trustHint');
1077
+ if (trustHint) {
1078
+ trustHint.textContent = !trust.pubkeySet
1079
+ ? '当前框架没有内置信任公钥,这个开关即使打开也不起作用(所有插件照常放行)。'
1080
+ : !trust.pubkeyValid
1081
+ ? '信任公钥配错了,解析不了——这个开关打开也不会生效。检查配置里的 security.trust.pubkey。'
1082
+ : '开启后,官方市场里未签名或签名不符的插件会被拒绝安装和更新;手写插件、以及你自己配置的市场源不受影响。';
1083
+ }
1069
1084
  }
1070
1085
 
1071
1086
  /* ---------------- 事件订阅 intents ---------------- */
@@ -1348,6 +1363,10 @@ $('#cfgForm').addEventListener('submit', async (e) => {
1348
1363
  groupManage: {
1349
1364
  invited: f.groupInvited.checked,
1350
1365
  },
1366
+ // 全局设置(config/bot.json),和 webui 一样不写进机器人配置
1367
+ security: {
1368
+ trust: { enforce: f.trustEnforce.checked },
1369
+ },
1351
1370
  };
1352
1371
 
1353
1372
  const btn = f.querySelector('button[type=submit]');
@@ -1804,6 +1823,31 @@ function stopPluginDashPolling() {
1804
1823
  pluginDashTimer = null;
1805
1824
  }
1806
1825
 
1826
+ /** 插件卡片上的声明摘要:命令/工具/钩子数量 + 版本要求 */
1827
+ function renderDecl(p) {
1828
+ const pr = p.provides || {};
1829
+ const bits = [];
1830
+ if (pr.commands?.length) bits.push(`命令 ${pr.commands.length}`);
1831
+ if (pr.tools?.length) bits.push(`工具 ${pr.tools.length}`);
1832
+ if (pr.hooks?.length) bits.push(`钩子 ${pr.hooks.length}`);
1833
+ const eng = Object.entries(p.engines || {});
1834
+ if (!bits.length && !eng.length) return '';
1835
+ const detail = [
1836
+ pr.commands?.length ? `命令:${pr.commands.join('、')}` : '',
1837
+ pr.tools?.length ? `工具:${pr.tools.join('、')}` : '',
1838
+ pr.hooks?.length ? `钩子:${pr.hooks.join('、')}` : '',
1839
+ ].filter(Boolean).join(';');
1840
+ const engText = eng.map(([k, v]) => `${k} ${v}`).join(' / ');
1841
+ return [
1842
+ bits.length
1843
+ ? `<span class="hint" title="${escapeHtml(detail)}">声明 ${escapeHtml(bits.join(' · '))}</span>`
1844
+ : '',
1845
+ eng.length
1846
+ ? `<span class="hint" title="插件声明的版本要求">${p.enginesOk === false ? '⚠ ' : ''}需 ${escapeHtml(engText)}</span>`
1847
+ : '',
1848
+ ].join('');
1849
+ }
1850
+
1807
1851
  async function loadPlugins() {
1808
1852
  const wrap = $('#pluginList');
1809
1853
  // 插件装了/卸了,它注册的面板页面也会变,顺带刷一下标签栏
@@ -1850,6 +1894,7 @@ async function loadPlugins() {
1850
1894
  ? `<span class="hint" title="插件声明的框架版本要求">需框架 ${escapeHtml(p.frameworkVersion)}</span>`
1851
1895
  : ''
1852
1896
  }
1897
+ ${renderDecl(p)}
1853
1898
  ${(p.shortDesc || p.description) ? '<br>' + escapeHtml(p.shortDesc || p.description) : ''}
1854
1899
  </div>
1855
1900
  ${chips ? `<div class="plugin-metrics">${chips}</div>` : ''}
@@ -2223,6 +2268,7 @@ function renderMarketRows() {
2223
2268
  <span class="kind-tag ${p.runtime === 'node' ? 'int' : 'ext'}">
2224
2269
  ${p.runtime === 'node' ? '内建 · node' : '外部 · ' + escapeHtml(p.runtime || '')}
2225
2270
  </span>
2271
+ ${verifyTag(p)}
2226
2272
  ${p.author ? `<span class="hint">by ${escapeHtml(p.author)}</span>` : ''}
2227
2273
  ${p.credits ? `<span class="hint">${escapeHtml(p.credits)}</span>` : ''}
2228
2274
  ${p.description ? '<br>' + escapeHtml(p.description) : ''}
@@ -3540,6 +3586,23 @@ function escapeHtml(s) {
3540
3586
  return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
3541
3587
  }
3542
3588
 
3589
+ /**
3590
+ * 插件分级标签。
3591
+ *
3592
+ * 分级是**我们的判断**(写在插件仓的 verified.json 里),不是插件自述;
3593
+ * 签名是另一回事——它是「这份索引没被改过」的全体属性,所有签名插件
3594
+ * 都落同一个状态,所以不能做成档位。
3595
+ */
3596
+ function verifyTag(p) {
3597
+ const map = {
3598
+ official: ['官方维护', 'vt-official', '跟着框架一起发布,代码在同一个仓库里'],
3599
+ reviewed: ['已复核', 'vt-reviewed', '我们看过代码、试过能跑,但不由我们维护'],
3600
+ self: ['作者自证', 'vt-self', '作者声明的内容,我们没有核对'],
3601
+ };
3602
+ const [label, cls, tip] = map[p.verified] || ['未验证', 'vt-none', '没有分级信息'];
3603
+ return `<span class="verify-tag ${cls}" title="${escapeHtml(tip)}">${label}</span>`;
3604
+ }
3605
+
3543
3606
  function applyFilter() {
3544
3607
  renderLogs();
3545
3608
  }
package/web/index.html CHANGED
@@ -394,6 +394,24 @@
394
394
  <div class="hint">修改监听地址或端口需要重启进程才生效。</div>
395
395
  </div>
396
396
 
397
+ <div class="box">
398
+ <div class="box-hd"><h3>插件来源校验</h3></div>
399
+ <label class="switch">
400
+ <input type="checkbox" name="trustEnforce">
401
+ 强制校验插件市场签名
402
+ <span class="beta-tag" title="签名文件刚铺开,还没经过一轮完整发版验证">Beta</span>
403
+ <span class="hint">默认关闭</span>
404
+ </label>
405
+ <div class="hint" id="trustHint">
406
+ 开启后,官方市场里未签名或签名不符的插件会被拒绝安装和更新;手写插件、以及你自己配置的市场源不受影响。
407
+ 需要框架内置信任公钥——没内置时这个开关不起作用(所有插件照常放行)。
408
+ </div>
409
+ <div class="hint">
410
+ <b>Beta:</b>签名文件还没跟着一次正式发版走完流程,现在打开可能让某些插件装不上。
411
+ <b>建议先保持关闭</b>,等发版验证过一轮再考虑。
412
+ </div>
413
+ </div>
414
+
397
415
  <div class="sticky-actions">
398
416
  <span id="cfgMsg" class="hint"></span>
399
417
  <button type="submit" class="btn primary">保存配置</button>
package/web/style.css CHANGED
@@ -971,6 +971,26 @@ label.switch.small { font-size: 12.5px; color: var(--muted); }
971
971
  background: var(--primary-soft, var(--panel-3)); color: var(--primary);
972
972
  }
973
973
 
974
+ /* 插件分级标签:官方 / 已复核 / 作者自证 / 未验证 */
975
+ .verify-tag {
976
+ display: inline-block; margin-right: 6px; padding: 0 6px;
977
+ border-radius: 4px; font-size: 10.5px; font-weight: 600;
978
+ vertical-align: 1px;
979
+ }
980
+ .verify-tag.vt-official { background: var(--primary-soft); color: var(--primary); }
981
+ .verify-tag.vt-reviewed { background: var(--c-teal-soft); color: var(--c-teal); }
982
+ .verify-tag.vt-self { background: var(--warn-soft); color: var(--warn); }
983
+ .verify-tag.vt-none { background: var(--panel-3); color: var(--muted); }
984
+
985
+ /* Beta 标记:签名开关还是 beta,不能让它看起来跟别的开关一样成熟 */
986
+ .beta-tag {
987
+ display: inline-block; margin-left: 6px; padding: 0 7px;
988
+ border-radius: 999px; font-size: 10.5px; font-weight: 600;
989
+ background: var(--warn-soft); color: var(--warn);
990
+ border: 1px solid var(--warn-border);
991
+ vertical-align: 1px;
992
+ }
993
+
974
994
  /* 连接凭证下方的保存条:不吸底,紧跟在输入框下面 */
975
995
  .cred-actions {
976
996
  display: flex; align-items: center; justify-content: flex-end; gap: 14px;