koishi-plugin-adapter-onebot-multi 0.0.10 → 0.0.12

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.
Files changed (2) hide show
  1. package/lib/index.js +273 -1
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -1628,6 +1628,77 @@ var StatusPanel = class {
1628
1628
  this.ctx.logger.info(`重启 Bot 配置: #${id} ${config.name}`);
1629
1629
  ctx.body = { success: true };
1630
1630
  });
1631
+ router.post(`${adminPath}/api/bots/update`, authMiddleware, async (ctx) => {
1632
+ const data = await this.parseJsonBody(ctx);
1633
+ const id = Number(data.id);
1634
+ const existing = await this.configManager.getConfigById(id);
1635
+ if (!existing) {
1636
+ ctx.status = 404;
1637
+ ctx.body = { error: `配置 #${id} 不存在` };
1638
+ return;
1639
+ }
1640
+ const updates = {};
1641
+ if (data.name !== void 0) updates.name = data.name;
1642
+ if (data.protocol !== void 0) updates.protocol = data.protocol;
1643
+ if (data.endpoint !== void 0) updates.endpoint = data.endpoint;
1644
+ if (data.path !== void 0) updates.path = data.path;
1645
+ if (data.token !== void 0) updates.token = data.token;
1646
+ await this.configManager.updateConfigById(id, updates);
1647
+ this.ctx.logger.info(`更新 Bot 配置: #${id} ${existing.name}`);
1648
+ if (existing.enabled && existing.selfId) {
1649
+ await this.stopBot(existing.selfId);
1650
+ const newConfig = await this.configManager.getConfigById(id);
1651
+ if (newConfig) {
1652
+ await this.startBot(this.configManager.toBotConfig(newConfig));
1653
+ }
1654
+ }
1655
+ ctx.body = { success: true };
1656
+ });
1657
+ router.post(`${adminPath}/api/bots/profile`, authMiddleware, async (ctx) => {
1658
+ const data = await this.parseJsonBody(ctx);
1659
+ const selfId = String(data.selfId);
1660
+ const nickname = data.nickname || "";
1661
+ const bot = this.ctx.bots.find((b) => b.selfId === selfId && b.platform === "onebot");
1662
+ if (!bot) {
1663
+ ctx.status = 404;
1664
+ ctx.body = { error: `Bot ${selfId} 未在线` };
1665
+ return;
1666
+ }
1667
+ try {
1668
+ await bot.internal.setQqProfile(nickname, "", "", "", "");
1669
+ this.ctx.logger.info(`修改 Bot ${selfId} 昵称为: ${nickname}`);
1670
+ ctx.body = { success: true };
1671
+ } catch (error) {
1672
+ this.ctx.logger.error("修改资料失败:", error);
1673
+ ctx.status = 500;
1674
+ ctx.body = { error: "修改失败: " + error.message };
1675
+ }
1676
+ });
1677
+ router.post(`${adminPath}/api/bots/avatar`, authMiddleware, async (ctx) => {
1678
+ const data = await this.parseJsonBody(ctx);
1679
+ const selfId = String(data.selfId);
1680
+ const file = data.file;
1681
+ const bot = this.ctx.bots.find((b) => b.selfId === selfId && b.platform === "onebot");
1682
+ if (!bot) {
1683
+ ctx.status = 404;
1684
+ ctx.body = { error: `Bot ${selfId} 未在线` };
1685
+ return;
1686
+ }
1687
+ if (!file) {
1688
+ ctx.status = 400;
1689
+ ctx.body = { error: "请提供图片" };
1690
+ return;
1691
+ }
1692
+ try {
1693
+ await bot.internal.setQqAvatar(file);
1694
+ this.ctx.logger.info(`修改 Bot ${selfId} 头像`);
1695
+ ctx.body = { success: true };
1696
+ } catch (error) {
1697
+ this.ctx.logger.error("修改头像失败:", error);
1698
+ ctx.status = 500;
1699
+ ctx.body = { error: "修改失败: " + error.message };
1700
+ }
1701
+ });
1631
1702
  if (this.config.port) {
1632
1703
  this.app = new import_koa.default();
1633
1704
  this.app.use(router.routes());
@@ -1930,6 +2001,72 @@ var StatusPanel = class {
1930
2001
  </div>
1931
2002
  </div>
1932
2003
 
2004
+ <!-- 编辑 Bot 配置模态框 -->
2005
+ <div class="modal" id="configModal">
2006
+ <div class="modal-content">
2007
+ <h2>编辑 Bot 配置</h2>
2008
+ <form id="configForm">
2009
+ <input type="hidden" id="configId">
2010
+ <div class="form-group">
2011
+ <label for="configEditName">配置名称 *</label>
2012
+ <input type="text" id="configEditName" required placeholder="例如: 主号、小号">
2013
+ </div>
2014
+ <div class="form-group">
2015
+ <label for="configProtocol">协议</label>
2016
+ <select id="configProtocol" onchange="toggleConfigEndpoint()">
2017
+ <option value="ws-reverse">反向 WebSocket (ws-reverse)</option>
2018
+ <option value="ws">正向 WebSocket (ws)</option>
2019
+ </select>
2020
+ </div>
2021
+ <div class="form-group" id="configEndpointGroup" style="display: none;">
2022
+ <label for="configEndpoint">Endpoint *</label>
2023
+ <input type="text" id="configEndpoint" placeholder="例如: ws://127.0.0.1:6700">
2024
+ </div>
2025
+ <div class="form-group" id="configPathGroup">
2026
+ <label for="configPath">路径</label>
2027
+ <input type="text" id="configPath" placeholder="例如: /onebot">
2028
+ </div>
2029
+ <div class="form-group">
2030
+ <label for="configToken">Token(可选)</label>
2031
+ <input type="password" id="configToken" placeholder="访问令牌(留空不修改)">
2032
+ </div>
2033
+ <div class="modal-actions">
2034
+ <button type="submit" class="btn">保存</button>
2035
+ <button type="button" class="btn btn-secondary" onclick="hideConfigModal()">取消</button>
2036
+ </div>
2037
+ </form>
2038
+ </div>
2039
+ </div>
2040
+
2041
+ <!-- 修改 Bot 资料模态框 -->
2042
+ <div class="modal" id="profileModal">
2043
+ <div class="modal-content">
2044
+ <h2>修改 Bot 资料</h2>
2045
+ <form id="profileForm">
2046
+ <input type="hidden" id="profileSelfId">
2047
+ <div class="form-group">
2048
+ <label for="profileNickname">昵称</label>
2049
+ <input type="text" id="profileNickname" placeholder="新的QQ昵称">
2050
+ </div>
2051
+ <div class="form-group">
2052
+ <label for="profileAvatar">头像</label>
2053
+ <input type="file" id="profileAvatar" accept="image/*" style="display:none">
2054
+ <div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
2055
+ <img id="avatarPreview" style="width:64px;height:64px;border:var(--nl-border);border-radius:50%;object-fit:cover;">
2056
+ <button type="button" class="btn btn-secondary" onclick="document.getElementById('profileAvatar').click()" style="padding:0.5rem 1rem;">选择图片</button>
2057
+ <span id="avatarFileName" style="font-size:0.85rem;color:#92400e;"></span>
2058
+ </div>
2059
+ <p style="font-size:0.8rem;color:#92400e;margin-top:0.5rem;">支持本地图片或输入图片URL</p>
2060
+ <input type="text" id="profileAvatarUrl" placeholder="或输入图片URL" style="margin-top:0.5rem;">
2061
+ </div>
2062
+ <div class="modal-actions">
2063
+ <button type="submit" class="btn">保存</button>
2064
+ <button type="button" class="btn btn-secondary" onclick="hideProfileModal()">取消</button>
2065
+ </div>
2066
+ </form>
2067
+ </div>
2068
+ </div>
2069
+
1933
2070
  <!-- Toast -->
1934
2071
  <div class="toast" id="toast"></div>
1935
2072
 
@@ -2138,7 +2275,9 @@ var StatusPanel = class {
2138
2275
  '<div class="bot-actions">' +
2139
2276
  '<button class="btn btn-secondary" onclick="toggleBot(' + bot.id + ')">' + (bot.enabled ? '禁用' : '启用') + '</button>' +
2140
2277
  '<button class="btn btn-secondary" onclick="restartBot(' + bot.id + ')">重启</button>' +
2141
- '<button class="btn btn-danger" onclick="deleteBot(' + bot.id + ', \\'' + bot.name + '\\')">删除</button>' +
2278
+ '<button class="btn btn-secondary" onclick="showConfigModal(' + bot.id + ', \\'' + bot.name.replace(/'/g, "\\\\'") + '\\', \\'' + (bot.protocol || 'ws-reverse') + '\\', \\'' + (bot.endpoint || '').replace(/'/g, "\\\\'") + '\\', \\'' + (bot.path || '/onebot').replace(/'/g, "\\\\'") + '\\')">配置</button>' +
2279
+ (bot.status === 'online' ? '<button class="btn btn-secondary" onclick="showProfileModal(\\'' + bot.selfId + '\\', \\'' + (bot.nickname || '').replace(/'/g, "\\\\'") + '\\')">资料</button>' : '') +
2280
+ '<button class="btn btn-danger" onclick="deleteBot(' + bot.id + ', \\'' + bot.name.replace(/'/g, "\\\\'") + '\\')">删除</button>' +
2142
2281
  '</div>' +
