From 7518685893f04456cb35be890795f77711046511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=87=89=E8=8F=9C?= <1659488338@qq.com> Date: Fri, 1 Mar 2024 10:18:15 +0800 Subject: [PATCH] CI --- .changeset/README.md | 8 + .changeset/config.json | 11 + .gitattributes | 14 + .github/workflows/ci.yml | 41 +- .github/workflows/deploy_docs.yml | 4 +- .github/workflows/release.yml | 15 - .gitignore | 82 +- .npmrc | 5 +- core/package.json | 2 +- core/src/message.ts | 176 ++-- package.json | 10 +- packages/adapters/dingtalk/package.json | 6 +- packages/adapters/discord/package.json | 6 +- packages/adapters/icqq/package.json | 6 +- packages/adapters/onebot-11/package.json | 10 +- packages/adapters/onebot-11/src/message.ts | 8 +- packages/adapters/onebot-11/src/onebot.ts | 17 +- packages/adapters/onebot-12/package.json | 29 + packages/adapters/onebot-12/src/index.ts | 69 ++ packages/adapters/onebot-12/src/message.ts | 106 ++ packages/adapters/onebot-12/src/onebot.ts | 352 +++++++ packages/adapters/onebot-12/src/types.ts | 129 +++ packages/adapters/onebot-12/tsconfig.json | 12 + packages/adapters/qq/package.json | 6 +- packages/adapters/wechat/package.json | 6 +- packages/plugins/client/package.json | 10 +- packages/plugins/game/package.json | 7 +- packages/plugins/groupManage/package.json | 7 +- packages/plugins/groupManage/src/index.ts | 57 +- packages/plugins/guildManage/package.json | 7 +- packages/plugins/qa/package.json | 5 +- packages/plugins/schedule/package.json | 5 +- packages/services/drawer/package.json | 6 +- packages/services/http-server/package.json | 6 +- packages/services/sandbox/package.json | 39 +- packages/services/upyun/package.json | 5 +- rush.json | 412 ++++++++ scripts/build.js | 11 - scripts/publish.js | 43 - test/CHANGELOG.md | 29 + test/package.json | 39 +- test/plugins/reg-explain.js | 1029 ++++++++++++++++++++ test/plugins/test.ts | 86 +- test/src/App.vue | 8 +- 44 files changed, 2631 insertions(+), 310 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .gitattributes create mode 100644 packages/adapters/onebot-12/package.json create mode 100644 packages/adapters/onebot-12/src/index.ts create mode 100644 packages/adapters/onebot-12/src/message.ts create mode 100644 packages/adapters/onebot-12/src/onebot.ts create mode 100644 packages/adapters/onebot-12/src/types.ts create mode 100644 packages/adapters/onebot-12/tsconfig.json create mode 100644 rush.json delete mode 100644 scripts/build.js delete mode 100644 scripts/publish.js create mode 100644 test/CHANGELOG.md create mode 100644 test/plugins/reg-explain.js diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..e5b6d8d6a --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..91b6a9512 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..79a85db5c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Don't allow people to merge changes to these generated files, because the result +# may be invalid. You need to run "rush update" again. +pnpm-lock.yaml merge=text +shrinkwrap.yaml merge=binary +npm-shrinkwrap.json merge=binary +yarn.lock merge=binary + +# Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic +# syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor +# may also require a special configuration to allow comments in JSON. +# +# For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088 +# +*.json linguist-language=JSON-with-Comments diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e90313ee..0268f9087 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,41 @@ -name: CI +name: Changesets on: - - push + push: + branches: + - main +env: + CI: true + PNPM_CACHE_FOLDER: .pnpm-store jobs: - codeCov: + version: + timeout-minutes: 15 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: checkout code repository + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 \ No newline at end of file + - name: setup node.js + uses: actions/setup-node@v4 + with: + node-version: 16.x + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: latest + - name: setup pnpm config + run: pnpm config set store-dir $PNPM_CACHE_FOLDER + - name: install dependencies + run: pnpm install + env: + GITHUB_AUTH_TOKEN: ${{secrets.PERSONAL_TOKEN}} + - name: create and publish versions + uses: changesets/action@v1 + with: + version: pnpm ci:version + commit: "chore: update versions" + title: "chore: update versions" + publish: pnpm ci:publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index a439e8ddc..c41448383 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -1,4 +1,4 @@ -name: CI Deploy Docs +name: new_ci.yml Deploy Docs on: workflow_dispatch: {} push: @@ -26,7 +26,7 @@ jobs: - name: Install run: npm install env: - NODE_AUTH_TOKEN: ${{secrets.PERSONAL_TOKEN}} + GITHUB_AUTH_TOKEN: ${{secrets.PERSONAL_TOKEN}} - name: Build run: npm run docs:build - name: Upload artifact diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c2b5d22e..8905426dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,23 +22,8 @@ jobs: uses: pnpm/action-setup@v3 with: version: latest - - name: Bump version - run: npm run bump - uses: google-github-actions/release-please-action@v4 id: release with: token: ${{ secrets.GITHUB_TOKEN }} release-type: node - - name: Install Dependencies - run: pnpm install - if: ${{ steps.release.outputs.release_created }} - env: - NODE_AUTH_TOKEN: ${{secrets.PERSONAL_TOKEN}} - - name: Build - run: pnpm run build - if: ${{ steps.release.outputs.release_created }} - - name: Publish - run: pnpm publish -r --access public --no-git-checks - if: ${{ steps.release.outputs.release_created }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.gitignore b/.gitignore index 93395a691..9c1203a70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,84 @@ -/**/*/lib -/node_modules -/.idea -/**/*/node_modules +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# OS X temporary files +.DS_Store + +# IntelliJ IDEA project files; if you want to commit IntelliJ settings, this recipe may be helpful: +# https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +.idea/ +*.iml + +# Rush temporary files +common/deploy/ +common/temp/ +common/autoinstallers/*/.npmrc +**/.rush/temp/ +*.lock + +# Heft temporary files +.cache +.heft +# project test files + package-lock.json /test/.*.env /test/*.yaml test/*.config.js test/*.config.ts /test/data + diff --git a/.npmrc b/.npmrc index 9771ede3b..3afb7d20a 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,6 @@ -//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} +//npm.pkg.github.com/:_authToken=${GITHUB_AUTH_TOKEN} @icqqjs:registry=https://npm.pkg.github.com always-auth=true +registry=https://registry.npmmirror.com/ + diff --git a/core/package.json b/core/package.json index e71feb100..046b63ddc 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "zhin", - "version": "2.4.6", + "version": "3.0.0", "main": "lib/index.js", "bin": "./bin.js", "scripts": { diff --git a/core/src/message.ts b/core/src/message.ts index b83dd891e..73445b828 100644 --- a/core/src/message.ts +++ b/core/src/message.ts @@ -1,94 +1,100 @@ import { Bot, Dict } from '@/types'; import { Prompt } from '@/prompt'; -import { Adapter, AdapterBot, AdapterReceive } from '@/adapter'; -export interface MessageBase{ - from_id:string - group_id?:string|number - guild_id?:string|number - channel_id?:string|number - discuss_id?:string|number - sender:MessageSender - raw_message:string - quote?:{ - message_id:string - message?:string - } - message_type:Message.Type +import { Adapter, AdapterReceive } from '@/adapter'; +export interface MessageBase { + from_id: string; + group_id?: string | number; + guild_id?: string | number; + channel_id?: string | number; + discuss_id?: string | number; + sender: MessageSender; + raw_message: string; + quote?: { + message_id: string; + message?: string; + }; + message_type: Message.Type; } -export interface Message extends MessageBase{ - prompt:Prompt - reply(message:string):Promise +export interface Message extends MessageBase { + prompt: Prompt; + reply(message: string): Promise; } export class Message { - constructor(public adapter:AD,public bot:Bot,public original?:AdapterReceive) { - } - async reply(message:string,quote:boolean=true){ - return this.adapter.sendMsg(this.bot.unique_id,this.from_id,this.message_type,message,quote?this:undefined) - } - toJSON():MessageBase{ - return { - from_id:this.from_id, - sender:this.sender, - raw_message:this.raw_message, - message_type:this.message_type, - } - } + constructor( + public adapter: AD, + public bot: Bot, + public original?: AdapterReceive, + ) {} + async reply(message: string, quote: boolean = true) { + return this.adapter.sendMsg(this.bot.unique_id, this.from_id, this.message_type, message, quote ? this : undefined); + } + toJSON(): MessageBase { + return { + from_id: this.from_id, + sender: this.sender, + raw_message: this.raw_message, + message_type: this.message_type, + }; + } } -const wrapKV=Object.entries({ - ',':'_🤤_🤖_', - '&':'$amp;', - '<':'<', - '>':'>' -}).map(([key,value])=>({key,value})) -export function wrap(message:string){ - for(const {key,value} of wrapKV){ - message=message.replace(new RegExp(key,'g'),value) - } - return message +const wrapKV = Object.entries({ + ',': '_🤤_🤖_', + '&': '$amp;', + '<': '<', + '>': '>', +}).map(([key, value]) => ({ key, value })); +export function wrap(message: string) { + if (!message) return; + for (const { key, value } of wrapKV) { + message = message.replace(new RegExp(key, 'g'), value); + } + return message; } -export function unwrap(message:string){ - for(const {key,value} of wrapKV){ - message=message.replace(new RegExp(value,'g'),key) - } - return message +export function unwrap(message: string) { + for (const { key, value } of wrapKV) { + message = message.replace(new RegExp(value, 'g'), key); + } + return message; } -export namespace Message{ - export type Render=(template:string,message?:T)=>Promise|string - export type Segment=`<${string},${string}>`|string - export type DefineSegment={ - (type:string,data:Dict):string - text(text:string):string - face(id:number):string - image(url:string):string - at(user_id:string|number):string - } - export type Type = 'private'|'group'|'guild'|'direct'; - export function fromEvent(adapter:AD,bot:Bot,message:AdapterReceive){ - const result= new Message(adapter,bot,message) - result.prompt=new Prompt(adapter,bot,result) - return result - } - export function fromJSON(adapter:AD,bot:Bot,json:MessageBase){ - const result= new Message(adapter,bot) - result.from_id=json.from_id - result.sender=json.sender - result.message_type=json.message_type - result.raw_message=json.raw_message - result.prompt=new Prompt(adapter,bot,result) - return result - } -} -export const segment:Message.DefineSegment=function(type, data){ - return `<${type},${Object.entries(data).map(([key,value])=>{ - return `${key}=${wrap(JSON.stringify(value))}` - }).join()}>` -} as Message.DefineSegment -segment.text=(text)=>text -segment.face=(id:number)=>`` -segment.image=(file:string)=>`` -segment.at=(user_id)=>`` -type MessageSender={ - user_id?:string|number - user_name?:string - permissions?:string[] +export namespace Message { + export type Render = (template: string, message?: T) => Promise | string; + export type Segment = `<${string},${string}>` | string; + export type DefineSegment = { + (type: string, data: Dict): string; + text(text: string): string; + face(id: number): string; + image(url: string): string; + at(user_id: string | number): string; + }; + export type Type = 'private' | 'group' | 'guild' | 'direct'; + export function fromEvent(adapter: AD, bot: Bot, message: AdapterReceive) { + const result = new Message(adapter, bot, message); + result.prompt = new Prompt(adapter, bot, result); + return result; + } + export function fromJSON(adapter: AD, bot: Bot, json: MessageBase) { + const result = new Message(adapter, bot); + result.from_id = json.from_id; + result.sender = json.sender; + result.message_type = json.message_type; + result.raw_message = json.raw_message; + result.prompt = new Prompt(adapter, bot, result); + return result; + } } +export const segment: Message.DefineSegment = function (type, data) { + return `<${type},${Object.entries(data) + .map(([key, value]) => { + return `${key}=${wrap(JSON.stringify(value))}`; + }) + .join()}>`; +} as Message.DefineSegment; +segment.text = text => text; +segment.face = (id: number) => ``; +segment.image = (file: string) => ``; +segment.at = user_id => ``; +type MessageSender = { + user_id?: string | number; + user_name?: string; + permissions?: string[]; +}; diff --git a/package.json b/package.json index 100a0d69e..3fa35c06f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "zhin-workspace", + "private": true, "version": "0.0.1", "description": "zhin机器人开发框架", "repository": { @@ -15,7 +16,7 @@ "format": "prettier packages --write", "build": "node ./scripts/build.js", "clean": "node ./scripts/clean.js", - "pub": "node ./scripts/publish.js", + "ci:publish": "pnpm publish -r", "bump": "bumpp packages/**/package.json", "docs:dev": "vitepress dev docs --port 5566", "docs:build": "vitepress build docs", @@ -33,6 +34,7 @@ "author": "凉菜", "license": "MIT", "devDependencies": { + "@changesets/cli": "^2.27.1", "@commitlint/cli": "^18.2.0", "@commitlint/config-conventional": "^18.1.0", "@types/node": "^20.5.9", @@ -43,15 +45,13 @@ "eslint-plugin-prettier": "latest", "husky": "^8.0.3", "latest-version": "^5.0.0", + "less": "^4.2.0", "prettier": "^3.1.0", "rimraf": "^5.0.5", "semver": "^7.5.4", "tsc-alias": "^1.8.8", "typescript": "^5.2.2", "vitepress": "1.0.0-rc.39", - "vue": "^3.4.15", - "less": "^4.2.0" - }, - "dependencies": { + "vue": "^3.4.15" } } diff --git a/packages/adapters/dingtalk/package.json b/packages/adapters/dingtalk/package.json index 4cbd5d229..ecfd8f38f 100644 --- a/packages/adapters/dingtalk/package.json +++ b/packages/adapters/dingtalk/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/dingtalk", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.5", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -15,11 +15,11 @@ "directory": "packages/adapters/dingtalk" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "node-dd-bot": "latest" }, "peerDependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "node-dd-bot": "latest" } } diff --git a/packages/adapters/discord/package.json b/packages/adapters/discord/package.json index 769f03d14..68167038f 100644 --- a/packages/adapters/discord/package.json +++ b/packages/adapters/discord/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/discord", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.6", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -15,11 +15,11 @@ "directory": "packages/adapters/discord" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "ts-disc-bot": "latest" }, "devDependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "ts-disc-bot": "latest", "@types/node": "latest", "@types/ws": "latest" diff --git a/packages/adapters/icqq/package.json b/packages/adapters/icqq/package.json index 70af41c1b..50f1d0023 100644 --- a/packages/adapters/icqq/package.json +++ b/packages/adapters/icqq/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/icqq", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.13", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -15,11 +15,11 @@ "directory": "packages/adapters/icqq" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "icqq": "latest" }, "peerDependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "icqq": "latest" } } diff --git a/packages/adapters/onebot-11/package.json b/packages/adapters/onebot-11/package.json index fb50254f6..9c4032c57 100644 --- a/packages/adapters/onebot-11/package.json +++ b/packages/adapters/onebot-11/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/onebot-11", "main": "./lib/index.js", "types": "./lib/index.d.ts", - "version": "0.0.12", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -15,13 +15,13 @@ "directory": "packages/adapters/onebot-v11" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "ws": "latest", - "@zhinjs/plugin-http-server": "workspace:latest" + "@zhinjs/plugin-http-server": "workspace:^" }, "peerDependencies": { - "zhin": "workspace:latest", - "@zhinjs/plugin-http-server": "workspace:latest" + "zhin": "workspace:^", + "@zhinjs/plugin-http-server": "workspace:^" }, "devDependencies": { "@types/ws": "latest" diff --git a/packages/adapters/onebot-11/src/message.ts b/packages/adapters/onebot-11/src/message.ts index 3fd76b672..c80646f9c 100644 --- a/packages/adapters/onebot-11/src/message.ts +++ b/packages/adapters/onebot-11/src/message.ts @@ -25,7 +25,7 @@ export namespace MessageV11 { const { type, data } = item; if (type === 'text') result += data.text || ''; else - result += `[CQ:${type},${Object.entries(data) + result += `[CQ:${type},${Object.entries(data || {}) .map(([key, value]) => `${key}=${value}`) .join(',')}]`; } @@ -46,7 +46,11 @@ export namespace MessageV11 { const data = Object.fromEntries( attrs.map(attrStr => { const [key, ...valueArr] = attrStr.split('='); - return [key, JSON.parse(unwrap(valueArr.join('=')))]; + try { + return [key, JSON.parse(unwrap(valueArr.join('=')))]; + } catch { + return [key, valueArr.join('=')]; + } }), ); result.push({ type, data }); diff --git a/packages/adapters/onebot-11/src/onebot.ts b/packages/adapters/onebot-11/src/onebot.ts index 8b2556f2f..364ed1aad 100644 --- a/packages/adapters/onebot-11/src/onebot.ts +++ b/packages/adapters/onebot-11/src/onebot.ts @@ -1,10 +1,11 @@ import { EventEmitter } from 'events'; -import { WebSocketServer, WebSocket, MessageEvent, VerifyClientCallbackAsync } from 'ws'; +import { WebSocketServer, WebSocket, MessageEvent } from 'ws'; import { MessageV11 } from '@/message'; import { OneBotMethodsV11 } from '@/types'; import { Router } from '@zhinjs/plugin-http-server'; import { OneBotV11Adapter } from '@/index'; import { Dict } from 'zhin'; +import { IncomingMessage } from 'http'; export class OneBotV11 extends EventEmitter { constructor( @@ -51,10 +52,10 @@ export class OneBotV11 extends EventEmitter { }; Object.entries(config).map(([key, path]) => { const server = this.router.ws(path, { - verifyClient: (...args: Parameters) => { + verifyClient: (info: { origin: string; secure: boolean; req: IncomingMessage }) => { const { req: { headers }, - } = args[0]; + } = info; const authorization = headers['authorization'] || ''; if (this.config.access_token && authorization !== `Bearer ${this.config.access_token}`) { this.adapter.logger.error('鉴权失败'); @@ -63,9 +64,15 @@ export class OneBotV11 extends EventEmitter { return true; }, }); - if (['path', 'event_path'].includes(path)) + if (['path', 'event_path'].includes(key)) server.on('connection', (ws, req) => { - this.adapter.logger.mark('new connect from: ', req.socket.remoteAddress); + this.adapter.logger.info(`已连接到协议端:${req.socket.remoteAddress}`); + ws.on('error', err => { + this.adapter.logger.error('连接出错:', err); + }); + ws.on('close', code => { + this.adapter.logger.error(`与连接端(${req.socket.remoteAddress})断开,错误码:${code}`); + }); ws.on('message', this.dispatch); }); this.adapter.logger.mark(`ws server is start at route path: ${path}`); diff --git a/packages/adapters/onebot-12/package.json b/packages/adapters/onebot-12/package.json new file mode 100644 index 000000000..e1e5cf0d9 --- /dev/null +++ b/packages/adapters/onebot-12/package.json @@ -0,0 +1,29 @@ +{ + "name": "@zhinjs/onebot-12", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "version": "1.0.0", + "scripts": { + "build": "tsc && tsc-alias" + }, + "files": [ + "lib" + ], + "author": "凉菜", + "repository": { + "url": "https://github.com/zhinjs/zhin", + "directory": "packages/adapters/onebot-v12" + }, + "dependencies": { + "zhin": "workspace:^", + "ws": "latest", + "@zhinjs/plugin-http-server": "workspace:^" + }, + "peerDependencies": { + "zhin": "workspace:^", + "@zhinjs/plugin-http-server": "workspace:^" + }, + "devDependencies": { + "@types/ws": "latest" + } +} diff --git a/packages/adapters/onebot-12/src/index.ts b/packages/adapters/onebot-12/src/index.ts new file mode 100644 index 000000000..4f105ae72 --- /dev/null +++ b/packages/adapters/onebot-12/src/index.ts @@ -0,0 +1,69 @@ +import { Adapter, Message } from 'zhin'; +import '@zhinjs/plugin-http-server'; +import { OneBotV12 } from '@/onebot'; +import { MessageV12 } from '@/message'; +export type OneBotV12Adapter = typeof oneBotV12; +const oneBotV12 = new Adapter, MessageV12>('onebot-12'); +declare module 'zhin' { + namespace App { + interface Adapters { + 'onebot-12': OneBotV12.Config; + } + } +} +oneBotV12.define('sendMsg', async (bot_id, target_id, target_type, message, source) => { + const bot = oneBotV12.pick(bot_id); + let msg: MessageV12.Sendable = await oneBotV12.app!.renderMessage(message as string, source); + msg = MessageV12.formatSegments(msg); + switch (target_type) { + case 'group': + return bot.sendGroupMsg(target_id, msg, source?.original?.message_id); + case 'private': + return bot.sendPrivateMsg(target_id, msg, source?.original?.message_id); + default: + throw new Error(`OneBotV12适配器暂不支持发送${target_type}类型的消息`); + } +}); +const initBot = (configs: OneBotV12.Config[]) => { + if (!oneBotV12.app?.server) + throw new Error('“oneBot V12 miss require service “http”, maybe you need install “ @zhinjs/plugin-http-server ”'); + + for (const config of configs) { + const bot = new OneBotV12(oneBotV12, config, oneBotV12.app!.router); + Object.defineProperty(bot, 'unique_id', { + value: `OneBotV12:${configs.indexOf(config) + 1}`, + writable: false, + }); + oneBotV12.bots.push(bot as Adapter.Bot); + } + oneBotV12.on('start', startBots); + oneBotV12.on('stop', stopBots); +}; +const messageHandler = (bot: Adapter.Bot, event: MessageV12) => { + const message = Message.fromEvent(oneBotV12, bot, event); + message.raw_message = MessageV12.formatToString(event.message); + message.message_type = event.message_type; + message.from_id = event.message_type === 'private' ? event.user_id + '' : event.group_id + ''; + message.sender = { + user_id: event.user_id, + user_name: event.nickname || '', + }; + oneBotV12.app!.emit('message', oneBotV12, bot, message); +}; +const startBots = () => { + for (const bot of oneBotV12.bots) { + bot.on('message', messageHandler.bind(global, bot)); + bot.start(); + } +}; +const stopBots = () => { + for (const bot of oneBotV12.bots) { + bot.stop(); + } +}; +oneBotV12.on('mounted', initBot); + +export default oneBotV12; +export namespace OneBotV12Adapter { + export type Config = OneBotV12.Config[]; +} diff --git a/packages/adapters/onebot-12/src/message.ts b/packages/adapters/onebot-12/src/message.ts new file mode 100644 index 000000000..92c961ab3 --- /dev/null +++ b/packages/adapters/onebot-12/src/message.ts @@ -0,0 +1,106 @@ +import { Dict, unwrap } from 'zhin'; + +export interface MessageV12 { + raw_message: string; + user_id: number; + message_id: string; + nickname?: string; + group_id: number; + message_type: 'group' | 'private'; + message: string | (MessageV12.Segment | string)[]; +} +export namespace MessageV12 { + export type Segment = { + type: string; + data: Dict; + }; + export type Ret = { + message_id: number; + }; + export type Sendable = string | Segment | (string | Segment)[]; + export function parseSegmentsFromTemplate(template: string): Segment[] { + const result: Segment[] = []; + const reg = /(<[^>]+>)/; + while (template.length) { + const [match] = template.match(reg) || []; + if (!match) break; + const index = template.indexOf(match); + const prevText = template.slice(0, index); + if (prevText) result.push({ type: 'text', data: { text: prevText } }); + template = template.slice(index + match.length); + const [type, ...attrs] = match.slice(1, -1).split(','); + const data = Object.fromEntries( + attrs.map(attrStr => { + const [key, ...valueArr] = attrStr.split('='); + try { + return [key, JSON.parse(unwrap(valueArr.join('=')))]; + } catch { + return [key, valueArr.join('=')]; + } + }), + ); + result.push({ type, data }); + } + if (template.length) result.push({ type: 'text', data: { text: template } }); + return result; + } + + export function parseSegmentsFromCqCode(template: string): Segment[] { + const result: Segment[] = []; + const reg = /(\[CQ:[!\]]+])/; + while (template.length) { + const [match] = template.match(reg) || []; + if (!match) break; + const index = template.indexOf(match); + const prevText = template.slice(0, index); + if (prevText) result.push({ type: 'text', data: { text: prevText } }); + template = template.slice(index + match.length); + const [typeWithPrefix, ...attrs] = match.slice(1, -1).split(','); + const type = typeWithPrefix.replace('CQ:', ''); + const data = Object.fromEntries( + attrs.map(attrStr => { + const [key, ...valueArr] = attrStr.split('='); + return [key, valueArr.join('=')]; + }), + ); + result.push({ + type, + data, + }); + } + if (template.length) { + result.push({ + type: 'text', + data: { + text: template, + }, + }); + } + return result; + } + + export function formatSegments(message: Sendable): Segment[] { + const result: Segment[] = []; + if (!Array.isArray(message)) message = [message]; + for (const item of message) { + if (typeof item === 'string') result.push(...parseSegmentsFromTemplate(item)); + else result.push(item); + } + return result; + } + + export function formatToString(message: string | (Segment | string)[]) { + if (typeof message === 'string') return formatToString(parseSegmentsFromCqCode(message)); + let result: string = ''; + for (let item of message) { + if (typeof item == 'string') item = { type: 'text', data: { text: item } }; + const { type, data } = item; + if (type === 'text') result += data.text || ''; + else + result += `<${type},${Object.entries(data) + .map(([key, value]) => `${key}=${JSON.stringify(value).replace(/,/g, '_🤤_🤖_')}`) + .join(',')}>`; + } + return result; + } +} diff --git a/packages/adapters/onebot-12/src/onebot.ts b/packages/adapters/onebot-12/src/onebot.ts new file mode 100644 index 000000000..67ac3006f --- /dev/null +++ b/packages/adapters/onebot-12/src/onebot.ts @@ -0,0 +1,352 @@ +import { EventEmitter } from 'events'; +import { WebSocketServer, WebSocket, MessageEvent } from 'ws'; +import { MessageV12 } from '@/message'; +import { OneBotMethodsV12 } from '@/types'; +import { Router } from '@zhinjs/plugin-http-server'; +import { OneBotV12Adapter } from '@/index'; +import { Dict } from 'zhin'; +import { IncomingMessage } from 'http'; + +export class OneBotV12 extends EventEmitter { + constructor( + private adapter: OneBotV12Adapter, + public config: OneBotV12.Config, + private router: Router, + ) { + super(); + this.dispatch = this.dispatch.bind(this); + } + get app() { + return this.adapter.app; + } + reTryCount = 0; + ws?: WebSocket; + wss: Map = new Map(); + + async start() { + switch (this.config.type) { + case 'ws': + return this.connectWs(this.config as OneBotV12.Config<'ws'>); + case 'ws_reverse': + return this.startWsServer(this.config as OneBotV12.Config<'ws_reverse'>); + default: + throw new Error(`unsupported type:${this.config.type}, supported types:'ws', 'ws_reverse'`); + } + } + + private dispatch(message: MessageEvent) { + const result: OneBotV12.EventPayload | OneBotV12.ApiResult = JSON.parse(message?.toString() || 'null'); + if (!result) return; + if (result.retcode !== undefined && result.echo) return this.emit('echo', result.echo, result.data); + const event: OneBotV12.EventPayload = result as OneBotV12.EventPayload; + this.adapter.logger.debug('receive event', event); + if (event.post_type === 'message') { + this.adapter.logger.info(`recv [${event.message_type} ${event.group_id || event.user_id}]: ${event.raw_message}`); + } + this.emit(event.post_type, event); + } + + private startWsServer(cfg: OneBotV12.Config<'ws_reverse'>) { + const config: Dict = { + path: `${cfg.prefix || '/onebot/v12'}`, + api_path: `${cfg.prefix || '/onebot/v12'}/api`, + event_path: `${cfg.prefix || '/onebot/v12'}/event`, + }; + Object.entries(config).map(([key, path]) => { + const server = this.router.ws(path, { + verifyClient: (info: { origin: string; secure: boolean; req: IncomingMessage }) => { + const { + req: { headers }, + } = info; + if (!headers['sec-websocket-protocol']?.startsWith('12')) { + this.adapter.logger.error('连接的协议不是有效的OneBotV12协议'); + } + const authorization = headers['authorization'] || ''; + if (this.config.access_token && authorization !== `Bearer ${this.config.access_token}`) { + this.adapter.logger.error('鉴权失败'); + return false; + } + return true; + }, + handleProtocols(protocols, req) { + return [...protocols][0] || false; + }, + }); + if (['path', 'event_path'].includes(key)) + server.on('connection', (ws, req) => { + this.adapter.logger.info(`已连接到协议端:${req.socket.remoteAddress}`); + ws.on('error', err => { + this.adapter.logger.error('连接出错:', err); + }); + ws.on('close', code => { + this.adapter.logger.error(`与连接端(${req.socket.remoteAddress})断开,错误码:${code}`); + }); + ws.on('message', this.dispatch); + }); + this.adapter.logger.mark(`ws server is start at route path: ${path}`); + this.wss.set(key, server); + }); + } + + private connectWs(cfg: OneBotV12.Config<'ws'>) { + const config: Required = { + url: cfg.url || 'ws://127.0.0.1:6700', + max_reconnect_count: (cfg.max_reconnect_count ||= 10), + reconnect_interval: (cfg.reconnect_interval ||= 3000), + }; + this.ws = new WebSocket(config.url, { + headers: { + Authorization: `Bearer ${cfg.access_token}`, + }, + }); + this.ws.on('open', () => { + this.adapter.logger.mark(`connected to ${config.url}`); + this.reTryCount = 0; + }); + this.ws.on('message', this.dispatch); + this.ws.on('error', e => { + this.adapter.logger.error(e?.message); + }); + this.ws.on('close', () => { + if (this.reTryCount < config.max_reconnect_count) { + this.adapter.logger.mark(`reconnect after ${config.reconnect_interval} ms`); + setTimeout(() => { + this.reTryCount++; + this.connectWs(cfg); + }, config.reconnect_interval); + } else { + this.adapter.logger.mark(`retry times is exceeded of ${config.max_reconnect_count}`); + } + }); + } + + async stop() { + this.ws?.close(); + for (const [_, server] of this.wss) { + server.close(); + } + } + + sendPayload(payload: { + action: T; + params: Parameters[0]; + echo?: number | string; + }): Promise> { + return new Promise>((resolve, reject) => { + payload.echo = payload.echo || Date.now(); + const timer = setTimeout( + () => { + this.off('echo', receiveHandler); + reject('timeout'); + }, + this.config.timeout || 1000 * 30, + ); + const receiveHandler = (resultEcho: string | number, result: Dict) => { + if (resultEcho === payload.echo) { + clearTimeout(timer); + this.off('echo', receiveHandler); + resolve(result as any); + } + }; + this.on('echo', receiveHandler); + this.adapter.logger.debug('send payload', payload); + if (this.config.type === 'ws') return this.ws!.send(JSON.stringify(payload)); + for (const [name, server] of this.wss) { + if (name === 'event_path') continue; + for (const ws of server.clients) { + ws.send(JSON.stringify(payload)); + } + } + }); + } + + async sendPrivateMsg(user_id: string, message: MessageV12.Sendable, message_id?: string) { + this.adapter.logger.info(`send [Private ${user_id}]: ${this.getBrief(message)}`); + const result = await this.sendPayload({ + action: 'send_private_msg', + params: { user_id, message, message_id }, + }); + if (!result.message_id) return this.adapter.logger.error(`send failed:`, result); + return result.message_id; + } + getGroupList() { + return this.sendPayload({ + action: 'get_group_list', + params: {}, + }); + } + getGroupInfo(group_id: string) { + return this.sendPayload({ + action: 'get_group_info', + params: { group_id }, + }); + } + getFriendList() { + return this.sendPayload({ + action: 'get_friend_list', + params: {}, + }); + } + sendLike(user_id: string, times = 1) { + return this.sendPayload({ + action: 'send_like', + params: { user_id, times }, + }); + } + getStrangerInfo(user_id: string) { + return this.sendPayload({ + action: 'get_stranger_info', + params: { user_id }, + }); + } + getGroupMemberList(group_id: string) { + return this.sendPayload({ + action: 'get_group_member_list', + params: { group_id }, + }); + } + setEssenceMessage(message_id: string) { + return this.sendPayload({ + action: 'set_essence_message', + params: { message_id }, + }); + } + removeEssenceMessage(message_id: string) { + return this.sendPayload({ + action: 'remove_essence_message', + params: { message_id }, + }); + } + setGroupBan(group_id: string, user_id: string, duration?: number) { + return this.sendPayload({ + action: 'set_group_ban', + params: { group_id, user_id, duration }, + }); + } + getGroupMemberInfo(group_id: string, user_id: string) { + return this.sendPayload({ + action: 'get_group_member_info', + params: { + group_id, + user_id, + }, + }); + } + setGroupKick(group_id: string, user_id: string, reject_add_request?: boolean) { + return this.sendPayload({ + action: 'set_group_kick', + params: { + group_id, + user_id, + reject_add_request, + }, + }); + } + setGroupAdmin(group_id: string, user_id: string, enable?: boolean) { + return this.sendPayload({ + action: 'set_group_admin', + params: { group_id, user_id, enable }, + }); + } + setGroupSpecialTitle(group_id: string, user_id: string, special_title?: string, duration?: number) { + return this.sendPayload({ + action: 'set_group_special_title', + params: { group_id, user_id, special_title, duration }, + }); + } + sendGroupNotice(group_id: string, content: string) { + return this.sendPayload({ + action: 'send_group_notice', + params: { group_id, content }, + }); + } + setGroupAnonymous(group_id: string, enable?: boolean) { + return this.sendPayload({ + action: 'set_group_anonymous', + params: { group_id, enable }, + }); + } + setGroupCard(group_id: string, user_id: string, card?: string) { + return this.sendPayload({ + action: 'set_group_card', + params: { group_id, user_id, card }, + }); + } + setGroupName(group_id: string, group_name: string) { + return this.sendPayload({ + action: 'set_group_name', + params: { group_id, group_name }, + }); + } + sendGroupPoke(group_id: string, user_id: string) { + return this.sendPayload({ + action: 'send_group_poke', + params: { group_id, user_id }, + }); + } + async sendGroupMsg(group_id: string, message: MessageV12.Sendable, message_id?: string) { + this.adapter.logger.info(`send [Group ${group_id}]: ${this.getBrief(message)}`); + const result = await this.sendPayload({ + action: 'send_group_msg', + params: { group_id, message, message_id }, + }); + if (!result.message_id) return this.adapter.logger.error(`send failed:`, result); + return result.message_id; + } + getBrief(message: MessageV12.Sendable): string { + if (typeof message === 'string') { + return message; + } + if (Array.isArray(message)) { + return message.map(m => this.getBrief(m)).join(''); + } + if (message.type === 'text') { + return message.data.text; + } + return `{${message.type},${Object.keys(message.data).join(',')}}`; + } +} + +export namespace OneBotV12 { + type WsConfig = { + url?: string; + max_reconnect_count?: number; + reconnect_interval?: number; + }; + export type ApiResult = { + status: 'ok' | 'failed'; + retcode: 1400 | 1401 | 1403 | 1404; + data: any; + echo?: string | number; + }; + export type EventPayload = { + time: number; + self_id: number; + post_type: 'message' | 'notice' | 'request' | 'meta_event'; + } & Dict; + type WsReverseConfig = { + prefix?: string; + }; + + export interface ConfigMap { + ws?: WsConfig; + ws_reverse?: WsReverseConfig; + } + + export type Config = { + type: T; + access_token?: string; + timeout?: number; + } & ConfigMap[T]; + export const defaultConfig = { + ws: { + host: '0.0.0.0', + port: 6700, + max_reconnect_count: 10, + reconnect_interval: 3000, + }, + ws_reverse: { + prefix: '/onebot/v12', + }, + }; +} diff --git a/packages/adapters/onebot-12/src/types.ts b/packages/adapters/onebot-12/src/types.ts new file mode 100644 index 000000000..6a3f093d5 --- /dev/null +++ b/packages/adapters/onebot-12/src/types.ts @@ -0,0 +1,129 @@ +import { MessageV12 } from '@/message'; +import { Dict } from 'zhin'; + +export type OneBotMethodsV12 = { + send_private_msg(params: { + user_id: string; + message: MessageV12.Sendable; + auto_escape?: boolean; + message_id?: string; + }): MessageV12.Ret; + send_group_msg(params: { + group_id: string; + message: MessageV12.Sendable; + auto_escape?: boolean; + message_id?: string; + }): MessageV12.Ret; + send_msg(params: { + message_type: 'private' | 'group'; + user_id?: number; + group_id?: number; + message: MessageV12.Sendable; + auto_escape?: boolean; + }): MessageV12.Ret; + delete_msg(params: { message_id: number }): void; + get_forward_msg(params: { id: string }): MessageV12.Segment[]; + send_like(params: { user_id: string; times?: number }): boolean; + set_group_kick(params: { group_id: string; user_id: string; reject_add_request?: boolean }): boolean; + set_group_ban(params: { group_id: string; user_id: string; duration?: number }): void; + set_group_anonymous_ban(params: { + group_id: string; + anonymous?: Dict; + anonymous_flag?: string; + flag?: string; + duration?: number; + }): void; + set_group_whole_ban(params: { group_id: string; enable?: boolean }): void; + set_group_admin(params: { group_id: string; user_id: string; enable?: boolean }): boolean; + set_group_anonymous(params: { group_id: string; enable?: boolean }): boolean; + set_group_card(params: { group_id: string; user_id: string; card?: string }): boolean; + set_group_name(params: { group_id: string; group_name: string }): boolean; + set_group_leave(params: { group_id: string; is_dismiss?: boolean }): void; + set_group_special_title(params: { + group_id: string; + user_id: string; + special_title?: string; + duration?: number; + }): boolean; + set_friend_add_request(params: { flag: string; approve?: boolean; remark?: string }): void; + set_group_add_request(params: { + flag: string; + sub_type?: string; + type?: string; + approve?: boolean; + reason?: string; + }): void; + get_login_info(params: object): { user_id: string; nickname: string }; + get_stranger_info(params: { user_id: string; no_cache?: boolean }): StrangerInfo; + get_friend_list(params: object): FriendInfo[]; + get_group_info(params: { group_id: string; no_cache?: boolean }): GroupInfo; + get_group_list(params: object): GroupInfo[]; + get_group_member_list(params: { group_id: string }): MemberInfo[]; + get_group_member_info(params: { group_id: string; user_id: string }): MemberInfo; + get_group_honor_info(params: { group_id: string; type: string }): HonorInfo; + get_cookies(params: { domain?: string }): { cookies: string }; + get_csrf_token(params: object): { token: number }; + get_credentials(params: { domain?: string }): { token: number; cookies: string }; + get_record(prams: { file: string; out_format: string }): { file: string }; + get_image(prams: { file: string }): { file: string }; + can_send_image(params: object): { yes: boolean }; + can_send_record(params: object): { yes: boolean }; + get_status(params: object): { online: boolean; good: boolean }; + get_version_info(params: object): { app_name: string; app_version: string; protocol_version: string }; + set_restart(params: { delay?: number }): void; + clean_cache(params: object): void; + '.handle_quick_operation'(params: { context: Dict; operation: Dict }): void; + 'set_essence_message'(params: { message_id: string }): string; + remove_essence_message(params: { message_id: string }): boolean; + send_group_notice(params: { group_id: string; content: string }): boolean; + send_group_poke(params: { group_id: string; user_id: string }): boolean; +}; +interface BaseInfo { + user_id: string; + nickname: string; +} +export interface StrangerInfo extends BaseInfo { + sex: string; + age: number; +} +export interface FriendInfo extends BaseInfo { + remark: string; +} +export interface MemberInfo extends BaseInfo { + group_id: string; + card: string; + sex: string; + age: string; + area: string; + join_time: number; + last_sent_time: number; + level: string; + role: string; + unfriendly: boolean; + title: string; + title_expire_time: string; + card_changeable: boolean; +} +export interface GroupInfo { + group_id: string; + group_name: string; + member_count: number; + max_member_count: number; +} +export interface TalkAtive extends BaseInfo { + avatar: string; + day_count: number; +} +export interface MemberHonor extends BaseInfo { + avatar: string; + description: string; +} +export interface HonorInfo { + group_id: string; + current_talkative?: TalkAtive; + talkative_list?: MemberHonor[]; + performer_list?: MemberHonor[]; + legend_list?: MemberHonor[]; + strong_newbie_list?: MemberHonor[]; + emotion_list?: MemberHonor[]; +} diff --git a/packages/adapters/onebot-12/tsconfig.json b/packages/adapters/onebot-12/tsconfig.json new file mode 100644 index 000000000..b36ff4243 --- /dev/null +++ b/packages/adapters/onebot-12/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../tsconfig.base", + "compilerOptions": { + "rootDir": "src", + "baseUrl": ".", + "outDir": "lib", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"] +} diff --git a/packages/adapters/qq/package.json b/packages/adapters/qq/package.json index a1b18cc38..f6a054254 100644 --- a/packages/adapters/qq/package.json +++ b/packages/adapters/qq/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/qq", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.14", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -15,11 +15,11 @@ "directory": "packages/adapters/qq" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "qq-group-bot": "latest" }, "peerDependencies": { "qq-group-bot": "latest", - "zhin": "workspace:latest" + "zhin": "workspace:^" } } diff --git a/packages/adapters/wechat/package.json b/packages/adapters/wechat/package.json index 11d31c158..be1d1aabf 100644 --- a/packages/adapters/wechat/package.json +++ b/packages/adapters/wechat/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/wechat", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.1", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -15,11 +15,11 @@ "directory": "packages/adapters/wechat" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "lib-wechat": "latest" }, "peerDependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "lib-wechat": "latest" } } diff --git a/packages/plugins/client/package.json b/packages/plugins/client/package.json index 1df919381..e492ee21a 100644 --- a/packages/plugins/client/package.json +++ b/packages/plugins/client/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/client", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.13", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -15,14 +15,14 @@ "directory": "packages/plugin/client" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "@vitejs/plugin-vue": "latest", - "@zhinjs/plugin-http-server": "workspace:latest", + "@zhinjs/plugin-http-server": "workspace:^", "vue": "latest", "vite": "latest" }, "peerDependencies": { - "zhin": "workspace:latest", - "@zhinjs/plugin-http-server": "workspace:latest" + "zhin": "workspace:^", + "@zhinjs/plugin-http-server": "workspace:^" } } diff --git a/packages/plugins/game/package.json b/packages/plugins/game/package.json index f1e9f8f45..72d14beee 100644 --- a/packages/plugins/game/package.json +++ b/packages/plugins/game/package.json @@ -2,8 +2,7 @@ "name": "@zhinjs/plugin-game", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.1", - + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -16,7 +15,7 @@ "directory": "packages/plugins/game" }, "dependencies": { - "zhin": "workspace:latest", - "@zhinjs/plugin-drawer": "workspace:latest" + "zhin": "workspace:^", + "@zhinjs/plugin-drawer": "workspace:^" } } diff --git a/packages/plugins/groupManage/package.json b/packages/plugins/groupManage/package.json index 8e20e33a5..e7425ef8b 100644 --- a/packages/plugins/groupManage/package.json +++ b/packages/plugins/groupManage/package.json @@ -2,8 +2,7 @@ "name": "@zhinjs/plugin-group-manage", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.1", - + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -16,7 +15,7 @@ "directory": "packages/plugins/groupManage" }, "dependencies": { - "zhin": "workspace:latest", - "@zhinjs/icqq": "workspace:latest" + "zhin": "workspace:^", + "@zhinjs/onebot-12": "workspace:^" } } diff --git a/packages/plugins/groupManage/src/index.ts b/packages/plugins/groupManage/src/index.ts index 6aaf8f607..db3886386 100644 --- a/packages/plugins/groupManage/src/index.ts +++ b/packages/plugins/groupManage/src/index.ts @@ -1,9 +1,8 @@ import { Plugin } from 'zhin'; -import { ICQQAdapter } from '@zhinjs/icqq'; - +import { OneBotV12Adapter } from '@zhinjs/onebot-12'; const groupManage = new Plugin({ name: '群管理', - adapters: ['icqq'], + adapters: ['onebot-12'], }); groupManage .command('thumbMe') @@ -16,8 +15,8 @@ groupManage }) .option('-t ', 10) .scope('group') - .action(async ({ bot, options, message }) => { - const thumbSuccess = await bot.sendLike(parseInt('' + message.sender.user_id), options.times); + .action(async ({ bot, options, message }) => { + const thumbSuccess = await bot.sendLike(message.sender.user_id + '', options.times); return thumbSuccess ? '给你赞好啦' : '不能赞了'; }); groupManage @@ -26,7 +25,7 @@ groupManage .alias('置顶') .permission('admin') .scope('group') - .action(async ({ bot, message }, message_id) => { + .action(async ({ bot, message }, message_id) => { if (!message_id) message_id = message.quote?.message_id!; if (!message_id) return '请输入消息id或引用需要置顶的消息'; const result = await bot.setEssenceMessage(message_id); @@ -38,7 +37,7 @@ groupManage .alias('取消置顶') .permission('admin') .scope('group') - .action(async ({ bot, message }, message_id) => { + .action(async ({ bot, message }, message_id) => { if (!message_id) message_id = message.quote?.message_id!; if (!message_id) return '请输入消息id或引用需要取消置顶的消息'; const result = await bot.removeEssenceMessage(message_id); @@ -50,8 +49,8 @@ groupManage .permission('admin') .scope('group') .option('-t [time:number] 禁言时长,单位秒', 10) - .action(async ({ bot, message, options }, user_id) => { - const result = await bot.setGroupBan(+message.group_id!, +user_id, options.time as number); + .action(async ({ bot, message, options }, user_id) => { + const result = await bot.setGroupBan(message.group_id + '', user_id + '', options.time as number); return `已尝试将(${user_id})禁言时长设为${options.time}秒`; }); groupManage @@ -59,8 +58,8 @@ groupManage .desc('踢出群成员') .permission('admin') .scope('group') - .action(async ({ bot, message }, user_id) => { - const isSuccess = await bot.setGroupKick(+message.group_id!, +user_id); + .action(async ({ bot, message }, user_id) => { + const isSuccess = await bot.setGroupKick(message.group_id + '', user_id + ''); return isSuccess ? `已踢出用户 ${user_id}` : '踢出失败'; }); groupManage @@ -69,8 +68,8 @@ groupManage .option('-c ', false) .permission('master') .scope('group') - .action(async ({ bot, message, options }, user_id) => { - const isSuccess = await bot.setGroupAdmin(+message.group_id!, +user_id, !options.cancel); + .action(async ({ bot, message, options }, user_id) => { + const isSuccess = await bot.setGroupAdmin(message.group_id + '', user_id + '', !options.cancel); return isSuccess ? `已${options.cancel ? '取消' : ''}设置管理员 ${user_id}` : '设置管理员失败'; }); groupManage @@ -80,9 +79,9 @@ groupManage .option('-t ', -1) .option('-c ', false) .scope('group') - .action(async ({ bot, message, options }, user_id, title) => { + .action(async ({ bot, message, options }, user_id, title) => { if (options.cancel) options.time = 0; - const isSuccess = await bot.setGroupSpecialTitle(+message.group_id!, +user_id, title, options.time); + const isSuccess = await bot.setGroupSpecialTitle(message.group_id + '', user_id, title, options.time); return isSuccess ? `已${options.cancel ? '取消' : ''}设置头衔 ${user_id}` : '设置头衔失败'; }); groupManage @@ -90,8 +89,8 @@ groupManage .desc('设置设置群公告') .permission('admin') .scope('group') - .action(async ({ bot, message }, notice) => { - const isSuccess = await bot.sendGroupNotice(+message.group_id!, notice); + .action(async ({ bot, message }, notice) => { + const isSuccess = await bot.sendGroupNotice(message.group_id + '', notice); return isSuccess ? '设置公告成功' : '设置公告失败'; }); groupManage @@ -100,8 +99,8 @@ groupManage .permission('admin') .scope('group') .option('-c ', false) - .action(async ({ bot, message, options }) => { - const isSuccess = await bot.setGroupAnonymous(+message.group_id!, !options.cancel); + .action(async ({ bot, message, options }) => { + const isSuccess = await bot.setGroupAnonymous(message.group_id + '', !options.cancel); return isSuccess ? `已${options.cancel ? '开启' : '关闭'}群匿名` : '管理群匿名失败'; }); groupManage @@ -109,25 +108,17 @@ groupManage .desc('设置/取消设置群名片') .permission('admin') .scope('group') - .action(async ({ bot, message }, user_id, card) => { - const isSuccess = await bot.setGroupCard(+message.group_id!, +user_id, card); + .action(async ({ bot, message }, user_id, card) => { + const isSuccess = await bot.setGroupCard(message.group_id + '', user_id + '', card); return isSuccess ? `已${!card ? '取消' : ''}设置名片 ${user_id}` : '设置名片失败'; }); -groupManage - .command('addMe [comment:string]') - .desc('机器人主动加你为好友') - .scope('group') - .action(async ({ bot, message }, comment) => { - const isSuccess = await bot.addFriend(+message.group_id!, +message.sender?.user_id!, comment); - return isSuccess ? `已发向${message.sender.user_id}发起好友请求` : '发起好友请求失败'; - }); groupManage .command('setName [name:string]') .desc('修改群名称') .permission('admin') .scope('group') - .action(async ({ bot, message }, name) => { - const isSuccess = await bot.setGroupName(+message.group_id!, name); + .action(async ({ bot, message }, name) => { + const isSuccess = await bot.setGroupName(message.group_id + '', name); return isSuccess ? '修改成功' : '修改失败'; }); groupManage @@ -135,8 +126,8 @@ groupManage .desc('发送戳一戳') .alias('戳') .scope('group') - .action(async ({ bot, message }, user_id) => { - const isSuccess = await bot.sendGroupPoke(+message.group_id!, +user_id); + .action(async ({ bot, message }, user_id) => { + const isSuccess = await bot.sendGroupPoke(message.group_id + '', user_id + ''); return isSuccess ? '发送成功' : '发送失败'; }); export default groupManage; diff --git a/packages/plugins/guildManage/package.json b/packages/plugins/guildManage/package.json index e7431dd11..e836bbed8 100644 --- a/packages/plugins/guildManage/package.json +++ b/packages/plugins/guildManage/package.json @@ -2,8 +2,7 @@ "name": "@zhinjs/plugin-guild-manage", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.1", - + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -16,7 +15,7 @@ "directory": "packages/plugins/guildManage" }, "dependencies": { - "zhin": "workspace:latest", - "@zhinjs/qq": "workspace:latest" + "zhin": "workspace:^", + "@zhinjs/qq": "workspace:^" } } diff --git a/packages/plugins/qa/package.json b/packages/plugins/qa/package.json index 2a715c9f6..3cb5391b5 100644 --- a/packages/plugins/qa/package.json +++ b/packages/plugins/qa/package.json @@ -2,8 +2,7 @@ "name": "@zhinjs/plugin-qa", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.2", - + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -16,7 +15,7 @@ "directory": "packages/plugins/qa" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "@zhinjs/plugin-jsondb": "latest" } } diff --git a/packages/plugins/schedule/package.json b/packages/plugins/schedule/package.json index b9ee28650..f1ea061e4 100644 --- a/packages/plugins/schedule/package.json +++ b/packages/plugins/schedule/package.json @@ -2,8 +2,7 @@ "name": "@zhinjs/plugin-schedule", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.4", - + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -16,7 +15,7 @@ "directory": "packages/plugins/schedule" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "node-cron": "^3.0.3", "@zhinjs/plugin-jsondb": "latest" }, diff --git a/packages/services/drawer/package.json b/packages/services/drawer/package.json index 4350bd20d..5f1717168 100644 --- a/packages/services/drawer/package.json +++ b/packages/services/drawer/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/plugin-drawer", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.2", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -16,12 +16,12 @@ }, "dependencies": { "@types/svgdom": "latest", - "zhin": "workspace:latest", + "zhin": "workspace:^", "@svgdotjs/svg.js": "latest", "@resvg/resvg-js": "latest", "svgdom": "latest" }, "peerDependencies": { - "zhin": "workspace:latest" + "zhin": "workspace:^" } } diff --git a/packages/services/http-server/package.json b/packages/services/http-server/package.json index 4b6fb1334..5b29eadd6 100644 --- a/packages/services/http-server/package.json +++ b/packages/services/http-server/package.json @@ -2,7 +2,7 @@ "name": "@zhinjs/plugin-http-server", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.3", + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -17,9 +17,9 @@ "directory": "packages/services/http-server" }, "dependencies": { - "zhin": "workspace:latest", + "zhin": "workspace:^", "koa": "latest", - "koa-basic-auth":"latest", + "koa-basic-auth": "latest", "@koa/router": "latest", "koa-bodyparser": "latest", "ws": "latest" diff --git a/packages/services/sandbox/package.json b/packages/services/sandbox/package.json index 7285debb8..471114cd6 100644 --- a/packages/services/sandbox/package.json +++ b/packages/services/sandbox/package.json @@ -1,22 +1,21 @@ { - "name": "@zhinjs/plugin-sandbox", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "version": "0.0.5", - - "scripts": { - "build": "tsc && tsc-alias" - }, - "files": [ - "lib", - "README.md" - ], - "author": "凉菜", - "repository": { - "url": "https://github.com/zhinjs/zhin", - "directory": "packages/services/sandbox" - }, - "dependencies": { - "zhin": "workspace:latest" - } + "name": "@zhinjs/plugin-sandbox", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "version": "1.0.0", + "scripts": { + "build": "tsc && tsc-alias" + }, + "files": [ + "lib", + "README.md" + ], + "author": "凉菜", + "repository": { + "url": "https://github.com/zhinjs/zhin", + "directory": "packages/services/sandbox" + }, + "dependencies": { + "zhin": "workspace:^" + } } diff --git a/packages/services/upyun/package.json b/packages/services/upyun/package.json index 14174f1b7..f564095d2 100644 --- a/packages/services/upyun/package.json +++ b/packages/services/upyun/package.json @@ -2,8 +2,7 @@ "name": "@zhinjs/plugin-upyun", "main": "lib/index.js", "types": "lib/index.d.ts", - "version": "0.0.2", - + "version": "1.0.0", "scripts": { "build": "tsc && tsc-alias" }, @@ -17,6 +16,6 @@ "directory": "packages/services/upyun" }, "dependencies": { - "zhin": "workspace:latest" + "zhin": "workspace:^" } } diff --git a/rush.json b/rush.json new file mode 100644 index 000000000..57f037219 --- /dev/null +++ b/rush.json @@ -0,0 +1,412 @@ +/** + * This is the main configuration file for Rush. + * For full documentation, please see https://rushjs.io + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", + + /** + * (Required) This specifies the version of the Rush engine to be used in this repo. + * Rush's "version selector" feature ensures that the globally installed tool will + * behave like this release, regardless of which version is installed globally. + * + * The common/scripts/install-run-rush.js automation script also uses this version. + * + * NOTE: If you upgrade to a new major version of Rush, you should replace the "v5" + * path segment in the "$schema" field for all your Rush config files. This will ensure + * correct error-underlining and tab-completion for editors such as VS Code. + */ + "rushVersion": "5.117.1", + + /** + * The next field selects which package manager should be installed and determines its version. + * Rush installs its own local copy of the package manager to ensure that your build process + * is fully isolated from whatever tools are present in the local environment. + * + * Specify one of: "pnpmVersion", "npmVersion", or "yarnVersion". See the Rush documentation + * for details about these alternatives. + */ + "pnpmVersion": "7.33.5", + + // "npmVersion": "6.14.15", + // "yarnVersion": "1.9.4", + + /** + * Older releases of the Node.js engine may be missing features required by your system. + * Other releases may have bugs. In particular, the "latest" version will not be a + * Long Term Support (LTS) version and is likely to have regressions. + * + * Specify a SemVer range to ensure developers use a Node.js version that is appropriate + * for your repo. + * + * LTS schedule: https://nodejs.org/en/about/releases/ + * LTS versions: https://nodejs.org/en/download/releases/ + */ + "nodeSupportedVersionRange": ">=14.15.0 <15.0.0 || >=16.13.0 <17.0.0 || >=18.15.0 <19.0.0", + + /** + * If the version check above fails, Rush will display a message showing the current + * node version and the supported version range. You can use this setting to provide + * additional instructions that will display below the warning, if there's a specific + * tool or script you'd like the user to use to get in line with the expected version. + */ + // "nodeSupportedVersionInstructions": "Run 'nvs use' to switch to the expected node version.", + + /** + * Odd-numbered major versions of Node.js are experimental. Even-numbered releases + * spend six months in a stabilization period before the first Long Term Support (LTS) version. + * For example, 8.9.0 was the first LTS version of Node.js 8. Pre-LTS versions are not recommended + * for production usage because they frequently have bugs. They may cause Rush itself + * to malfunction. + * + * Rush normally prints a warning if it detects a pre-LTS Node.js version. If you are testing + * pre-LTS versions in preparation for supporting the first LTS version, you can use this setting + * to disable Rush's warning. + */ + // "suppressNodeLtsWarning": false, + + /** + * If you would like the version specifiers for your dependencies to be consistent, then + * uncomment this line. This is effectively similar to running "rush check" before any + * of the following commands: + * + * rush install, rush update, rush link, rush version, rush publish + * + * In some cases you may want this turned on, but need to allow certain packages to use a different + * version. In those cases, you will need to add an entry to the "allowedAlternativeVersions" + * section of the common-versions.json. + */ + // "ensureConsistentVersions": true, + + /** + * Large monorepos can become intimidating for newcomers if project folder paths don't follow + * a consistent and recognizable pattern. When the system allows nested folder trees, + * we've found that teams will often use subfolders to create islands that isolate + * their work from others ("shipping the org"). This hinders collaboration and code sharing. + * + * The Rush developers recommend a "category folder" model, where buildable project folders + * must always be exactly two levels below the repo root. The parent folder acts as the category. + * This provides a basic facility for grouping related projects (e.g. "apps", "libraries", + * "tools", "prototypes") while still encouraging teams to organize their projects into + * a unified taxonomy. Limiting to 2 levels seems very restrictive at first, but if you have + * 20 categories and 20 projects in each category, this scheme can easily accommodate hundreds + * of projects. In practice, you will find that the folder hierarchy needs to be rebalanced + * occasionally, but if that's painful, it's a warning sign that your development style may + * discourage refactoring. Reorganizing the categories should be an enlightening discussion + * that brings people together, and maybe also identifies poor coding practices (e.g. file + * references that reach into other project's folders without using Node.js module resolution). + * + * The defaults are projectFolderMinDepth=1 and projectFolderMaxDepth=2. + * + * To remove these restrictions, you could set projectFolderMinDepth=1 + * and set projectFolderMaxDepth to a large number. + */ + // "projectFolderMinDepth": 2, + // "projectFolderMaxDepth": 2, + + /** + * Today the npmjs.com registry enforces fairly strict naming rules for packages, but in the early + * days there was no standard and hardly any enforcement. A few large legacy projects are still using + * nonstandard package names, and private registries sometimes allow it. Set "allowMostlyStandardPackageNames" + * to true to relax Rush's enforcement of package names. This allows upper case letters and in the future may + * relax other rules, however we want to minimize these exceptions. Many popular tools use certain punctuation + * characters as delimiters, based on the assumption that they will never appear in a package name; thus if we relax + * the rules too much it is likely to cause very confusing malfunctions. + * + * The default value is false. + */ + // "allowMostlyStandardPackageNames": true, + + /** + * This feature helps you to review and approve new packages before they are introduced + * to your monorepo. For example, you may be concerned about licensing, code quality, + * performance, or simply accumulating too many libraries with overlapping functionality. + * The approvals are tracked in two config files "browser-approved-packages.json" + * and "nonbrowser-approved-packages.json". See the Rush documentation for details. + */ + // "approvedPackagesPolicy": { + // /** + // * The review categories allow you to say for example "This library is approved for usage + // * in prototypes, but not in production code." + // * + // * Each project can be associated with one review category, by assigning the "reviewCategory" field + // * in the "projects" section of rush.json. The approval is then recorded in the files + // * "common/config/rush/browser-approved-packages.json" and "nonbrowser-approved-packages.json" + // * which are automatically generated during "rush update". + // * + // * Designate categories with whatever granularity is appropriate for your review process, + // * or you could just have a single category called "default". + // */ + // "reviewCategories": [ + // // Some example categories: + // "production", // projects that ship to production + // "tools", // non-shipping projects that are part of the developer toolchain + // "prototypes" // experiments that should mostly be ignored by the review process + // ], + // + // /** + // * A list of NPM package scopes that will be excluded from review. + // * We recommend to exclude TypeScript typings (the "@types" scope), because + // * if the underlying package was already approved, this would imply that the typings + // * are also approved. + // */ + // // "ignoredNpmScopes": ["@types"] + // }, + + /** + * If you use Git as your version control system, this section has some additional + * optional features you can use. + */ + "gitPolicy": { + /** + * Work at a big company? Tired of finding Git commits at work with unprofessional Git + * emails such as "beer-lover@my-college.edu"? Rush can validate people's Git email address + * before they get started. + * + * Define a list of regular expressions describing allowable e-mail patterns for Git commits. + * They are case-insensitive anchored JavaScript RegExps. Example: ".*@example\.com" + * + * IMPORTANT: Because these are regular expressions encoded as JSON string literals, + * RegExp escapes need two backslashes, and ordinary periods should be "\\.". + */ + // "allowedEmailRegExps": [ + // "[^@]+@users\\.noreply\\.github\\.com", + // "rush-bot@example\\.org" + // ], + + /** + * When Rush reports that the address is malformed, the notice can include an example + * of a recommended email. Make sure it conforms to one of the allowedEmailRegExps + * expressions. + */ + // "sampleEmail": "example@users.noreply.github.com", + + /** + * The commit message to use when committing changes during 'rush publish'. + * + * For example, if you want to prevent these commits from triggering a new_ci.yml build, + * you might configure your system's trigger to look for a special string such as "[skip-ci]" + * in the commit message, and then customize Rush's message to contain that string. + */ + // "versionBumpCommitMessage": "Bump versions [skip ci]", + + /** + * The commit message to use when committing changes during 'rush version'. + * + * For example, if you want to prevent these commits from triggering a new_ci.yml build, + * you might configure your system's trigger to look for a special string such as "[skip-ci]" + * in the commit message, and then customize Rush's message to contain that string. + */ + // "changeLogUpdateCommitMessage": "Update changelogs [skip ci]", + + /** + * The commit message to use when commiting changefiles during 'rush change --commit' + * + * If no commit message is set it will default to 'Rush change' + */ + // "changefilesCommitMessage": "Rush change" + }, + + "repository": { + /** + * The URL of this Git repository, used by "rush change" to determine the base branch for your PR. + * + * The "rush change" command needs to determine which files are affected by your PR diff. + * If you merged or cherry-picked commits from the main branch into your PR branch, those commits + * should be excluded from this diff (since they belong to some other PR). In order to do that, + * Rush needs to know where to find the base branch for your PR. This information cannot be + * determined from Git alone, since the "pull request" feature is not a Git concept. Ideally + * Rush would use a vendor-specific protocol to query the information from GitHub, Azure DevOps, etc. + * But to keep things simple, "rush change" simply assumes that your PR is against the "main" branch + * of the Git remote indicated by the repository.url setting in rush.json. If you are working in + * a GitHub "fork" of the real repo, this setting will be different from the repository URL of your + * your PR branch, and in this situation "rush change" will also automatically invoke "git fetch" + * to retrieve the latest activity for the remote main branch. + */ + // "url": "https://github.com/microsoft/rush-example", + + /** + * The default branch name. This tells "rush change" which remote branch to compare against. + * The default value is "main" + */ + // "defaultBranch": "main", + + /** + * The default remote. This tells "rush change" which remote to compare against if the remote URL is + * not set or if a remote matching the provided remote URL is not found. + */ + // "defaultRemote": "origin" + }, + + /** + * Event hooks are customized script actions that Rush executes when specific events occur + */ + "eventHooks": { + /** + * A list of shell commands to run before "rush install" or "rush update" starts installation + */ + "preRushInstall": [ + // "common/scripts/pre-rush-install.js" + ], + + /** + * A list of shell commands to run after "rush install" or "rush update" finishes installation + */ + "postRushInstall": [], + + /** + * A list of shell commands to run before "rush build" or "rush rebuild" starts building + */ + "preRushBuild": [], + + /** + * A list of shell commands to run after "rush build" or "rush rebuild" finishes building + */ + "postRushBuild": [], + + /** + * A list of shell commands to run before the "rushx" command starts + */ + "preRushx": [], + + /** + * A list of shell commands to run after the "rushx" command finishes + */ + "postRushx": [] + }, + + /** + * Rush can collect anonymous telemetry about everyday developer activity such as + * success/failure of installs, builds, and other operations. You can use this to identify + * problems with your toolchain or Rush itself. THIS TELEMETRY IS NOT SHARED WITH MICROSOFT. + * It is written into JSON files in the common/temp folder. It's up to you to write scripts + * that read these JSON files and do something with them. These scripts are typically registered + * in the "eventHooks" section. + */ + // "telemetryEnabled": false, + + /** + * Allows creation of hotfix changes. This feature is experimental so it is disabled by default. + * If this is set, 'rush change' only allows a 'hotfix' change type to be specified. This change type + * will be used when publishing subsequent changes from the monorepo. + */ + // "hotfixChangeEnabled": false, + + /** + * This is an optional, but recommended, list of allowed tags that can be applied to Rush projects + * using the "tags" setting in this file. This list is useful for preventing mistakes such as misspelling, + * and it also provides a centralized place to document your tags. If "allowedProjectTags" list is + * not specified, then any valid tag is allowed. A tag name must be one or more words + * separated by hyphens or slashes, where a word may contain lowercase ASCII letters, digits, + * ".", and "@" characters. + */ + // "allowedProjectTags": [ "tools", "frontend-team", "1.0.0-release" ], + + /** + * (Required) This is the inventory of projects to be managed by Rush. + * + * Rush does not automatically scan for projects using wildcards, for a few reasons: + * 1. Depth-first scans are expensive, particularly when tools need to repeatedly collect the list. + * 2. On a caching new_ci.yml machine, scans can accidentally pick up files left behind from a previous build. + * 3. It's useful to have a centralized inventory of all projects and their important metadata. + */ + "projects": [ + // { + // /** + // * The NPM package name of the project (must match package.json) + // */ + // "packageName": "my-app", + // + // /** + // * The path to the project folder, relative to the rush.json config file. + // */ + // "projectFolder": "apps/my-app", + // + // /** + // * An optional category for usage in the "browser-approved-packages.json" + // * and "nonbrowser-approved-packages.json" files. The value must be one of the + // * strings from the "reviewCategories" defined above. + // */ + // "reviewCategory": "production", + // + // /** + // * A list of Rush project names that are to be installed from NPM + // * instead of linking to the local project. + // * + // * If a project's package.json specifies a dependency that is another Rush project + // * in the monorepo workspace, normally Rush will locally link its folder instead of + // * installing from NPM. If you are using PNPM workspaces, this is indicated by + // * a SemVer range such as "workspace:^1.2.3". To prevent mistakes, Rush reports + // * an error if the "workspace:" protocol is missing. + // * + // * Locally linking ensures that regressions are caught as early as possible and is + // * a key benefit of monorepos. However there are occasional situations where + // * installing from NPM is needed. A classic example is a cyclic dependency. + // * Imagine three Rush projects: "my-toolchain" depends on "my-tester", which depends + // * on "my-library". Suppose that we add "my-toolchain" to the "devDependencies" + // * of "my-library" so it can be built by our toolchain. This cycle creates + // * a problem -- Rush can't build a project using a not-yet-built dependency. + // * We can solve it by adding "my-toolchain" to the "decoupledLocalDependencies" + // * of "my-library", so it builds using the last published release. Choose carefully + // * which package to decouple; some choices are much easier to manage than others. + // * + // * (In older Rush releases, this setting was called "cyclicDependencyProjects".) + // */ + // "decoupledLocalDependencies": [ + // // "my-toolchain" + // ], + // + // /** + // * If true, then this project will be ignored by the "rush check" command. + // * The default value is false. + // */ + // // "skipRushCheck": false, + // + // /** + // * A flag indicating that changes to this project will be published to npm, which affects + // * the Rush change and publish workflows. The default value is false. + // * NOTE: "versionPolicyName" and "shouldPublish" are alternatives; you cannot specify them both. + // */ + // // "shouldPublish": false, + // + // /** + // * Facilitates postprocessing of a project's files prior to publishing. + // * + // * If specified, the "publishFolder" is the relative path to a subfolder of the project folder. + // * The "rush publish" command will publish the subfolder instead of the project folder. The subfolder + // * must contain its own package.json file, which is typically a build output. + // */ + // // "publishFolder": "temp/publish", + // + // /** + // * An optional version policy associated with the project. Version policies are defined + // * in "version-policies.json" file. See the "rush publish" documentation for more info. + // * NOTE: "versionPolicyName" and "shouldPublish" are alternatives; you cannot specify them both. + // */ + // // "versionPolicyName": "", + // + // /** + // * An optional set of custom tags that can be used to select this project. For example, + // * adding "my-custom-tag" will allow this project to be selected by the + // * command "rush list --only tag:my-custom-tag". The tag name must be one or more words + // * separated by hyphens or slashes, where a word may contain lowercase ASCII letters, digits, + // * ".", and "@" characters. + // */ + // // "tags": [ "1.0.0-release", "frontend-team" ] + // }, + // + // { + // "packageName": "my-controls", + // "projectFolder": "libraries/my-controls", + // "reviewCategory": "production", + // "tags": [ "frontend-team" ] + // }, + // + // { + // "packageName": "my-toolchain", + // "projectFolder": "tools/my-toolchain", + // "reviewCategory": "tools", + // "tags": [ "tools" ] + // } + ] +} diff --git a/scripts/build.js b/scripts/build.js deleted file mode 100644 index ed76f6c5c..000000000 --- a/scripts/build.js +++ /dev/null @@ -1,11 +0,0 @@ - -const { execSync } = require('child_process'); -const { getPackages } = require('./common'); -for(const root of getPackages()){ - console.log(`building ${root.replace(process.cwd()+'/','')}`) - const result=execSync(`npm run build`,{ - cwd:root, - encoding:'utf8' - }) - result && console.log(result) -} diff --git a/scripts/publish.js b/scripts/publish.js deleted file mode 100644 index 02390fa63..000000000 --- a/scripts/publish.js +++ /dev/null @@ -1,43 +0,0 @@ -const { getPackages } = require('./common'); -const fs = require('fs') -const { execSync } = require('child_process'); -const { gt, prerelease } = require('semver') -const latest = require('latest-version') - -function getVersion(name, isNext) { - if (isNext) { - return latest(name, { version: 'next' }).catch(() => getVersion(name)) - } else { - return latest(name).catch(() => '0.0.0') - } -} - -function isNext(version) { - const parts = prerelease(version) - if (!parts) return false - return parts[0] !== 'rc' -} -(async ()=>{ - for(const root of getPackages()){ - const meta=require(root+'/package.json') - const backupPackageJson=fs.readFileSync(root+'/package.json', 'utf8') - const newPackageJson=JSON.parse(backupPackageJson.replace(/workspace:latest/g,'latest')) - Object.keys(newPackageJson.peerDependencies||{}).forEach(key=>{ - if(newPackageJson.dependencies[key]) delete newPackageJson.dependencies[key] - }) - - const current = await getVersion(meta.name, isNext(meta.version)) // 获取最新版本号 - if (gt(meta.version, current)) { - fs.writeFileSync(root+'/package.json', JSON.stringify(newPackageJson,null,2)) - console.log(`start publish ${meta.name}@${meta.version}`) - execSync(`npm publish --access public --tag ${isNext(meta.version) ? 'next' : 'latest'}`,{ - cwd:root, - encoding:'utf8' - }) - fs.writeFileSync(root+'/package.json', backupPackageJson) - }else { - console.log(`${meta.name}@${meta.version} no change, skip`) - } - } -})() - diff --git a/test/CHANGELOG.md b/test/CHANGELOG.md new file mode 100644 index 000000000..eec0bc6f0 --- /dev/null +++ b/test/CHANGELOG.md @@ -0,0 +1,29 @@ +# test + +## 2.0.0 + +### Major Changes + +- Bump version + +### Patch Changes + +- Updated dependencies + - @zhinjs/plugin-http-server@1.0.0 + - @zhinjs/plugin-group-manage@1.0.0 + - @zhinjs/plugin-guild-manage@1.0.0 + - @zhinjs/onebot-11@1.0.0 + - @zhinjs/onebot-12@1.0.0 + - @zhinjs/dingtalk@1.0.0 + - @zhinjs/discord@1.0.0 + - @zhinjs/plugin-schedule@1.0.0 + - @zhinjs/plugin-sandbox@1.0.0 + - @zhinjs/wechat@1.0.0 + - @zhinjs/plugin-drawer@1.0.0 + - @zhinjs/client@1.0.0 + - @zhinjs/plugin-upyun@1.0.0 + - @zhinjs/icqq@1.0.0 + - @zhinjs/plugin-game@1.0.0 + - @zhinjs/qq@1.0.0 + - @zhinjs/plugin-qa@1.0.0 + - zhin@3.0.0 diff --git a/test/package.json b/test/package.json index 6fdb0ee5a..b6eec0d76 100644 --- a/test/package.json +++ b/test/package.json @@ -1,6 +1,6 @@ { "name": "test", - "version": "1.0.0", + "version": "2.0.0", "scripts": { "start": "zhin", "initial": "zhin init -m dev", @@ -8,23 +8,24 @@ }, "dependencies": { "icqq": "latest", - "zhin": "workspace:latest", - "@zhinjs/icqq": "workspace:latest", - "@zhinjs/qq": "workspace:latest", - "@zhinjs/dingtalk": "workspace:latest", - "@zhinjs/discord": "workspace:latest", - "@zhinjs/onebot-11": "workspace:latest", - "@zhinjs/wechat": "workspace:latest", - "@zhinjs/client": "workspace:latest", - "@zhinjs/plugin-http-server": "workspace:latest", - "@zhinjs/plugin-drawer": "workspace:latest", - "@zhinjs/plugin-game": "workspace:latest", - "@zhinjs/plugin-jsondb": "workspace:latest", - "@zhinjs/plugin-upyun": "workspace:latest", - "@zhinjs/plugin-sandbox": "workspace:latest", - "@zhinjs/plugin-schedule": "workspace:latest", - "@zhinjs/plugin-qa": "workspace:latest", - "@zhinjs/plugin-guild-manage": "workspace:latest", - "@zhinjs/plugin-group-manage": "workspace:latest" + "zhin": "workspace:^", + "@zhinjs/icqq": "workspace:^", + "@zhinjs/qq": "workspace:^", + "@zhinjs/dingtalk": "workspace:^", + "@zhinjs/discord": "workspace:^", + "@zhinjs/onebot-11": "workspace:^", + "@zhinjs/onebot-12": "workspace:^", + "@zhinjs/wechat": "workspace:^", + "@zhinjs/client": "workspace:^", + "@zhinjs/plugin-http-server": "workspace:^", + "@zhinjs/plugin-drawer": "workspace:^", + "@zhinjs/plugin-game": "workspace:^", + "@zhinjs/plugin-jsondb": "workspace:^", + "@zhinjs/plugin-upyun": "workspace:^", + "@zhinjs/plugin-sandbox": "workspace:^", + "@zhinjs/plugin-schedule": "workspace:^", + "@zhinjs/plugin-qa": "workspace:^", + "@zhinjs/plugin-guild-manage": "workspace:^", + "@zhinjs/plugin-group-manage": "workspace:^" } } diff --git a/test/plugins/reg-explain.js b/test/plugins/reg-explain.js new file mode 100644 index 000000000..971ef73e4 --- /dev/null +++ b/test/plugins/reg-explain.js @@ -0,0 +1,1029 @@ +if (typeof define !== 'function') var define = require('amdefine')(module); +define(['./Kit', './parse'], function (K, parse) { + parse.exportConstants(); + + var FONT_SIZE = 16, + LABEL_FONT_SIZE = 14, + PATH_LEN = 16, + BG_COLOR = '#EEE', + FONT_FAMILY = 'DejaVu Sans Mono,monospace'; + + var _multiLine = false; /* global flag quick work*/ + + var PAPER_MARGIN = 10; + + var _charSizeCache = {}, + _tmpText; + function getCharSize(fontSize, fontBold) { + fontBold = fontBold || 'normal'; + if (_charSizeCache[fontSize] && _charSizeCache[fontSize][fontBold]) return _charSizeCache[fontSize][fontBold]; + _tmpText.attr({ 'font-size': fontSize, 'font-weight': fontBold }); + var box = _tmpText.getBBox(); + _charSizeCache[fontSize] = _charSizeCache[fontSize] || {}; + return (_charSizeCache[fontSize][fontBold] = { + width: box.width / ((_tmpText.attr('text').length - 1) / 2), + height: box.height / 2, + }); + } + + function initTmpText(paper) { + _tmpText = paper + .text( + -1000, + -1000, + 'XgfTlM|.q\nXgfTlM|.q', // random multiline text + ) + .attr({ 'font-family': FONT_FAMILY, 'font-size': FONT_SIZE }); + } + + /** + @param {AST} re AST returned by `parse` + */ + function visualize(re, flags, paper) { + paper.clear(); + paper.setSize(0, 0); + var bg = paper.rect(0, 0, 0, 0); + bg.attr('fill', BG_COLOR); + bg.attr('stroke', BG_COLOR); + + initTmpText(paper); + _multiLine = !!~flags.indexOf('m'); + + var texts = highlight(re.tree); + + texts.unshift(text('/', hlColorMap.delimiter)); + texts.unshift(text('RegExp: ')); + texts.push(text('/', hlColorMap.delimiter)); + if (flags) texts.push(text(flags, hlColorMap.flags)); + var charSize = getCharSize(FONT_SIZE, 'bold'), + startX = PAPER_MARGIN, + startY = charSize.height / 2 + PAPER_MARGIN, + width = 0, + height = 0; + + width = texts.reduce(function (x, t) { + t.x = x; + t.y = startY; + var w = t.text.length * charSize.width; + return x + w; + }, startX); + width += PAPER_MARGIN; + height = charSize.height + PAPER_MARGIN * 2; + texts = paper.add(texts); + + paper.setSize(width, charSize.height + PAPER_MARGIN * 2); + + var ret = plot(re.tree, 0, 0); + + height = Math.max(ret.height + 3 * PAPER_MARGIN + charSize.height, height); + width = Math.max(ret.width + 2 * PAPER_MARGIN, width); + + paper.setSize(width, height); + + bg.attr('width', width); + bg.attr('height', height); + + translate(ret.items, PAPER_MARGIN, PAPER_MARGIN * 2 + charSize.height - ret.y); + paper.add(ret.items); + } + + function plot(tree, x, y) { + tree.unshift({ type: 'startPoint' }); + tree.push({ type: 'endPoint' }); + return plotTree(tree, x, y); + } + + function translate(items, dx, dy) { + items.forEach(function (t) { + if (t._translate) t._translate(dx, dy); + else { + t.x += dx; + t.y += dy; + } + }); + } + + // return NodePlot config + function plotTree(tree, x, y) { + var results = [], + items = [], + width = 0, + height = 0, + fromX = x, + top = y, + bottom = y; + if (!tree.length) return plotNode.empty(null, x, y); + tree.forEach(function (node) { + var ret; + if (node.repeat) { + ret = plotNode.repeat(node, fromX, y); + } else { + ret = plotNode[node.type](node, fromX, y); + } + results.push(ret); + fromX += ret.width + PATH_LEN; + width += ret.width; + top = Math.min(top, ret.y); + bottom = Math.max(bottom, ret.y + ret.height); + items = items.concat(ret.items); + }); + height = bottom - top; + results.reduce(function (a, b) { + width += PATH_LEN; + var p = hline(a.lineOutX, y, b.lineInX); + items.push(p); + return b; + }); + var lineInX = results[0].lineInX, + lineOutX = results[results.length - 1].lineOutX; + return { + items: items, + width: width, + height: height, + x: x, + y: top, + lineInX: lineInX, + lineOutX: lineOutX, + }; + } + // return NodePlot config + function textRect(s, x, y, bgColor, textColor) { + s = K.toPrint(s); + var padding = 6; + var charSize = getCharSize(FONT_SIZE); + var tw = s.length * charSize.width, + h = charSize.height + padding * 2, + w = tw + padding * 2; + var rect = { + type: 'rect', + x: x, + y: y - h / 2, + width: w, + height: h, + stroke: 'none', + fill: bgColor || 'transparent', + }; + var t = { + 'type': 'text', + 'x': x + w / 2, + 'y': y, + 'text': s, + 'font-size': FONT_SIZE, + 'font-family': FONT_FAMILY, + 'fill': textColor || 'black', + }; + return { + text: t, + rect: rect, + items: [rect, t], + width: w, + height: h, + x: x, + y: rect.y, + lineInX: x, + lineOutX: x + w, + }; + } + + // return LabelObject {lable:Element,x,y,width,height} + function textLabel(x, y, s, color) { + // x is center x ,y is bottom y + var charSize = getCharSize(LABEL_FONT_SIZE); + var lines = s.split('\n'); + var textHeight = lines.length * charSize.height; + var textWidth; + if (lines.length > 1) { + textWidth = Math.max.apply( + Math, + lines.map(function (a) { + return a.length; + }), + ); + } else { + textWidth = s.length; + } + textWidth = textWidth * charSize.width; + var margin = 4; + var txt = { + 'type': 'text', + 'x': x, + 'y': y - textHeight / 2 - margin, + 'text': s, + 'font-size': LABEL_FONT_SIZE, + 'font-family': FONT_FAMILY, + 'fill': color || '#444', + }; + return { + label: txt, + x: x - textWidth / 2, + y: y - textHeight - margin, + width: textWidth, + height: textHeight + margin, + }; + } + //return element config + function hline(x, y, destX) { + return { + 'type': 'path', + 'x': x, + 'y': y, + 'path': ['M', x, y, 'H', destX], + 'stroke-linecap': 'butt', + 'stroke-linejoin': 'round', + 'stroke': '#333', + 'stroke-width': 2, + '_translate': function (x, y) { + var p = this.path; + p[1] += x; + p[2] += y; + p[4] += x; + }, + }; + } + + //return element config + function smoothLine(fromX, fromY, toX, toY) { + var radius = 10, + p, + _translate; + var signX = fromX > toX ? -1 : 1, + signY = fromY > toY ? -1 : 1; + if (Math.abs(fromY - toY) < radius * 1.5 /*|| Math.abs(fromX-toX) 1 ? ' to ' : ' or ') + _plural(repeat.max); + } else { + txt += ' or more times'; + } + } + + var offsetX = padding, + offsetY = 0, + r = padding, + rectH = ret.y + ret.height - y, + rectW = padding * 2 + ret.width; + width = rectW; + var p; // repeat rect box path + if (repeat.max !== 1) { + // draw repeat rect box + rectH += padding; + height += padding; + p = { + 'type': 'path', + 'path': [ + 'M', + ret.x + padding, + y, + 'Q', + x, + y, + x, + y + r, + 'V', + y + rectH - r, + 'Q', + x, + y + rectH, + x + r, + y + rectH, + 'H', + x + rectW - r, + 'Q', + x + rectW, + y + rectH, + x + rectW, + y + rectH - r, + 'V', + y + r, + 'Q', + x + rectW, + y, + ret.x + ret.width + padding, + y, + ], + '_translate': _curveTranslate, + 'stroke': 'maroon', + 'stroke-width': 2, + }; + if (repeat.nonGreedy) { + //txt+="(NonGreedy!)"; + p.stroke = 'Brown'; + p['stroke-dasharray'] = '-'; + } + items.push(p); + } else { + // so completely remove label when /a?/ but not /a??/ + txt = false; + } + + var skipPath; + if (repeat.min === 0) { + //draw a skip path + var skipRectH = y - ret.y + padding, + skipRectW = rectW + padding * 2; + offsetX += padding; + offsetY = -padding - 2; //tweak,stroke-width is 2 + width = skipRectW; + height += padding; + skipPath = { + 'type': 'path', + 'path': [ + 'M', + x, + y, + 'Q', + x + r, + y, + x + r, + y - r, + 'V', + y - skipRectH + r, + 'Q', + x + r, + y - skipRectH, + x + r * 2, + y - skipRectH, + 'H', + x + skipRectW - r * 2, + 'Q', + x + skipRectW - r, + y - skipRectH, + x + skipRectW - r, + y - skipRectH + r, + 'V', + y - r, + 'Q', + x + skipRectW - r, + y, + x + skipRectW, + y, + ], + '_translate': _curveTranslate, + 'stroke': repeat.nonGreedy ? NonGreedySkipPathColor : '#333', + 'stroke-width': 2, + }; + if (p) translate([p], padding, 0); + items.push(skipPath); + } + + if (txt) { + var tl = textLabel(x + width / 2, y, txt); + translate([tl.label], 0, rectH + tl.height + LABEL_MARGIN); //bottom label + items.push(tl.label); + height += LABEL_MARGIN + tl.height; + var labelOffsetX = (Math.max(tl.width, width) - width) / 2; + if (labelOffsetX) translate(items, labelOffsetX, 0); + width = Math.max(tl.width, width); + offsetX += labelOffsetX; + } + + translate(ret.items, offsetX, 0); + items = items.concat(ret.items); + return { + items: items, + width: width, + height: height, + x: x, + y: ret.y + offsetY, + lineInX: ret.lineInX + offsetX, + lineOutX: ret.lineOutX + offsetX, + }; + + function _plural(n) { + return n + (n < 2 ? ' time' : ' times'); + } + function _curveTranslate(x, y) { + var p = this.path; + p[1] += x; + p[2] += y; + p[4] += x; + p[5] += y; + p[6] += x; + p[7] += y; + p[9] += y; + p[11] += x; + p[12] += y; + p[13] += x; + p[14] += y; + p[16] += x; + p[18] += x; + p[19] += y; + p[20] += x; + p[21] += y; + p[23] += y; + p[25] += x; + p[26] += y; + p[27] += x; + p[28] += y; + } + }, + choice: function (node, x, y) { + if (elideOK(node)) return plotNode.empty(null, x, y); + var marginX = 20, + spacing = 6, + paddingY = 4, + height = 0, + width = 0; + var branches = node.branches.map(function (branch) { + var ret = plotTree(branch, x, y); + height += ret.height; + width = Math.max(width, ret.width); + return ret; + }); + height += (branches.length - 1) * spacing + paddingY * 2; + width += marginX * 2; + + var centerX = x + width / 2, + dy = y - height / 2 + paddingY, // destY + lineOutX = x + width, + items = []; + branches.forEach(function (a) { + var dx = centerX - a.width / 2; // destX + translate(a.items, dx - a.x, dy - a.y); + items = items.concat(a.items); + /* + var p1=smoothLine(x,y,dx-a.x+a.lineInX,y+dy-a.y); + var p2=smoothLine(lineOutX,y,a.lineOutX+dx-a.x,y+dy-a.y); + items=items.concat(a.items); + items.push(p1,p2);*/ + // current a.y based on y(=0),its middle at y=0 + var lineY = y + dy - a.y; + var p1 = smoothLine(x, y, x + marginX, lineY); + var p2 = smoothLine(lineOutX, y, x + width - marginX, lineY); + items.push(p1, p2); + if (x + marginX !== dx - a.x + a.lineInX) { + items.push(hline(x + marginX, lineY, dx - a.x + a.lineInX)); + } + if (a.lineOutX + dx - a.x !== x + width - marginX) { + items.push(hline(a.lineOutX + dx - a.x, lineY, x + width - marginX)); + } + + a.x = dx; + a.y = dy; + dy += a.height + spacing; + }); + + return { + items: items, + width: width, + height: height, + x: x, + y: y - height / 2, + lineInX: x, + lineOutX: lineOutX, + }; + }, + charset: function (node, x, y) { + var padding = 6, + spacing = 4; + var clsDesc = { d: 'Digit', D: 'NonDigit', w: 'Word', W: 'NonWord', s: 'WhiteSpace', S: 'NonWhiteSpace' }; + var charBgColor = 'LightSkyBlue', + charTextColor = 'black', + clsBgColor = 'Green', + clsTextColor = 'white', + rangeBgColor = 'teal', + rangeTextColor = 'white', + boxColor = node.exclude ? 'Pink' : 'Khaki', + labelColor = node.exclude ? '#C00' : ''; + var simple = onlyCharClass(node); + if (simple) { + var a = textRect(clsDesc[node.classes[0]], x, y, clsBgColor, clsTextColor); + a.rect.r = 5; + if (!node.exclude) { + return a; + } else { + var tl = textLabel(a.x + a.width / 2, a.y, 'None of:', labelColor); + var items = a.items; + items.push(tl.label); + var oldWidth = a.width; + var width = Math.max(tl.width, a.width); + var offsetX = (width - oldWidth) / 2; //ajust label text + translate(items, offsetX, 0); + return { + items: items, + width: width, + height: a.height + tl.height, + x: Math.min(tl.x, a.x), + y: tl.y, + lineInX: offsetX + a.x, + lineOutX: offsetX + a.x + a.width, + }; + } + } + if (!node.chars && !node.ranges.length && !node.classes.length) { + // It must be exclude charset here + var a = textRect('AnyChar', x, y, 'green', 'white'); + a.rect.r = 5; + return a; + } + var packs = [], + ret, + width = 0, + height = 0, + singleBoxHeight; + if (node.chars) { + ret = textRect(node.chars, x, y, charBgColor, charTextColor); + ret.rect.r = 5; + packs.push(ret); + width = ret.width; + } + node.ranges.forEach(function (rg) { + rg = rg.split('').join('-'); + var ret = textRect(rg, x, y, rangeBgColor, rangeTextColor); + ret.rect.r = 5; + packs.push(ret); + width = Math.max(ret.width, width); + }); + node.classes.forEach(function (cls) { + var ret = textRect(clsDesc[cls], x, y, clsBgColor, clsTextColor); + ret.rect.r = 5; + packs.push(ret); + width = Math.max(ret.width, width); + }); + + singleBoxHeight = packs[0].height; + + var pack1 = [], + pack2 = []; + packs.sort(function (a, b) { + return b.width - a.width; + }); + packs.forEach(function (a) { + if (a.width * 2 + spacing > width) pack1.push(a); + else pack2.push(a); // can be inline + }); + packs = pack1; + var a1, a2; + while (pack2.length) { + a1 = pack2.pop(); + a2 = pack2.pop(); + if (!a2) { + packs.push(a1); + break; + } + if (a1.width - a2.width > 2) { + packs.push(a1); + pack2.push(a2); + continue; + } + translate(a2.items, a1.width + spacing, 0); + packs.push({ + items: a1.items.concat(a2.items), + width: a1.width + a2.width + spacing, + height: a1.height, + x: a1.x, + y: a1.y, + }); + height -= a1.height; + } + + width += padding * 2; + height = (packs.length - 1) * spacing + packs.length * singleBoxHeight + padding * 2; + + var rect = { + type: 'rect', + x: x, + y: y - height / 2, + r: 4, + width: width, + height: height, + stroke: 'none', + fill: boxColor, + }; + + var startY = rect.y + padding; + var items = [rect]; + + packs.forEach(function (a) { + translate(a.items, x - a.x + (width - a.width) / 2, startY - a.y); + items = items.concat(a.items); + startY += a.height + spacing; + }); + var tl = textLabel(rect.x + rect.width / 2, rect.y, (node.exclude ? 'None' : 'One') + ' of:', labelColor); + items.push(tl.label); + var oldWidth = width; + width = Math.max(tl.width, width); + var offsetX = (width - oldWidth) / 2; //ajust label text + translate(items, offsetX, 0); + return { + items: items, + width: width, + height: height + tl.height, + x: Math.min(tl.x, x), + y: tl.y, + lineInX: offsetX + x, + lineOutX: offsetX + x + rect.width, + }; + }, + group: function (node, x, y) { + if (elideOK(node)) return plotNode.empty(null, x, y); + var padding = 10, + lineColor = 'silver', + strokeWidth = 2; + var sub = plotTree(node.sub, x, y); + if (node.num) { + translate(sub.items, padding, 0); + var rectW = sub.width + padding * 2, + rectH = sub.height + padding * 2; + var rect = { + 'type': 'rect', + 'x': x, + 'y': sub.y - padding, + 'r': 6, + 'width': rectW, + 'height': rectH, + 'stroke-dasharray': '.', + 'stroke': lineColor, + 'stroke-width': strokeWidth, + }; + var tl = textLabel(rect.x + rect.width / 2, rect.y - strokeWidth, 'Group #' + node.num); + var items = sub.items.concat([rect, tl.label]); + var width = Math.max(tl.width, rectW); + var offsetX = (width - rectW) / 2; //ajust label text space + if (offsetX) translate(items, offsetX, 0); + return { + items: items, + width: width, + height: rectH + tl.height + 4, // 4 is margin + x: x, + y: tl.y, + lineInX: offsetX + sub.lineInX + padding, + lineOutX: offsetX + sub.lineOutX + padding, + }; + } + return sub; + }, + assert: function (node, x, y) { + var simpleAssert = { + AssertNonWordBoundary: { bg: 'maroon', fg: 'white' }, + AssertWordBoundary: { bg: 'purple', fg: 'white' }, + AssertEnd: { bg: 'Indigo', fg: 'white' }, + AssertBegin: { bg: 'Indigo', fg: 'white' }, + }; + var conf, + nat = node.assertionType, + txt = nat.replace('Assert', '') + '!'; + if ((conf = simpleAssert[nat])) { + if (_multiLine && (nat === 'AssertBegin' || nat === 'AssertEnd')) { + txt = 'Line' + txt; + } + return textRect(txt, x, y, conf.bg, conf.fg); + } + + var lineColor, + fg, + padding = 8; + if (nat === AssertLookahead) { + lineColor = 'CornflowerBlue'; + fg = 'darkgreen'; + txt = 'Followed by:'; + } else if (nat === AssertNegativeLookahead) { + lineColor = '#F63'; + fg = 'Purple'; + //txt="Negative\nLookahead!"; // break line + txt = 'Not followed by:'; + } + + var sub = plotNode.group(node, x, y); + var rectH = sub.height + padding * 2, + rectW = sub.width + padding * 2; + var rect = { + 'type': 'rect', + 'x': x, + 'y': sub.y - padding, + 'r': 6, + 'width': rectW, + 'height': rectH, + 'stroke-dasharray': '-', + 'stroke': lineColor, + 'stroke-width': 2, + }; + + var tl = textLabel(rect.x + rectW / 2, rect.y, txt, fg); + var width = Math.max(rectW, tl.width); + var offsetX = (width - rectW) / 2; //ajust label text + translate(sub.items, offsetX + padding, 0); + + if (offsetX) translate([rect, tl.label], offsetX, 0); + var items = sub.items.concat([rect, tl.label]); + return { + items: items, + width: width, + height: rect.height + tl.height, + x: x, + y: tl.y, + lineInX: offsetX + sub.lineInX + padding, + lineOutX: offsetX + sub.lineOutX + padding, + }; + }, + }; + + function elideOK(a) { + if (Array.isArray(a)) { + //stack + var stack = a; + for (var i = 0; i < stack.length; i++) { + if (!elideOK(stack[i])) return false; + } + return true; + } + var node = a; + if (node.type === EMPTY_NODE) return true; + + if (node.type === GROUP_NODE && node.num === undefined) { + return elideOK(node.sub); + } + + if (node.type === CHOICE_NODE) { + return elideOK(node.branches); + } + } + + var hlColorMap = { + 'delimiter': 'Indigo', + 'flags': 'darkgreen', + 'exact': '#334', + 'dot': 'darkblue', + 'backref': 'teal', + '$': 'purple', + '^': 'purple', + '\\b': '#F30', + '\\B': '#F30', + '(': 'blue', + ')': 'blue', + '?=': 'darkgreen', + '?!': 'red', + '?:': 'grey', + '[': 'navy', + ']': 'navy', + '|': 'blue', + '{': 'maroon', + ',': 'maroon', + '}': 'maroon', + '*': 'maroon', + '+': 'maroon', + '?': 'maroon', + 'repeatNonGreedy': '#F61', + 'defaults': 'black', + 'charsetRange': 'olive', + 'charsetClass': 'navy', + 'charsetExclude': 'red', + 'charsetChars': '#534', + }; + + /** + @param {AST.tree} re AST.tree return by `parse` + */ + function highlight(tree) { + var texts = []; + tree.forEach(function (node) { + if (node.sub) { + texts.push(text('(')); + if (node.type === ASSERT_NODE) { + if (node.assertionType === AssertLookahead) { + texts.push(text('?=')); + } else { + texts.push(text('?!')); + } + } else if (node.nonCapture) { + texts.push(text('?:')); + } + texts = texts.concat(highlight(node.sub)); + texts.push(text(')')); + } else if (node.branches) { + node.branches.map(highlight).forEach(function (ts) { + texts = texts.concat(ts); + texts.push(text('|')); + }); + texts.pop(); + } else { + var color = hlColorMap[node.type] || hlColorMap.defaults; + switch (node.type) { + case CHARSET_NODE: + var simple = onlyCharClass(node); + (!simple || node.exclude) && texts.push(text('[')); + if (node.exclude) texts.push(text('^', hlColorMap.charsetExclude)); + node.ranges.forEach(function (rg) { + texts.push(text(_charsetEscape(rg[0] + '-' + rg[1]), hlColorMap.charsetRange)); + }); + node.classes.forEach(function (cls) { + texts.push(text('\\' + cls, hlColorMap.charsetClass)); + }); + texts.push(text(_charsetEscape(node.chars), hlColorMap.charsetChars)); + (!simple || node.exclude) && texts.push(text(']')); + break; + default: + var s = node.raw || ''; + if (node.repeat) s = s.slice(0, node.repeat.beginIndex); + s = K.toPrint(s, true); + texts.push(text(s, color)); + } + } + if (node.repeat) { + var min = node.repeat.min, + max = node.repeat.max; + if (min === 0 && max === Infinity) texts.push(text('*')); + else if (min === 1 && max === Infinity) texts.push(text('+')); + else if (min === 0 && max === 1) texts.push(text('?')); + else { + texts.push(text('{')); + texts.push(text(min)); + if (min === max) texts.push(text('}')); + else { + texts.push(text(',')); + if (isFinite(max)) texts.push(text(max)); + texts.push(text('}')); + } + } + if (node.repeat.nonGreedy) { + texts.push(text('?', hlColorMap.repeatNonGreedy)); + } + } + }); + return texts; + } + + function _charsetEscape(s) { + s = K.toPrint(s); + return s.replace(/\[/g, '\\[').replace(/\]/g, '\\]'); + } + + function text(s, color) { + color = color || hlColorMap[s] || hlColorMap.defaults; + return { + 'type': 'text', + 'font-size': FONT_SIZE, + 'font-family': FONT_FAMILY, + 'text': s + '', + 'fill': color, + 'text-anchor': 'start', + 'font-weight': 'bold', + }; + } + + function onlyCharClass(node) { + return !node.chars && !node.ranges.length && node.classes.length === 1; + } + + return visualize; +}); diff --git a/test/plugins/test.ts b/test/plugins/test.ts index c59ef8558..355594dc9 100644 --- a/test/plugins/test.ts +++ b/test/plugins/test.ts @@ -5,7 +5,7 @@ test.command('test-confirm').action(async runtime => { const isConfirm = await runtime.prompt.confirm('确认吗'); return `${isConfirm ? '已确认' : '已取消'}:${isConfirm} ${typeof isConfirm}`; }); -test.command('test-text').action(async ({ adapter, prompt }) => { +test.command('test-text [test:number] [abc:boolean]').action(async ({ adapter, prompt }, text) => { const input = await prompt.text('请输入文本'); return `inputResult:${input} ${typeof input}`; }); @@ -60,4 +60,88 @@ test.mounted(() => { }, }); }); +function xml2Json(xmlText: string) { + const parse = (xmlText: string) => { + xmlText = xmlText.replace(/\s+/g, ''); + const xmlRegexp = /<([^>]+)>([^<>]+)<\/\1>/; + const result: Record = {}; + while (xmlText.length) { + const match = xmlRegexp.exec(xmlText); + if (!match) break; + const [, tagName, content] = match; + if (/(\$\{[^}]+})/.test(content)) { + const [...matches] = content.matchAll(/(\$\{[^}]+})/g); + const keys = matches.map(([item]) => item); + const obj = Object.fromEntries( + keys + .map(key => { + return [key.slice(2, -1), result[key.slice(2, -1)]]; + }) + .filter(([_, value]) => value !== undefined), + ); + const ks = Object.keys(obj); + if (ks.length) { + result[tagName] = obj; + for (const key of ks) { + delete result[key]; + } + } else { + if (result[tagName]) { + result[tagName] = [result[tagName], content]; + } else { + result[tagName] = content; + } + } + delete result[content.slice(2, -1)]; + } else { + if (result[tagName]) { + result[tagName] = [result[tagName], content]; + } else { + result[tagName] = content; + } + } + xmlText = xmlText.replace(`<${tagName}>${content}<${tagName}>`, `\${${tagName}}`); + } + return result; + }; + if (!/^(.+)<\/xml>$/.test(xmlText)) throw new Error('Invalid XML string'); + const temp = xmlText.match(/^(.+)<\/xml>$/) as RegExpMatchArray; + return parse(temp[1]); +} +export function json2Xml(content: any, level = 0): string { + const _stringify = (content: any) => { + if (typeof content !== 'object') return content; + return Object.entries(content) + .map(([key, value]) => { + if (Array.isArray(value)) return value.map(v => `<${key}>${json2Xml(v, level + 1)}`).join('\n'); + if (typeof value === 'object') return `<${key}>\n${json2Xml(value, level + 1)}\n`; + return `<${key}>${value}`; + }) + .join('\n'); + }; + return `${_stringify(content)}`; +} +console.log( + xml2Json( + ` + +123 + + + b2 + + abc + + +`.trimStart(), + ), +); +console.log( + json2Xml({ + test: 123, + foo: { + bar: [{ a1: 'b2' }, 'abc'], + }, + }), +); export default test; diff --git a/test/src/App.vue b/test/src/App.vue index 74e29e764..5cf8388b4 100644 --- a/test/src/App.vue +++ b/test/src/App.vue @@ -1,4 +1,4 @@ - - +