chore(meta): 三个脚本统一青龙元数据注解,新增 _probe_meta.js 自检
继 3531437 修好 qqmusic 之后,把同一问题在另两个脚本上一并清掉 ——
它们原本都没写 name 注解(任务名会退回文件名),且 jd 脚本的 cron 写在块注释里
用了 */ 转义,青龙会把反斜杠一起提取出去。
改动(三个脚本统一成同一形态:文件最前面两行行注释):
// cron: <表达式>
// name: <中文任务名>
- jd/scrip_moving_env_enhance.js: 补 name 注解;cron 从块注释移到行注释
(原来写的 \*/ 转义会产出非法表达式)
- xiequ/scrip_switch.js: 补 name 注解;正文两处日志里的「name: xxx」改成「账号 xxx」
(青龙的任务名正则不锚定行首、取全文第一个命中,正文命中会盖掉注解)
- qqmusic/scrip_qqmusic_checkin.js: 注解从块注释改成行注释,与另两个统一
- 新增 _probe_meta.js: 扫描所有 scrip_*.js,复刻旧版(/name:\s*(.+)/ 不锚定)
与新版(cli/src/internal/subscription 的 cronMetadata)两条提取逻辑,
断言任务名不是代码片段、cron 是合法表达式。不带 scrip_ 前缀,订阅不会拉取
验证:_probe_meta.js RESULT: PASS(3 个文件,两个版本青龙提取都正确)/
三个脚本 node --check 全过 / qqmusic 签名对拍 6 组 PASS / 空 env 优雅跳过
This commit is contained in:
32
README.md
32
README.md
@@ -39,6 +39,38 @@
|
||||
ql repo https://gitea.zhuhjay.cn/zhuhjay/ql-scripts.git "scrip_" "" "sendNotify|common" "js"
|
||||
```
|
||||
|
||||
## 青龙自动建任务的元数据约定(必读)
|
||||
|
||||
订阅勾了「自动添加定时任务」时,青龙从**脚本正文**正则提取任务名与 cron。
|
||||
两条正则都**不锚定行首、取全文第一个命中**(2026-11-04 真机踩坑实录):
|
||||
|
||||
| 项 | 青龙的提取规则 | 必须怎么写 |
|
||||
|---|---|---|
|
||||
| **任务名** | `/name:\s*(.+)/` | 文件最前面写一行 `// name: 中文任务名`。**全文任何位置都不能再出现「name + 冒号」的连写** —— `https.request({ hostname: u.hostname })` 会被抓成任务名 `u.hostname,`(已复现) |
|
||||
| **cron** | `/\bcron:\s*([^\r\n]+)/`,**必须带冒号** | 写 `// cron: 1 0,6 * * *`,**独占一行、后面不跟任何注释**(同一行剩余文字会被一起吞进 schedule) |
|
||||
|
||||
两个都抓不到时的兜底:任务名 = 文件名;cron = 随机 `M H * * *`(这就是那次任务被建成 `18 21 * * *` 的原因)。
|
||||
|
||||
**自检**:
|
||||
|
||||
```bash
|
||||
node _probe_meta.js # 扫描所有 scrip_*.js,复刻两个版本青龙的正则,验证提取结果
|
||||
```
|
||||
|
||||
`_probe_meta.js` 是开发期工具,不带 `scrip_` 前缀,不会被订阅拉取。
|
||||
新增脚本后跑一次,全 PASS 再提交。
|
||||
|
||||
### 各脚本的注解写法
|
||||
|
||||
```js
|
||||
// cron: <表达式>
|
||||
// name: <中文任务名>
|
||||
/** 说明文字写这里,注意别写 "name:" 连写 */
|
||||
```
|
||||
|
||||
用**行注释**而不是块注释:块注释里 cron 的 `*/` 会提前闭合注释,必须写成 `*\/`,
|
||||
而青龙会把反斜杠一起提取出去导致 cron 非法。
|
||||
|
||||
## 脚本说明
|
||||
|
||||
### 使用方法
|
||||
|
||||
106
_probe_meta.js
Normal file
106
_probe_meta.js
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* _probe_meta.js —— 校验所有 scrip_*.js 的青龙元数据注解是否会被正确提取
|
||||
*
|
||||
* 青龙订阅勾「自动添加定时任务」时,从脚本正文正则提取任务名与 cron:
|
||||
* 任务名: /name:\s*(.+)/ 不锚定行首,取全文第一个命中
|
||||
* cron: /\bcron:\s*([^\r\n]+)/ 不锚定行首,取全文第一个命中
|
||||
* 两条都抓不到时:任务名 = 文件名,cron = 随机 M H * * *
|
||||
*
|
||||
* 用法: node _probe_meta.js
|
||||
* 退出码 0 = 全部通过,1 = 有文件不合格
|
||||
*/
|
||||
'use strict'
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const ROOT = __dirname
|
||||
|
||||
/** 递归找所有 scrip_*.js(跳过 node_modules / .git) */
|
||||
function findScripts(dir, out = []) {
|
||||
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
if (ent.name === 'node_modules' || ent.name === '.git') continue
|
||||
const p = path.join(dir, ent.name)
|
||||
if (ent.isDirectory()) findScripts(p, out)
|
||||
else if (/^scrip_.*\.js$/.test(ent.name)) out.push(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** 复刻青龙旧版提取(用户实际部署的版本,2026-11-04 真机证实) */
|
||||
function extractLegacy(contents, filename) {
|
||||
const name = contents.match(/name:\s*(.+)/)?.[1]?.trim()
|
||||
const cron = contents.match(/\bcron:\s*([^\r\n]+)/)?.[1]?.replace(/["',]+$/, '').trim()
|
||||
return { name, cron, fallbackName: path.basename(filename) }
|
||||
}
|
||||
|
||||
/** 复刻青龙新版 cli/src/internal/subscription/subscriptionRunner.ts 的 cronMetadata */
|
||||
function extractCurrent(contents, filename) {
|
||||
const name = contents.match(/new\s+Env\(\s*['"]([^'"\r\n]+)['"]/)?.[1]
|
||||
|| contents.match(/^\s*(?:#\s*)?name:\s*(.+)$/m)?.[1]?.trim()
|
||||
|| path.basename(filename)
|
||||
const explicit = contents.match(/\bcron:\s*([^\r\n]+)/)?.[1]?.replace(/["',]+$/, '').trim()
|
||||
|| contents.match(/\bcron\s+["']([^"']+)["']/)?.[1]
|
||||
const matching = contents.split('\n')
|
||||
.filter(item => item.includes(path.basename(filename)))
|
||||
.map(item => item.match(/(?:[\d*](?:[\d*/,-]*[\d*])?\s+){4,5}[\d*](?:[\d*/,-]*[\d*])?/)?.[0]?.trim())
|
||||
.filter(v => !!v)
|
||||
const cron = [...new Set(matching)].sort()[0] || explicit
|
||||
return { name, cron }
|
||||
}
|
||||
|
||||
/** cron 表达式合法性:5 或 6 段,每段只允许数字与 * , - / */
|
||||
const CRON_RE = /^[\d*][\d*,/-]*(\s+[\d*][\d*,/-]*){4,5}$/
|
||||
const isValidCron = (c) => typeof c === 'string' && CRON_RE.test(c.trim())
|
||||
|
||||
let fails = 0
|
||||
const scripts = findScripts(ROOT).sort()
|
||||
|
||||
console.log(`扫描到 ${scripts.length} 个 scrip_*.js\n`)
|
||||
|
||||
for (const file of scripts) {
|
||||
const rel = path.relative(ROOT, file)
|
||||
const contents = fs.readFileSync(file, 'utf8')
|
||||
const legacy = extractLegacy(contents, file)
|
||||
const current = extractCurrent(contents, file)
|
||||
|
||||
console.log(`── ${rel}`)
|
||||
console.log(` 旧版青龙 name = ${JSON.stringify(legacy.name)}`)
|
||||
console.log(` 旧版青龙 cron = ${JSON.stringify(legacy.cron)}`)
|
||||
console.log(` 新版青龙 name = ${JSON.stringify(current.name)}`)
|
||||
console.log(` 新版青龙 cron = ${JSON.stringify(current.cron)}`)
|
||||
|
||||
const problems = []
|
||||
const warns = []
|
||||
// 判据以**旧版青龙**为准:那是当前实际部署的版本(2026-11-04 真机证实 ——
|
||||
// 任务名被建成 "u.hostname," 只可能来自不锚定行首的 /name:\s*(.+)/)。
|
||||
if (!legacy.name) problems.push('旧版青龙抓不到任务名(会退回文件名)')
|
||||
if (!legacy.cron) problems.push('旧版青龙抓不到 cron(会落随机 fallback)')
|
||||
else if (!isValidCron(legacy.cron)) problems.push(`旧版青龙提取的 cron 非法: ${JSON.stringify(legacy.cron)}`)
|
||||
if (!current.cron) problems.push('新版青龙抓不到 cron(会落随机 fallback)')
|
||||
else if (!isValidCron(current.cron)) problems.push(`新版青龙提取的 cron 非法: ${JSON.stringify(current.cron)}`)
|
||||
// 新版青龙的任务名只在两种情况会退回文件名:脚本不用 common.js 的 Env()。
|
||||
// 那是**可接受的降级**(任务照样建、照样跑,只是名字是文件名),故只告警不判失败。
|
||||
if (current.name === path.basename(file)) {
|
||||
warns.push('新版青龙会退回文件名作任务名(本脚本不用 common.js 的 Env(),属可接受降级)')
|
||||
}
|
||||
|
||||
// 任务名不该是代码片段(含点、括号、分号、花括号等)。
|
||||
// 例外:恰好等于文件名的命中是新版青龙的**合法兜底**,不是代码片段误抓。
|
||||
for (const [label, n] of [['旧版', legacy.name], ['新版', current.name]]) {
|
||||
if (!n || n === path.basename(file)) continue
|
||||
if (/[{}();=\[\]]|\.\w/.test(n)) problems.push(`${label}青龙抓到的任务名像代码片段: ${JSON.stringify(n)}`)
|
||||
}
|
||||
|
||||
if (problems.length) {
|
||||
fails++
|
||||
problems.forEach(p => console.log(` ✗ ${p}`))
|
||||
} else {
|
||||
console.log(' ✓ 元数据提取正确')
|
||||
}
|
||||
warns.forEach(w => console.log(` ! ${w}`))
|
||||
console.log('')
|
||||
}
|
||||
|
||||
console.log(fails === 0 ? `RESULT: PASS(${scripts.length} 个文件)` : `RESULT: FAIL(${fails} 个文件不合格)`)
|
||||
process.exit(fails === 0 ? 0 : 1)
|
||||
@@ -1,10 +1,16 @@
|
||||
// cron: 59 23 */3 * *
|
||||
// name: 京东Cookie辅助脚本
|
||||
/**
|
||||
* cron: 59 23 *\/3 * *
|
||||
*
|
||||
* 京东Cookie辅助脚本(将JD CK进行重排序 保证所有账号都有助力与被助力的机会)
|
||||
*
|
||||
* 上面两行是青龙订阅自动建任务用的元数据注解,格式约定见仓库根 README:
|
||||
* - 必须用行注释写在文件最前面(行首),不要塞进块注释 —— 块注释里 cron 的
|
||||
* 「星号斜杠」会被迫转义,青龙会把反斜杠一起提取出去。
|
||||
* - 全文不要出现第二处「name 加冒号」的连写:青龙的提取正则不锚定行首,
|
||||
* 取的是全文第一个命中。
|
||||
*/
|
||||
const { Env } = require('../common');
|
||||
const $ = new Env('京东Cookie辅助脚本(将JD CK进行重排序)');
|
||||
const $ = new Env('京东Cookie辅助脚本');
|
||||
const fs = require('fs').promises;
|
||||
const fetch = require('node-fetch');
|
||||
const envName = 'JD_COOKIE';
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
// cron: 1 0,6 * * *
|
||||
// name: QQ音乐自动签到
|
||||
/**
|
||||
* name: QQ音乐自动签到
|
||||
* QQ音乐会员成长值 + 会员福利站 自动签到(青龙 Node 零依赖)
|
||||
*
|
||||
* scrip_qqmusic_checkin.js —— QQ音乐会员成长值 + 会员福利站 自动签到(青龙 Node 零依赖)
|
||||
* 上面两行是青龙订阅自动建任务用的元数据注解,格式约定见仓库根 README。
|
||||
* 定时说明: 00:01 + 06:02 双轮(任务20跨天滚动 + 福利站翻新兜底)。
|
||||
*
|
||||
* 文件名前缀 scrip_ 是订阅白名单要求(ql repo 的 whitelist 参数 = "scrip_"),
|
||||
* 不带前缀的文件不会被青龙订阅拉取。
|
||||
@@ -21,12 +24,6 @@
|
||||
* QM_CACHE_DIR key 缓存目录覆盖(默认规则见上)
|
||||
* QM_PLAY_REPORT 任务20播放上报开关(默认 off;伪造统计数据属中风险,
|
||||
* 且需播放 XML 模板,本版未实现,仅挂 StartTask 标记)
|
||||
*
|
||||
* cron: 1 0,6 * * *
|
||||
*
|
||||
* 定时说明: 00:01 + 06:02 双轮(任务20跨天滚动 + 福利站翻新兜底)。
|
||||
* cron 注解必须**独占一行且后面不跟注释** —— 青龙的提取正则
|
||||
* /\bcron:\s*([^\r\n]+)/ 会把同一行剩下的文字一起吞进去,产出的 schedule 非法。
|
||||
*/
|
||||
'use strict'
|
||||
const fs = require('fs')
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// cron: 38 2,8,14,20 * * *
|
||||
// name: 携趣白名单切换
|
||||
/**
|
||||
* cron: 38 2,8,14,20 * * *
|
||||
*
|
||||
* 携趣白名单切换(多个携趣,多个IP,多个IP选中一个合适的携趣进行切换)
|
||||
*
|
||||
* 上面两行是青龙订阅自动建任务用的元数据注解,格式约定见仓库根 README。
|
||||
* 本文件已把正文日志里的「name 加冒号」字样改成「账号」,避免青龙的提取正则
|
||||
* (不锚定行首、取全文第一个命中)抓到正文。
|
||||
*/
|
||||
const { Env } = require('../common');
|
||||
const $ = new Env('携趣小助手-白名单切换');
|
||||
@@ -62,7 +66,7 @@ const apiTpl = {
|
||||
for (let tmpElement of tmp) {
|
||||
if (tmpElement?.res) {
|
||||
await fetch(apiTpl.delAll(tmpElement.info));
|
||||
$.log(`删除 name: ${tmpElement.info.comment} 下的所有白名单 -> `, tmpElement.res);
|
||||
$.log(`删除账号 ${tmpElement.info.comment} 下的所有白名单 -> `, tmpElement.res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +77,7 @@ const apiTpl = {
|
||||
if (change) {
|
||||
for (const ip of ips) {
|
||||
await fetch(apiTpl.add(change.info, ip));
|
||||
$.log(`添加 name: ${change.info.comment} 白名单`);
|
||||
$.log(`添加账号 ${change.info.comment} 白名单`);
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
Reference in New Issue
Block a user