2143
2282
  '</div>'
2144
2283
  }).join('')
@@ -2221,6 +2360,139 @@ var StatusPanel = class {
2221
2360
  }
2222
2361
  })
2223
2362
 
2363
+ // 编辑 Bot 配置模态框
2364
+ function showConfigModal(id, name, protocol, endpoint, path) {
2365
+ document.getElementById('configId').value = id
2366
+ document.getElementById('configEditName').value = name || ''
2367
+ document.getElementById('configProtocol').value = protocol || 'ws-reverse'
2368
+ document.getElementById('configEndpoint').value = endpoint || ''
2369
+ document.getElementById('configPath').value = path || '/onebot'
2370
+ document.getElementById('configToken').value = ''
2371
+ toggleConfigEndpoint()
2372
+ document.getElementById('configModal').classList.add('active')
2373
+ }
2374
+
2375
+ function hideConfigModal() {
2376
+ document.getElementById('configModal').classList.remove('active')
2377
+ }
2378
+
2379
+ function toggleConfigEndpoint() {
2380
+ const protocol = document.getElementById('configProtocol').value
2381
+ document.getElementById('configEndpointGroup').style.display = protocol === 'ws' ? 'block' : 'none'
2382
+ document.getElementById('configPathGroup').style.display = protocol === 'ws-reverse' ? 'block' : 'none'
2383
+ }
2384
+
2385
+ document.getElementById('configForm').addEventListener('submit', async (e) => {
2386
+ e.preventDefault()
2387
+ const id = document.getElementById('configId').value
2388
+ const data = {
2389
+ id: Number(id),
2390
+ name: document.getElementById('configEditName').value,
2391
+ protocol: document.getElementById('configProtocol').value,
2392
+ endpoint: document.getElementById('configEndpoint').value || undefined,
2393
+ path: document.getElementById('configPath').value || undefined,
2394
+ }
2395
+ // Token 留空不修改
2396
+ const token = document.getElementById('configToken').value
2397
+ if (token) data.token = token
2398
+
2399
+ try {
2400
+ await api('/bots/update', data)
2401
+ showToast('配置更新成功')
2402
+ hideConfigModal()
2403
+ refreshBots()
2404
+ } catch (e) {
2405
+ showToast('更新失败: ' + e.message, 'error')
2406
+ }
2407
+ })
2408
+
2409
+ // 修改 Bot 资料模态框
2410
+ let profileAvatarFile = null
2411
+
2412
+ function showProfileModal(selfId, nickname) {
2413
+ document.getElementById('profileSelfId').value = selfId
2414
+ document.getElementById('profileNickname').value = nickname || ''
2415
+ document.getElementById('avatarPreview').src = 'http://q.qlogo.cn/headimg_dl?dst_uin=' + selfId + '&spec=640'
2416
+ document.getElementById('avatarFileName').textContent = ''
2417
+ document.getElementById('profileAvatarUrl').value = ''
2418
+ profileAvatarFile = null
2419
+ document.getElementById('profileModal').classList.add('active')
2420
+ }
2421
+
2422
+ function hideProfileModal() {
2423
+ document.getElementById('profileModal').classList.remove('active')
2424
+ profileAvatarFile = null
2425
+ }
2426
+
2427
+ // 头像文件选择
2428
+ document.getElementById('profileAvatar').addEventListener('change', function(e) {
2429
+ const file = e.target.files[0]
2430
+ if (file) {
2431
+ profileAvatarFile = file
2432
+ document.getElementById('avatarFileName').textContent = file.name
2433
+ // 预览
2434
+ const reader = new FileReader()
2435
+ reader.onload = function(ev) {
2436
+ document.getElementById('avatarPreview').src = ev.target.result
2437
+ }
2438
+ reader.readAsDataURL(file)
2439
+ }
2440
+ })
2441
+
2442
+ // 资料表单提交
2443
+ document.getElementById('profileForm').addEventListener('submit', async (e) => {
2444
+ e.preventDefault()
2445
+ const selfId = document.getElementById('profileSelfId').value
2446
+ const nickname = document.getElementById('profileNickname').value
2447
+ const avatarUrl = document.getElementById('profileAvatarUrl').value
2448
+
2449
+ let hasChange = false
2450
+
2451
+ // 修改昵称
2452
+ if (nickname) {
2453
+ try {
2454
+ await api('/bots/profile', { selfId, nickname })
2455
+ showToast('昵称修改成功')
2456
+ hasChange = true
2457
+ } catch (e) {
2458
+ showToast('昵称修改失败: ' + e.message, 'error')
2459
+ }
2460
+ }
2461
+
2462
+ // 修改头像
2463
+ if (profileAvatarFile) {
2464
+ // 本地文件转 base64
2465
+ const reader = new FileReader()
2466
+ reader.onload = async function(ev) {
2467
+ try {
2468
+ await api('/bots/avatar', { selfId, file: ev.target.result })
2469
+ showToast('头像修改成功')
2470
+ refreshBots()
2471
+ } catch (e) {
2472
+ showToast('头像修改失败: ' + e.message, 'error')
2473
+ }
2474
+ }
2475
+ reader.readAsDataURL(profileAvatarFile)
2476
+ hasChange = true
2477
+ } else if (avatarUrl) {
2478
+ // URL 方式
2479
+ try {
2480
+ await api('/bots/avatar', { selfId, file: avatarUrl })
2481
+ showToast('头像修改成功')
2482
+ hasChange = true
2483
+ } catch (e) {
2484
+ showToast('头像修改失败: ' + e.message, 'error')
2485
+ }
2486
+ }
2487
+
2488
+ if (hasChange) {
2489
+ hideProfileModal()
2490
+ setTimeout(refreshBots, 1000)
2491
+ } else {
2492
+ showToast('未做任何修改', 'error')
2493
+ }
2494
+ })
2495
+
2224
2496
  // 自动刷新
2225
2497
  setInterval(() => {
2226
2498
  if (document.getElementById('adminView').classList.contains('show')) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-adapter-onebot-multi",
3
3
  "description": "奶龙bot定制版onebot适配器,支持自动负载均衡,适配器级黑名单/白名单,提供webui,可指定端口",
4
- "version": "0.0.10",
4
+ "version": "0.0.12",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [