koishi-plugin-kkk 1.0.7 → 1.0.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # 更新日志
2
2
 
3
+ ## 1.0.8
4
+
5
+ ### 修复
6
+ - 控制台入口包加载失败导致**侧边栏「kkk 配置」整个消失**:上一版加 RPC 失败日志时把 `try / else` 括错了,
7
+ 注册面板路由的那段代码跑到作用域外(`ReferenceError: server is not defined`),现在结构修正。
8
+ - 面板前端包禁用缓存(`/@plugin-<id>/index.js` 加 `no-store`):控制台默认不带缓存头,
9
+ 浏览器会一直用旧 bundle,表现为「代码改了但界面没变」。
10
+
11
+ ### 变更
12
+ - 控制台页面取登录凭据改成两条路依次尝试(RPC → store.user),都不行时**把失败原因显示在页面上**,
13
+ 方便一眼看出是没登录还是别的问题。
14
+
3
15
  ## 1.0.7
4
16
 
5
17
  ### 修复
package/client/index.ts CHANGED
@@ -1,17 +1,14 @@
1
1
  /**
2
2
  * kkk 的控制台入口(浏览器端)。
3
3
  *
4
- * 用官方工具 `koishi-console build .` 打包(入口必须是 client/index.ts),产物在 dist/index.js。
5
- *
6
- * 面板本体是插件自带的 `/kkk`(独立页面)。qq-chat 那种做法是把界面注册成控制台页面、
7
- * 天然跟着控制台登录态;独立 URL 没有这层保护,所以这里向服务端要一个「面板 token」:
8
- * 1. `send('kkk/panel-token')` —— 服务端那个监听器带 authority: 4,
9
- * 未登录 / 权限不足的控制台客户端根本调不到;
10
- * 2. 拿到 token 后拼进 iframe 地址(/kkk?panel=<token>),服务端校验通过会给面板发 cookie,
11
- * 后续静态资源与接口都靠它放行;
12
- * 3. 拿不到 token(没登录)就只显示提示,不加载面板。
4
+ * 面板本体是插件自带的 `/kkk`(独立页面),独立 URL 没有控制台那层保护,
5
+ * 所以这里负责把「控制台已登录」这件事告诉面板。两条路依次尝试:
6
+ * 1. RPC `kkk/panel-token`(服务端监听器带 authority: 4,未登录根本调不到);
7
+ * 2. 控制台 store 里的 user。
8
+ * 拿到凭据后拼进 iframe 地址;两条路都不行才显示「请先登录」,并把最后一条错误原因带上,
9
+ * 方便一眼看出是没登录还是别的什么问题。
13
10
  */
14
- import { send } from '@koishijs/client'
11
+ import { send, store } from '@koishijs/client'
15
12
  import { computed, defineComponent, h, onMounted, onUnmounted, ref } from 'vue'
16
13
 
17
14
  export const name = 'kkk-console'
@@ -30,8 +27,9 @@ export function apply (ctx: any) {
30
27
  setup () {
31
28
  const style = ref<Record<string, string>>({})
32
29
  const status = ref<{ authRequired: boolean } | null>(null)
33
- const panelToken = ref('')
34
- const denied = ref(false)
30
+ const target = ref('')
31
+ const reason = ref('')
32
+ const ready = ref(false)
35
33
  const sync = () => {
36
34
  const aside = document.querySelector('nav.layout-activity') as HTMLElement | null
37
35
  const footer = document.querySelector('footer.layout-status') as HTMLElement | null
@@ -50,46 +48,66 @@ export function apply (ctx: any) {
50
48
  }
51
49
  }
52
50
  let timer: any = null
53
- onMounted(async () => {
54
- sync()
55
- timer = setInterval(sync, 1000)
56
- window.addEventListener('resize', sync)
51
+
52
+ const openPanel = async () => {
53
+ let required = true
57
54
  try {
58
55
  const res = await fetch('/kkk/api/status').then((r) => r.json())
59
56
  status.value = (res && res.data) || null
57
+ required = !status.value || status.value.authRequired !== false
60
58
  } catch {
61
59
  status.value = null
62
60
  }
63
- if (!status.value || status.value.authRequired) {
64
- try {
65
- const token = await send('kkk/panel-token')
66
- panelToken.value = typeof token === 'string' ? token : ''
67
- } catch {
68
- panelToken.value = ''
61
+ if (!required) { target.value = '/kkk'; ready.value = true; return }
62
+
63
+ // 1. RPC 换面板 token
64
+ try {
65
+ const token = await send('kkk/panel-token')
66
+ if (typeof token === 'string' && token) { target.value = '/kkk?panel=' + token; ready.value = true; return }
67
+ reason.value = 'RPC 没有返回 token'
68
+ } catch (error: any) {
69
+ reason.value = 'RPC 失败: ' + String(error?.message ?? error)
70
+ }
71
+
72
+ // 2. store 里的 user(老版本控制台可能只在这里放登录信息)
73
+ try {
74
+ const user: any = (store as any)?.user
75
+ if (user?.token && user?.id) {
76
+ target.value = '/kkk?uid=' + user.id + '&token=' + user.token
77
+ ready.value = true
78
+ return
69
79
  }
70
- denied.value = !panelToken.value
80
+ } catch (error: any) {
81
+ reason.value = reason.value || '读取 store.user 失败: ' + String(error?.message ?? error)
71
82
  }
83
+ ready.value = true
84
+ }
85
+
86
+ onMounted(() => {
87
+ sync()
88
+ timer = setInterval(sync, 1000)
89
+ window.addEventListener('resize', sync)
90
+ void openPanel()
72
91
  })
73
92
  onUnmounted(() => {
74
93
  if (timer) clearInterval(timer)
75
94
  window.removeEventListener('resize', sync)
76
95
  })
77
- const src = computed(() => {
78
- const s = status.value
79
- if (s && s.authRequired === false) return '/kkk'
80
- return panelToken.value ? '/kkk?panel=' + encodeURIComponent(panelToken.value) : ''
81
- })
96
+
97
+ const src = computed(() => target.value)
82
98
  return () => {
83
99
  if (src.value) return h('iframe', { src: src.value, style: style.value })
100
+ if (!ready.value) return h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: '#666', fontFamily: 'system-ui' } }, '正在打开配置面板……')
84
101
  return h('div', {
85
102
  style: { display: 'flex', flexDirection: 'column', gap: '12px', alignItems: 'center', justifyContent: 'center', height: '100%', fontFamily: 'system-ui', color: '#666' }
86
103
  }, [
87
- h('h2', { style: { margin: 0, fontSize: '20px' } }, denied.value ? '请先登录 Koishi 控制台' : '正在打开配置面板……'),
88
- denied.value ? h('p', { style: { margin: 0 } }, '登录后即可打开 kkk 配置面板。') : null,
89
- denied.value ? h('a', { href: '/login', style: { color: '#3b82f6' } }, '去登录') : null
104
+ h('h2', { style: { margin: 0, fontSize: '20px' } }, '请先登录 Koishi 控制台'),
105
+ h('p', { style: { margin: 0 } }, '登录后即可打开 kkk 配置面板。'),
106
+ reason.value ? h('p', { style: { margin: 0, fontSize: '12px', color: '#999' } }, reason.value) : null,
107
+ h('a', { href: '/login', style: { color: '#3b82f6' } }, '去登录')
90
108
  ])
91
109
  }
92
110
  }
93
111
  })
94
112
  })
95
- }
113
+ }
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{send as v}from"@koishijs/client";import{defineComponent as y,ref as u,onMounted as h,onUnmounted as m,computed as g,h as o}from"vue";const A="kkk-console",f=64,x=28;function I(p){p.page({name:"kkk 配置",path:"/kkk-config",desc:"解析 / 面板 / 画质 / 图片切片等设置,改完直接生效",authority:4,icon:"activity:plugin",component:y({setup(){const r=u({}),t=u(null),n=u(""),l=u(false),a=()=>{const e=document.querySelector("nav.layout-activity"),i=document.querySelector("footer.layout-status"),d=e&&Math.round(e.getBoundingClientRect().right)||f,k=(i?Math.round(i.getBoundingClientRect().height):x)+8;r.value={position:"fixed",left:d+"px",top:"0px",width:"calc(100vw - "+d+"px)",height:"calc(100vh - "+k+"px)",border:"0",display:"block",background:"#fff",zIndex:"1"}};let s=null;h(async()=>{a(),s=setInterval(a,1e3),window.addEventListener("resize",a);try{const e=await fetch("/kkk/api/status").then(i=>i.json());t.value=e&&e.data||null}catch{t.value=null}if(!t.value||t.value.authRequired){try{const e=await v("kkk/panel-token");n.value=typeof e=="string"?e:""}catch{n.value=""}l.value=!n.value}}),m(()=>{s&&clearInterval(s),window.removeEventListener("resize",a)});const c=g(()=>{const e=t.value;return e&&e.authRequired===false?"/kkk":n.value?"/kkk?panel="+encodeURIComponent(n.value):""});return()=>c.value?o("iframe",{src:c.value,style:r.value}):o("div",{style:{display:"flex",flexDirection:"column",gap:"12px",alignItems:"center",justifyContent:"center",height:"100%",fontFamily:"system-ui",color:"#666"}},[o("h2",{style:{margin:0,fontSize:"20px"}},l.value?"请先登录 Koishi 控制台":"正在打开配置面板……"),l.value?o("p",{style:{margin:0}},"登录后即可打开 kkk 配置面板。"):null,l.value?o("a",{href:"/login",style:{color:"#3b82f6"}},"去登录"):null])}})})}export{I as apply,A as name};
1
+ import{send as m,store as g}from"@koishijs/client";import{defineComponent as h,ref as i,onMounted as x,onUnmounted as C,computed as S,h as t}from"vue";const L="kkk-console",k=64,w=28;function b(p){p.page({name:"kkk 配置",path:"/kkk-config",desc:"解析 / 面板 / 画质 / 图片切片等设置,改完直接生效",authority:4,icon:"activity:plugin",component:h({setup(){const v=i({}),r=i(null),s=i(""),n=i(""),o=i(false),u=()=>{const a=document.querySelector("nav.layout-activity"),l=document.querySelector("footer.layout-status"),e=a&&Math.round(a.getBoundingClientRect().right)||k,f=(l?Math.round(l.getBoundingClientRect().height):w)+8;v.value={position:"fixed",left:e+"px",top:"0px",width:"calc(100vw - "+e+"px)",height:"calc(100vh - "+f+"px)",border:"0",display:"block",background:"#fff",zIndex:"1"}};let c=null;const y=async()=>{var l;let a=true;try{const e=await fetch("/kkk/api/status").then(f=>f.json());r.value=e&&e.data||null,a=!r.value||r.value.authRequired!==false}catch{r.value=null}if(!a){s.value="/kkk",o.value=true;return}try{const e=await m("kkk/panel-token");if(typeof e=="string"&&e){s.value="/kkk?panel="+e,o.value=true;return}n.value="RPC 没有返回 token"}catch(e){n.value="RPC 失败: "+String((e==null?void 0:e.message)??e)}try{const e=(l=g)==null?void 0:l.user;if(e!=null&&e.token&&(e!=null&&e.id)){s.value="/kkk?uid="+e.id+"&token="+e.token,o.value=true;return}}catch(e){n.value=n.value||"读取 store.user 失败: "+String((e==null?void 0:e.message)??e)}o.value=true};x(()=>{u(),c=setInterval(u,1e3),window.addEventListener("resize",u),y()}),C(()=>{c&&clearInterval(c),window.removeEventListener("resize",u)});const d=S(()=>s.value);return()=>d.value?t("iframe",{src:d.value,style:v.value}):o.value?t("div",{style:{display:"flex",flexDirection:"column",gap:"12px",alignItems:"center",justifyContent:"center",height:"100%",fontFamily:"system-ui",color:"#666"}},[t("h2",{style:{margin:0,fontSize:"20px"}},"请先登录 Koishi 控制台"),t("p",{style:{margin:0}},"登录后即可打开 kkk 配置面板。"),n.value?t("p",{style:{margin:0,fontSize:"12px",color:"#999"}},n.value):null,t("a",{href:"/login",style:{color:"#3b82f6"}},"去登录")]):t("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"100%",color:"#666",fontFamily:"system-ui"}},"正在打开配置面板……")}})})}export{b as apply,L as name};
package/lib/webui.js CHANGED
@@ -229,15 +229,20 @@ function registerWebUi({ ctx, config, rawConfig, logger, pluginRoot }) {
229
229
  };
230
230
  try {
231
231
  const consoleService = ctx.console;
232
- consoleService?.addListener?.('kkk/panel-token', function () {
233
- // this 是发起调用的控制台客户端,auth 由 auth 插件写入
234
- if (!this?.auth)
235
- throw new Error('请先登录 Koishi 控制台');
236
- return issuePanelToken();
237
- }, { authority: 4 });
232
+ if (typeof consoleService?.addListener !== 'function') {
233
+ logger.warn('[kkk] 控制台没有 addListener,面板 token RPC 未注册(面板将无法从控制台获取登录态)');
234
+ }
235
+ else {
236
+ consoleService.addListener('kkk/panel-token', function () {
237
+ // this 是发起调用的控制台客户端,auth 由 auth 插件写入
238
+ if (!this?.auth)
239
+ throw new Error('请先登录 Koishi 控制台');
240
+ return issuePanelToken();
241
+ }, { authority: 4 });
242
+ }
238
243
  }
239
244
  catch (error) {
240
- logger.debug('[kkk] 注册面板 token RPC 失败: ' + String(error?.message ?? error));
245
+ logger.warn('[kkk] 注册面板 token RPC 失败: ' + String(error?.message ?? error));
241
246
  }
242
247
  const tokens = new Map();
243
248
  const issueToken = () => {
@@ -413,8 +418,32 @@ function registerWebUi({ ctx, config, rawConfig, logger, pluginRoot }) {
413
418
  ].join('');
414
419
  return true;
415
420
  };
421
+ /**
422
+ * 控制台下发的插件前端包(`/@plugin-<id>/index.js`)默认没有缓存头,
423
+ * 浏览器会拿旧的 bundle 一直用(改了客户端逻辑却看不到效果)。
424
+ * 这里给本插件自己的入口加 no-store —— qq-chat 也是这么处理的。
425
+ */
426
+ const isOwnEntry = (requestPath) => {
427
+ if (!requestPath.includes('/@plugin-'))
428
+ return false;
429
+ const entries = ctx.console?.entries ?? {};
430
+ for (const key of Object.keys(entries)) {
431
+ if (!requestPath.startsWith('/@plugin-' + key))
432
+ continue;
433
+ const files = entries[key]?.files;
434
+ const list = Array.isArray(files) ? files : [files?.dev, files?.prod];
435
+ if (list.some((file) => typeof file === 'string' && file.includes('koishi-plugin-kkk')))
436
+ return true;
437
+ }
438
+ return false;
439
+ };
416
440
  server.use(async (response, next) => {
417
441
  const requestPath = String(response.path || '');
442
+ if (isOwnEntry(requestPath)) {
443
+ await next();
444
+ response.set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
445
+ return;
446
+ }
418
447
  if (!requestPath.startsWith('/kkk/assets/'))
419
448
  return next();
420
449
  if (await pageDenied(response))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-kkk",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "抖音 / B站 / 快手 / 小红书 视频解析与动态推送,带 QQ 交互面板与弹幕烧录(karin-plugin-kkk 的 Koishi 移植版)",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
package/src/webui.ts CHANGED
@@ -236,13 +236,17 @@ export function registerWebUi ({ ctx, config, rawConfig, logger, pluginRoot }: W
236
236
 
237
237
  try {
238
238
  const consoleService: any = (ctx as any).console
239
- consoleService?.addListener?.('kkk/panel-token', function (this: any) {
240
- // this 是发起调用的控制台客户端,auth 由 auth 插件写入
241
- if (!this?.auth) throw new Error('请先登录 Koishi 控制台')
242
- return issuePanelToken()
243
- }, { authority: 4 })
239
+ if (typeof consoleService?.addListener !== 'function') {
240
+ logger.warn('[kkk] 控制台没有 addListener,面板 token RPC 未注册(面板将无法从控制台获取登录态)')
241
+ } else {
242
+ consoleService.addListener('kkk/panel-token', function (this: any) {
243
+ // this 是发起调用的控制台客户端,auth 由 auth 插件写入
244
+ if (!this?.auth) throw new Error('请先登录 Koishi 控制台')
245
+ return issuePanelToken()
246
+ }, { authority: 4 })
247
+ }
244
248
  } catch (error: any) {
245
- logger.debug('[kkk] 注册面板 token RPC 失败: ' + String(error?.message ?? error))
249
+ logger.warn('[kkk] 注册面板 token RPC 失败: ' + String(error?.message ?? error))
246
250
  }
247
251
 
248
252
  const tokens = new Map<string, number>()
@@ -423,8 +427,30 @@ export function registerWebUi ({ ctx, config, rawConfig, logger, pluginRoot }: W
423
427
  return true
424
428
  }
425
429
 
430
+ /**
431
+ * 控制台下发的插件前端包(`/@plugin-<id>/index.js`)默认没有缓存头,
432
+ * 浏览器会拿旧的 bundle 一直用(改了客户端逻辑却看不到效果)。
433
+ * 这里给本插件自己的入口加 no-store —— qq-chat 也是这么处理的。
434
+ */
435
+ const isOwnEntry = (requestPath: string): boolean => {
436
+ if (!requestPath.includes('/@plugin-')) return false
437
+ const entries: any = (ctx as any).console?.entries ?? {}
438
+ for (const key of Object.keys(entries)) {
439
+ if (!requestPath.startsWith('/@plugin-' + key)) continue
440
+ const files: any = entries[key]?.files
441
+ const list = Array.isArray(files) ? files : [files?.dev, files?.prod]
442
+ if (list.some((file: any) => typeof file === 'string' && file.includes('koishi-plugin-kkk'))) return true
443
+ }
444
+ return false
445
+ }
446
+
426
447
  server.use(async (response: any, next: any) => {
427
448
  const requestPath = String(response.path || '')
449
+ if (isOwnEntry(requestPath)) {
450
+ await next()
451
+ response.set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
452
+ return
453
+ }
428
454
  if (!requestPath.startsWith('/kkk/assets/')) return next()
429
455
  if (await pageDenied(response)) return
430
456
  const relative = decodeURIComponent(requestPath.slice('/kkk/assets/'.length))