├── .deepsource.toml
├── .github
└── workflows
│ ├── coverage.yml
│ └── node.js.yml
├── .gitignore
├── CONTRIBUTING.md
├── JavyInputAppendix.js
├── LICENSE.md
├── README.md
├── docs
├── .vitepress
│ ├── config.mjs
│ └── theme
│ │ ├── index.js
│ │ └── style.css
├── document
│ ├── CONTRIBUTING.md
│ ├── FAQ.md
│ ├── LICENSE.md
│ ├── bench.md
│ ├── best-practise.md
│ ├── character.md
│ ├── comparison.md
│ ├── demo-usage.md
│ ├── demo_next.png
│ ├── enc.md
│ ├── frontend-deploy.md
│ ├── js-deploy.md
│ ├── luhn-compress.md
│ ├── quick-start.md
│ ├── thanks.md
│ ├── wasm-deploy.md
│ └── wenyan.md
├── index.md
└── public
│ └── logo.png
├── package-lock.json
├── package.json
├── src
└── javascript
│ ├── ChineseMappingHelper.js
│ ├── CompressionHelper.js
│ ├── CoreHandler.js
│ ├── EncryptHelper.js
│ ├── Misc.js
│ ├── RoundObfusHelper.js
│ ├── main.d.ts
│ ├── main.js
│ ├── main.test.js
│ ├── mapping.json
│ ├── mapping_next.json
│ ├── mapping_next.test.js
│ └── unishox2.js
└── vite.config.js
/.deepsource.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 |
3 | exclude_patterns = ["src/javascript/unishox2.js"]
4 |
5 | [[analyzers]]
6 | name = "javascript"
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | name: 代码覆盖测试
2 |
3 | on:
4 | push:
5 | branches: ["main"]
6 |
7 | jobs:
8 | test:
9 | name: Run tests and collect coverage
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 2
16 |
17 | - name: Set up Node
18 | uses: actions/setup-node@v4
19 |
20 | - name: Install dependencies
21 | run: npm install
22 |
23 | - name: Run tests
24 | run: npx vitest run --coverage
25 |
26 | - name: Upload results to Codecov
27 | uses: codecov/codecov-action@v5
28 | with:
29 | token: ${{ secrets.CODECOV_TOKEN }}
30 | slug: SheepChef/Abracadabra
31 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: 自动集成
5 |
6 | on:
7 | push:
8 | branches: [ "main", "dev_nodejs" ]
9 | pull_request:
10 | branches: [ "main", "dev_nodejs" ]
11 |
12 | jobs:
13 | Build:
14 | runs-on: ubuntu-latest
15 | strategy:
16 | matrix:
17 | node-version: [20.x]
18 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: 使用 Node.js 版本 ${{ matrix.node-version }} 尝试构建
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | cache: 'npm'
27 | - run: npm ci
28 | - run: npm run build
29 | - name: 保存构建工件
30 | uses: actions/upload-artifact@v4
31 | with:
32 | name: Abracadabra-[${{ github.run_id }}]-Artifact.zip
33 | path: |
34 | dist
35 | !dist/**/*.md
36 |
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | abracadabra.exe
3 | main.exe
4 | node_modules
5 | abracadabra_linux_amd64
6 | abracadabra_win_amd64.exe
7 | abracadabra_linux_armv7
8 | abracadabra_linux_armv8
9 | test.html
10 | .prettierignore
11 | dist
12 | demo.zip
13 | text.html
14 | coverage
15 |
16 | /docs/.vitepress/cache
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Abracadabra 贡献指南
2 |
3 | ### 基本原则
4 |
5 | 本项目致力于实现高效的文本风格化加密技术,通过创新的字符映射方案提升信息表达的灵活性。
6 |
7 | # 贡献规则
8 |
9 | ### 贡献流程
10 |
11 | - 请在 dev 分支提交 Pull Request,等待维护者审核
12 | **禁止直接向 main 分支提交 PR,此类请求将被自动关闭**
13 | - 维护者将在 48 小时内提出代码改进建议(如有)
14 | - 通过审核的 PR 将合并到 dev 分支进行集成测试
15 | - 如果修改了文言映射表和句式,在提交 PR 前**必须**使用 `npm run test` 执行测试。
16 |
17 | ### 编码规范
18 |
19 | - 遵循 ES6+ 语法规范,禁用已被废弃的语法特性
20 | - 善用注释,复杂逻辑必须附加中文注释
21 | - 禁止修改 package.json 版本号字段
22 | - 保持模块化设计,新增模块须详细说明
23 | - 优先使用原生 API,避免不必要的第三方依赖
24 |
25 | ### 模块管理准则
26 |
27 | - 现有模块重构优于新建模块
28 | - 新依赖引入需在 PR 中提供必要性论证
29 | - 涉及性能的修改,需提供基准测试结果
30 |
31 | ### 密码表维护规则
32 |
33 | - **严格保持向后兼容**,现存映射关系永不删除
34 | - 新增映射前必须执行 `npm run test` 查重
35 | - **禁止添加**:
36 | - Unicode 扩展区汉字(U+20000 及之后)
37 | - 总笔画超过 18 画的字符
38 | - 存在输入法输入困难的字符
39 |
40 | ### 质量保障
41 |
42 | - 所有提交必须能够构建成功。
43 | - 涉及加密核心的修改必须提供详细说明
44 | - 重大功能改进应同步更新文档内容
45 |
--------------------------------------------------------------------------------
/JavyInputAppendix.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | // 这是在编译后附加到Artifact后面的额外内容
14 | // 使得Javy可以将其编译成WASM
15 |
16 | /*
17 |
18 | 传入的参数应当是JSON
19 |
20 | {
21 | "method":"", // WENYAN | OLD
22 | "inputType":"", // TEXT | UINT8
23 | "outputType":"", // TEXT | UINT8
24 | "input":"", // 输入的数据,如果是TEXT请直接输入纯文本,如果是任意字节,请输入Base64编码字符串
25 | "mode":"", // ENCRYPT | DECRYPT | AUTO // AUTO 仅在 method 指定 OLD 时合法
26 | "key":"", // 加密密钥,一个字符串 //如果缺省,自动使用默认值
27 | "q":bool, // OLD模式下,决定是否添加标志位
28 | "WenyanConfig":{ //文言文生成配置,解密时可以缺省。
29 | "PunctuationMark": bool;// 指定是否为密文添加标点符号,默认 true/添加;
30 | "RandomIndex":number, // 仅WENYAN模式下需要:算法的随机程度,越大随机性越强,默认 50,最大100,超过100将会出错;
31 | "PianwenMode":bool, // 仅WENYAN模式下需要:尽可能使用对仗的骈文句式; 与逻辑句式冲突
32 | "LogicMode":bool, // 仅WENYAN模式下需要:尽可能使用逻辑句式; 与骈文句式冲突
33 | "Traditional":bool, // 仅WENYAN模式下需要:输出繁体中文。
34 | },
35 | }
36 |
37 | */
38 |
39 | function base64ToUint8Array(base64) {
40 | // 将Base64字符串转换为二进制字符串
41 | const binaryString = _atob(base64);
42 | // 将二进制字符串转换为Uint8Array
43 | const len = binaryString.length;
44 | const bytes = new Uint8Array(len);
45 | for (let i = 0; i < len; i++) {
46 | bytes[i] = binaryString.charCodeAt(i);
47 | }
48 | return bytes;
49 | }
50 |
51 | function uint8ArrayToBase64(uint8Array) {
52 | return _btoa(String.fromCharCode.apply(null, uint8Array));
53 | }
54 |
55 | // Read input from stdin
56 | const Javyinput = JavyreadInput();
57 | // Call the function with the input
58 | const Javyresult = index(Javyinput);
59 | // Write the result to stdout
60 | JavywriteOutput(Javyresult);
61 |
62 | // The main function.
63 | function index(input) {
64 | if (input === "ERROR") {
65 | return "INCORRECT JSON";
66 | }
67 |
68 | if (input.method == "WENYAN") {
69 | if (input.inputType == "TEXT") {
70 | let Abra = new Abracadabra(input.inputType, input.outputType);
71 | Abra.WenyanInput(input.input, input.mode, input.key, input.WenyanConfig);
72 | let Output = Abra.Output();
73 | if (input.outputType == "UINT8") {
74 | Output = uint8ArrayToBase64(Output);
75 | }
76 | return Output;
77 | } else if (input.inputType == "UINT8") {
78 | let Abra = new Abracadabra(input.inputType, input.outputType);
79 | let UINT8In = base64ToUint8Array(input.input);
80 | Abra.WenyanInput(UINT8In, input.mode, input.key, input.WenyanConfig);
81 | let Output = Abra.Output();
82 | if (input.outputType == "UINT8") {
83 | Output = uint8ArrayToBase64(Output);
84 | }
85 | return Output;
86 | } else {
87 | return "ERROR inputType";
88 | }
89 | } else if (input.method == "OLD") {
90 | if (input.inputType == "TEXT") {
91 | let Abra = new Abracadabra(input.inputType, input.outputType);
92 | Abra.Input(input.input, input.mode, input.key, input.q);
93 | let Output = Abra.Output();
94 | if (input.outputType == "UINT8") {
95 | Output = uint8ArrayToBase64(Output);
96 | }
97 | return Output;
98 | } else if (input.inputType == "UINT8") {
99 | let Abra = new Abracadabra(input.inputType, input.outputType);
100 | let UINT8In = base64ToUint8Array(input.input);
101 | Abra.Input(UINT8In, input.mode, input.key, input.q);
102 | let Output = Abra.Output();
103 | if (input.outputType == "UINT8") {
104 | Output = uint8ArrayToBase64(Output);
105 | }
106 | return Output;
107 | } else {
108 | return "ERROR inputType";
109 | }
110 | } else {
111 | return "ERROR method";
112 | }
113 | }
114 |
115 | // Read input from stdin
116 | function JavyreadInput() {
117 | const chunkSize = 1024;
118 | const inputChunks = [];
119 | let totalBytes = 0;
120 |
121 | // Read all the available bytes
122 | while (1) {
123 | const buffer = new Uint8Array(chunkSize);
124 | // Stdin file descriptor
125 | const fd = 0;
126 | const bytesRead = Javy.IO.readSync(fd, buffer);
127 |
128 | totalBytes += bytesRead;
129 | if (bytesRead === 0) {
130 | break;
131 | }
132 | inputChunks.push(buffer.subarray(0, bytesRead));
133 | }
134 |
135 | // Assemble input into a single Uint8Array
136 | const { finalBuffer } = inputChunks.reduce(
137 | (context, chunk) => {
138 | context.finalBuffer.set(chunk, context.bufferOffset);
139 | context.bufferOffset += chunk.length;
140 | return context;
141 | },
142 | { bufferOffset: 0, finalBuffer: new Uint8Array(totalBytes) }
143 | );
144 |
145 | const InputDecoded = new TextDecoder().decode(finalBuffer);
146 | try {
147 | return JSON.parse(InputDecoded);
148 | } catch {
149 | return "ERROR";
150 | }
151 | }
152 |
153 | // Write output to stdout
154 | function JavywriteOutput(output) {
155 | const encodedOutput = new TextEncoder().encode(JSON.stringify(output));
156 | const buffer = new Uint8Array(encodedOutput);
157 | // Stdout file descriptor
158 | const fd = 1;
159 | Javy.IO.writeSync(fd, buffer);
160 | }
161 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | **Academic Innovation Protection License 1.1**
2 | **学术创新保护许可证 1.1 (AIPL-1.1)**
3 |
4 | **版权所有 (C) 2025 SheepChef**
5 |
6 | **序言**
7 |
8 | 在开放源代码软件项目遭到严重抄袭和滥用,大量作者的权益遭受侵犯的背景下,本《学术创新保护许可证》(AIPL)旨在遏制开源作品遭到不正当的使用,与此同时保证大多数正常用户,开源作者和协作者的权利。
9 |
10 | 本许可证授予被授权人有条件的非商业使用权,严格保障著作权人的学术优先权与成果控制权,限制未经许可的竞争性学术利用,商业利用及专利主张行为。
11 |
12 | 针对学术利用,本许可证仅限制被授权人依赖受保护项目获取学术声誉和学术利益。私下研究的学术自由不受限制。
13 |
14 | 针对商业利用,本许可证限制被授权人依赖受保护项目获取直接或间接经济利益,参加商业竞赛,获得第三方投资等。特别地,针对媒体和艺术衍生作品,被授权人享有创作、发布和盈利自由,但著作权人保留终止协议的权利。
15 |
16 | 本许可证为独立授权框架,与现有主流开源协议体系 (GPL 等) 不兼容,使用者应知悉其特殊限制属性。使用本许可证保护的项目,不再是符合开源精神的项目,不符合开源定义(Open Source Definition)。
17 |
18 | 第三方组件的权利与义务由其原始许可证独立管辖,本协议仅约束著作权人声明的原创内容。
19 |
20 | ---
21 |
22 | **第 1 条 定义**
23 |
24 | 1.1 "本作品"指由著作权人依本许可证发布的原创性代码、文档及相关材料,不含明确标注的第三方组件。
25 | 1.2 "衍生作品"指符合下列任一情形的作品:
26 | (a) 全部或部分包含本作品原始表达元素的任何形式的成果,无论其载体形式或技术实现方式;
27 | (b) 与本作品形成单一功能性整体的成果,包括但不限于:
28 | - 通过静态链接/动态链接调用本作品功能模块;
29 | - 作为微服务架构中的依赖组件;
30 | - 以软件即服务(SaaS)形式提供本作品核心功能;
31 | (c) 任何实质上完全依赖本作品技术方案的成果,包括但不限于:
32 | - 通过反编译/反汇编获得的功能等效实现;
33 | 1.3 "学术发表"指在任何具有 ISSN/ISBN/DOI 等标识的正式学术渠道中,或在任何可能被正式学术渠道引用的平台(包括但不限于 arXiv)中,或在第 1.7 条中定义的学术竞赛中,公开发表包含本作品或其衍生作品的任何内容的行为。
34 | 1.4 "著作权人"指对本作品拥有**著作人格权**的自然人、法人或其他法律实体。
35 | 1.5 "被授权人"指根据本许可证条款获得使用、修改、分发代码等权利的主体。
36 | 1.6 "商业竞赛"指由任何商业实体作为主办、承办、出资或冠名单位的竞赛。
37 | 1.7 "学术竞赛"指符合以下任一条件的竞赛:
38 | (a) 由任何获得所在国家或地区主管部门认可的社会组织或学术机构(包括但不限于大学,研究院,科学协会等)作为主办、承办、出资或冠名单位的竞赛。
39 | (b) 由所在国家或地区的市级以上教育主管部门作为主办、承办、出资或冠名单位的竞赛。
40 | 1.8 "本许可证"指 AIPL 学术创新保护许可证第 1.1 版。
41 | 1.9 作品的 "源代码" 指对作品进行修改所首选的作品形式。
42 |
43 | ---
44 |
45 | **第 2 条 基础授权**
46 |
47 | 2.1 在不违反本许可证条款的前提下,被授权人享有以下非独占权利:
48 | (a) 复制、修改、执行本作品;
49 | (b) 在遵守本许可证第 6 条的前提下,使用本作品进行学术研究;
50 | (c) 依本许可证第 3 条分发衍生作品。
51 |
52 | 2.2 著作权人可自由使用其在本作品中的原创内容,不受本许可证限制。若本作品为多位著作权人的合作作品,则:
53 | (a) 各方对自身创作部分保留上述自由使用权;
54 | (b) 使用他人创作内容时,仍须以被授权人身份遵守本许可证条款。
55 |
56 | ---
57 |
58 | **第 3 条 分发**
59 |
60 | 3.1 被授权人分发本作品或其衍生作品**必须**同时满足:
61 | (a) 以显著方式提供完整的原始版权声明。
62 | (b) 公开提供完整的对应源代码,并确保不得采用混淆、加密或其他阻碍技术审查的手段。
63 | (c) 以显著方式标注本许可证名称及版本号。
64 | (d) 采用 AIPL-1.1 许可证。
65 |
66 | 3.2 被授权人**不得**以提供互联网服务为由,拒绝以可获取的方式提供本作品或其衍生作品的全部对应源代码。
67 |
68 | 3.3 禁止被授权人附加任何额外限制条款。
69 |
70 | ---
71 |
72 | **第 4 条 商业活动限制**
73 |
74 | 4.1 未经著作权人书面授权,**禁止**被授权人将本作品或其衍生作品用于:
75 | (a) 直接或间接获取经济利益;
76 | (b) 商业产品开发或服务运营;
77 | (c) 提升企业资产价值;
78 | (d) 竞标,投标等商业竞争行为;
79 | (e) 第 1.6 条中定义的商业竞赛;
80 | (f) 获取任意形式的第三方投资;
81 | (g) 其他任何具有商业性质的行为。
82 |
83 | 4.2 被授权人使用本作品或其衍生作品创作媒体或艺术作品,遵守以下全部条款的,无论是否获得经济利益,无需提前获得商业授权。著作权人保留对相关被授权人终止许可的权利:
84 | (a) 不得使用相关作品参加第 1.6 条中定义的商业竞赛;
85 | (b) 不得以任何方式损害著作权人的合法权益;
86 |
87 | 4.3 被授权人将本作品用于第 4.1 条规定的商业用途前,必须取得著作权人签署的纸质许可文件。
88 |
89 | 4.4 被授权人若将本作品集成至商业产品中,须自行履行相关法律规定的安全评估义务,包括但不限于向监管部门备案、配合执法机关依法调取数据等法定义务。
90 |
91 | ---
92 |
93 | **第 5 条 专利权限制**
94 |
95 | 5.1 被授权人使用本作品,即视为被授权人承诺不就本作品技术方案主张专利保护。
96 |
97 | 5.2 若被授权人持有与本作品相关的有效专利,应:
98 | (a) 授予所有本作品使用者免许可费的实施权;
99 | (b) 不得主张专利侵权。
100 |
101 | 5.3 任何由被授权人发起的针对本作品的专利侵权诉讼,将导致本许可终止。
102 |
103 | ---
104 |
105 | **第 6 条 竞争性学术使用限制**
106 |
107 | 6.1 未经书面"竞争性学术使用"授权,在本作品著作财产权有效期内,**禁止**被授权人将本作品或其衍生作品用于:
108 | (a) 任何为被授权人获取学术荣誉或资格的行为,如撰写学位论文,完成毕业设计等;
109 | (b) 参加第 1.7 条中定义的学术竞赛;
110 | (c) 进行第 1.3 条中定义的学术发表;
111 |
112 | 6.2 被授权人取得"竞争性学术使用"授权,必须满足以下所有条件:
113 | (a) 提交《竞争性学术使用申请书》,包含:
114 | - 个人的身份证明或组织的资质证明;
115 | - 拟使用本作品的范围,以及详细用途。
116 | (b) 收到著作权人作出的书面许可;
117 | (c) 被授权人获得"竞争性学术使用"授权后,仍须依本许可证第 3 条分发衍生作品。
118 |
119 | 6.3 在遵守第 6.1 条的前提下,被授权人将本作品或其衍生作品用于其他学术用途 (例如:授课,布置作业等),不受限制。
120 |
121 | ---
122 |
123 | **第 7 条 适用排除**
124 |
125 | 7.1 本许可证不适用于明确标注不在本许可证保护范围内的内容。
126 |
127 | 7.2 不在本许可证保护范围内的内容,遵守其原始许可证条款。
128 |
129 | ---
130 |
131 | **第 8 条 法律责任**
132 |
133 | 8.1 **禁止**被授权人将本作品或其衍生作品用于违反中华人民共和国法律的用途,包括但不限于:
134 | (a) 网络诈骗;
135 | (b) 洗钱;
136 | (c) 其他违法行为。
137 |
138 | 8.2 在使用本作品及其衍生作品前,被授权人需承诺其应用场景符合相关法律法规。
139 |
140 | 8.3 **著作权人明确不参与、不知晓且不认可任何非法使用行为**。任何使用本作品产生的法律后果由使用者自行承担。在适用法律允许的最大范围内,著作权人不承担因本作品被非法使用而导致的任何直接或间接法律责任。
141 |
142 | 8.4 著作权人有权对被授权人的违法滥用行为采取反制措施,包括但不限于:
143 | (a) 法律追诉;
144 | (b) 终止本许可证授予的权利;
145 | (c) 依法向有关部门举报。
146 |
147 | 8.5 本作品"按原样"提供,不包含任何形式的明示或默示保证,包括但不限于适销性、特定目的适用性及不侵权的保证。在任何情况下,无论是在合同、侵权或其他案件中,著作权人均不对因本作品、或因本作品的使用或其他利用而引起的、引发的或与之相关的任何权利主张、损害赔偿或其他责任承担责任。
148 |
149 | ---
150 |
151 | **第 9 条 许可终止**
152 |
153 | 9.1 被授权人违反本许可证的任一条款将自动导致许可终止,且必须:
154 | (a) 销毁所有作品副本;
155 | (b) 撤回已发布的衍生作品;
156 | (c) 撤回已发布的相关媒体或艺术作品;
157 | (d) 在相关学术数据库发布撤销声明;
158 | (e) 向相关竞赛组委会/学术机构提交违规使用告知函;
159 | (f) 如涉嫌违法或侵权,自行承担相关法律责任。
160 |
161 | 9.2 第 5 条所规定的专利条款的效力,不因许可终止而失效。
162 |
163 | ---
164 |
165 | **第 10 条 法律管辖和溯及力**
166 |
167 | 10.1 本许可证适用中华人民共和国法律,排除其国际私法。
168 |
169 | 10.2 本许可证适用于本作品自 2024 年 9 月 1 日 以来公开发布的所有版本和衍生版本。对于版本发布时使用不同许可证的,以本许可证为准。
170 |
171 | ---
172 |
173 | 本许可证文本以 CC-NC-ND 4.0 协议许可
174 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Abracadabra 魔曰
2 |
3 | <div align=center>
4 | <img src="https://github.com/user-attachments/assets/4c6544fe-166b-4572-acd6-cd1d6d3b4ca0" width="20%">
5 | </div>
6 |
7 | <div align=center>
8 | <h3>Abracadabra 魔曰</h3>
9 |
10 | <h3>熔古铸今,韵入密语</h3>
11 | </div>
12 |
13 | <div align=center>
14 |
15 | [<img src="https://img.shields.io/badge/license-AIPL%201.1-yellow"/>](LICENSE.md)
16 | 
17 | 
18 |
19 | [<img src="https://img.shields.io/github/v/release/SheepChef/Abracadabra?color=00aaff"/>](https://github.com/SheepChef/Abracadabra/releases/latest)
20 | [<img src="https://img.shields.io/github/actions/workflow/status/SheepChef/Abracadabra/node.js.yml?branch=main&label=%E6%9E%84%E5%BB%BA"/>](https://github.com/SheepChef/Abracadabra/actions/workflows/node.js.yml)
21 | [<img src="https://img.shields.io/codecov/c/github/SheepChef/Abracadabra?label=%E8%A6%86%E7%9B%96%E7%8E%87"/>](https://github.com/SheepChef/Abracadabra/actions/workflows/coverage.yml)
22 | <a href="https://hellogithub.com/repository/6d67b7be3ccc43419924dbe40b31e937" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=6d67b7be3ccc43419924dbe40b31e937&claim_uid=cQrPYdpGNg4ACK6&theme=small" alt="Featured|HelloGitHub" /></a>
23 | 
24 |
25 | </div>
26 |
27 | <div align=center>
28 |
29 | [<img src="https://img.shields.io/badge/立刻使用-ffd91c?logo=cloudflarepages&style=for-the-badge&logoColor=000000" width="170"/>](https://abra.js.org/)
30 | [<img src="https://img.shields.io/badge/下载插件-8a54ff?logo=googlechrome&style=for-the-badge&logoColor=ffffff" width="170" />](#浏览器插件)
31 |
32 | [<img src="https://img.shields.io/badge/项目主页-54ffac?logo=javascript&style=for-the-badge&logoColor=000000" width="117" />](https://abracadabra.js.org)
33 | [<img src="https://img.shields.io/badge/前端源码仓库-9a10b5?style=for-the-badge" width="120" />](https://github.com/SheepChef/Abracadabra_demo)
34 | [<img src="https://img.shields.io/badge/更新频道-0970ba?logo=telegram&style=for-the-badge&logoColor=ffffff" width="118" />](https://t.me/abracadabra_cn)
35 |
36 | </div>
37 |
38 | **Abracadabra(魔曰)** 是开源,安全,高效的文本加密工具。
39 | 将数据加密为汉字构成的文言文,完全开源,易于部署,易于使用。
40 |
41 | ---
42 |
43 | ✨ 查阅 [**快速使用**](#快速使用) 一节,快速开始使用/部署本项目。
44 | ✨ 查阅 [**项目主页**](https://abracadabra.js.org),了解本项目的技术特点和细节。
45 |
46 | 👉 查阅 [**开放源代码许可**](#开放源代码许可) 一节,了解本项目的依赖项和许可证。
47 |
48 | ## 特性
49 |
50 | - **仿真,使用文言语法句式**。
51 | - 开源,所有源代码公开可查。
52 | - 安全,完全离线的AES加密。
53 | - 可靠,代码经过严格单元测试。
54 | - 便捷,易于本地部署和使用。
55 |
56 | ---
57 |
58 | ### **熔古铸今:文言文仿真加密**
59 |
60 | > 镜有能弹,雨有谧然。以语,当笑速夜,非花称雪所将湛流。不应事也,畅则为动礼,迷则为达鲤,然则绣雪自恋致矣。
61 | >
62 | > 此棋有南涧迷森,悠雨清琴。遥家为鸢兮,宏梦为鹤。或指林写岩,进恋于雨,是语也,鸳极驿安,璃慧空舒。况请瀚者宏,是故无和无后,无安无舒,空之所连、城之所见也。光语筑天,良于璃韵,安铃振灯,局文放花。是故无余无青,无遥无寒,语之所事、琴之所歌也。虽火俊茶长,所以赴雨,其空速也,或弹物任绸,弹楼于声。
63 |
64 | 构造高仿真文言文,**参考《古文观止》《经史百家杂钞》《古文辞类纂》等古代典籍。**
65 | 标准 AES256 加密,引入更复杂的组句、语法匹配机制,将密码和中国古典文言文相融合。
66 |
67 | 密文高度随机,支持用户自定义随机性和文本风格偏好,打造前所未有的跨文化数字加密方案。
68 |
69 | <div style="width: 350px; height: 57px; border: 1px solid #BBBBBB;"><a href="https://ctext.org/zhs"><img src="https://ctext.org/logos/ctplogo6.gif" border="0" alt="中国哲学书电子化计划" /></a></div>
70 |
71 | ## 快速使用
72 |
73 | 请查阅 [**项目主页**](https://abracadabra.js.org) ,详细了解使用/部署方法。
74 |
75 | ### 静态页面 / 前端源码
76 |
77 | 本项目有自动托管在Cloudflare Pages的静态页面可供直接使用。
78 |
79 | 如果你想自行快速部署这个静态页,可以在Release中下载快速部署文件包。若要自行编译或修改,请前往前端源代码仓库。
80 |
81 | 浏览器插件的源码同样在前端源代码仓库,位于 crx 分支。
82 |
83 | [<img src="https://img.shields.io/badge/静态页面-ffd91c?logo=cloudflarepages&style=for-the-badge&logoColor=000000" width="130"/>](https://abra.js.org/)
84 | [<img src="https://img.shields.io/badge/前端源码-9a10b5?style=for-the-badge" width="103" />](https://github.com/SheepChef/Abracadabra_demo)
85 |
86 | ### 浏览器插件
87 |
88 | 浏览器插件基于本项目的 JavaScript 实现。
89 |
90 | 已上架 **Chrome WebStore**, **Edge 加载项** 和 **Firefox 扩展**。
91 |
92 | 如果不方便访问Chrome插件商店,也可以访问Edge插件商店,和Firefox扩展商店。
93 |
94 | [<img src="https://img.shields.io/badge/Chrome 商店-8a54ff?logo=chromewebstore&style=for-the-badge&logoColor=ffffff" width="171" />](https://chrome.google.com/webstore/detail/jgmlgdoefnmlealmfmhjhnoiejaifpko)
95 | [<img src="https://img.shields.io/badge/MSEdge 商店-8a54ff?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMyAyMyI+CiAgICA8cGF0aCBmaWxsPSIjZjNmM2YzIiBkPSJNMCAwaDIzdjIzSDB6Ii8+CiAgICA8cGF0aCBmaWxsPSIjZjM1MzI1IiBkPSJNMSAxaDEwdjEwSDF6Ii8+CiAgICA8cGF0aCBmaWxsPSIjODFiYzA2IiBkPSJNMTIgMWgxMHYxMEgxMnoiLz4KICAgIDxwYXRoIGZpbGw9IiMwNWE2ZjAiIGQ9Ik0xIDEyaDEwdjEwSDF6Ii8+CiAgICA8cGF0aCBmaWxsPSIjZmZiYTA4IiBkPSJNMTIgMTJoMTB2MTBIMTJ6Ii8+Cjwvc3ZnPg==&style=for-the-badge&logoColor=ffffff" width="170" />](https://microsoftedge.microsoft.com/addons/detail/abracadabra-%E9%AD%94%E6%9B%B0/kfkmhdcahjblddpkkmnjeppmfmfoihkb)
96 | [<img src="https://img.shields.io/badge/Firefox 商店-8a54ff?logo=firefoxbrowser&style=for-the-badge&logoColor=ffffff" width="174" />](https://addons.mozilla.org/zh-CN/firefox/addon/abracadabra-%E9%AD%94%E6%9B%B0/)
97 |
98 | > **提示:Edge 插件商店的上架审核速度十分缓慢,因此更新速度也更慢。不推荐从Edge商店下载本插件。**
99 |
100 | ### Android 客户端
101 |
102 | 本项目的 Android 客户端完全在 WebView 中静态运行。
103 |
104 | 
105 |
106 | APK使用HBuilderX自动打包,**完全离线运行,没有自动更新等配套功能**。
107 |
108 | 功能和界面均和前端静态网页没有差异。
109 |
110 | APK文件可以 [**在 Release 中下载**](https://github.com/SheepChef/Abracadabra/releases/latest)
111 |
112 | ## 细节概要
113 |
114 | 请查阅 [**项目主页**](https://abracadabra.js.org) 了解更多。
115 |
116 | [](https://deepwiki.com/SheepChef/Abracadabra)
117 |
118 | ### 加解密过程
119 |
120 | ```
121 | 明文 -> 压缩 -> AES-256-CTR -> Base64 -> 三重转轮 -> 映射汉字 -> 组句(仅仿真加密时) -> 密文
122 |
123 | 密文 -> 解仿真(仅仿真加密) -> 转轮逆映射 -> Base64 -> AES-256-CTR 解密 -> 解压缩 -> 明文
124 | ```
125 |
126 | ### 映射表
127 |
128 | Abracadabra 以最常用的 3000 个汉字为密本,对大小写拉丁字母,阿拉伯数字和部分符号进行映射。
129 |
130 | 密表为纯人工编纂,没有让人眼花缭乱的生僻字。
131 |
132 | 映射表公开可查,查阅 [**映射表(传统)**](https://github.com/SheepChef/Abracadabra/blob/main/src/javascript/mapping.json) 或者 [**映射表(仿真)**](https://github.com/SheepChef/Abracadabra/blob/main/src/javascript/mapping_next.json) 以了解密本的全貌。
133 |
134 | ### AES 加密
135 |
136 | 核心安全性由久经考验的 AES 加密算法提供,采用无填充的AES-256-CTR,节省密文长度。
137 |
138 | AES 加密密钥和转轮密钥是同一个,均采用哈希值。
139 |
140 | ### 三重转轮混淆
141 |
142 | 模拟古老的转轮,每次加密均会对密本映射进行偏移。
143 |
144 | 简言之,程序会将给定的密钥进行 SHA256,得到一个长度为 32 的 Uint8_t 数组。
145 |
146 | 这个数组中的每个数字,都会决定三重转轮中每个转轮每次迭代的转动方向和转动距离。
147 |
148 | 数字/符号,字母分别拥有一套转轮,即总共六个转轮,改变密钥相当于更换一套完全不同的转轮。
149 |
150 | 转轮显著增加了 Base64 密文的安全性,查阅 [**项目主页**](https://abracadabra.js.org/document/enc.html#三重转轮混淆) 来了解转轮的详细运行机制。
151 |
152 | ### 压缩
153 |
154 | 为了削减密文的长度,每次加密前会对数据进行智能压缩。
155 |
156 | 针对短文本,采用专门为短文本优化的 Unishox2 压缩算法。
157 | 一般数据(>1KB)则采用GZIP。
158 |
159 | 压缩后会执行效率验证,如果出现无效压缩,则自动回落到原始数据。
160 |
161 | ## 密文对比
162 |
163 | ```
164 | 明文:Abracadabra
165 |
166 | 魔曰(仿真):不应报也。树将棋之,书曰:“天水探火,临于云楼” ,夜乃写定绸之莺,指之不为火,换之不为苗。
167 |
168 | 魔曰(仿真):流霞以停空,局返,作文换雪。不可彰也,火之无灯,璃说之文,智鸢湛事。
169 |
170 | 魔曰(传统):桨捷欤网炯棠囍设声沢仅氖城织把夹短阐瑞玖祉作
171 |
172 | <-- ↓↓对比项目↓↓ -->
173 |
174 | 熊曰:呋食性類啽家現出爾常肉嘿達嗷很
175 | 佛曰:諸南隸僧南降南吽諸陀南摩隸南僧南缽南薩咤南心彌嚴迦聞婆吽願南眾南色南兜南眾南如婆如南
176 | 社会主义:自由民主公正文明法治文明公正民主公正和谐公正民主公正自由公正民主公正文明法治文明公正民主
177 | 兽音:~呜嗷嗷嗷嗷呜呜啊呜嗷呜嗷呜呜~嗷啊呜啊嗷啊呜嗷呜~呜~嗷~呜呜嗷嗷嗷嗷嗷嗷呜啊嗷呜啊呜嗷呜呜~嗷啊嗷啊嗷啊呜嗷嗷~~~嗷~呜呜嗷嗷嗷嗷嗷嗷呜啊嗷呜呜呜嗷呜呜~呜啊呜啊嗷啊呜嗷嗷~嗷啊
178 | 火星文:(不支持英文)
179 |
180 | ```
181 |
182 | ## 鸣谢
183 |
184 | 感谢 [**Unishox2**](https://github.com/siara-cc/Unishox2) 提供高效的短文本压缩方案。
185 |
186 | 感谢 [**中国哲学书电子化计划**](http://ctext.org/zhs) 提供高质量的古籍参考资料。
187 |
188 | 感谢 [**JS.ORG**](https://js.org) 为本项目提供域名支持。
189 |
190 | 感谢 [**@Amlkiller**](https://github.com/amlkiller) 为本项目提供十分有价值的反馈和建议。
191 |
192 | 感谢 **熊曰(与熊论道)、佛曰、兽音译者** 为本项目提供灵感和参考。
193 |
194 | 感谢贡献 PR 和参与测试的其他所有人,以及**正在使用本项目的您**。
195 |
196 | ## 开放源代码许可
197 |
198 | **⚠️本项目受私有许可证保护**,使用本项目则默认视为同意并遵守相关条款。禁止将本项目用于非法用途。
199 | 👉 查阅 [**AIPL-1.1 许可**](LICENSE.md) 来了解详细信息,也可以前往 [**#87**](https://github.com/SheepChef/Abracadabra/issues/87) 查阅简单介绍。
200 |
201 | ---
202 |
203 | 以下是本项目的依赖项:
204 |
205 | - [**Unishox2**](https://github.com/siara-cc/Unishox2) 短字符串压缩实现 _©Siara-cc_, **Apache-2.0** License.
206 | - [**crypto-js**](https://github.com/brix/crypto-js) AES加密实现 _©Jeff Mott/Evan Vosberg_, **MIT** License.
207 | - [**pako**](https://github.com/nodeca/pako) GZIP压缩实现 _©Vitaly Puzrin/Andrei Tuputcyn_, **MIT** License.
208 | - [**js-base64**](https://github.com/dankogai/js-base64) Base64编码工具实现 _©Dan Kogai_, **BSD-3-Clause** License.
209 | - [**mersenne-twister**](https://github.com/boo1ean/mersenne-twister) 梅森旋转算法实现 _©Makoto Matsumoto/Takuji Nishimura_, **BSD-3-Clause** License.
210 | - [**opencc-js**](https://github.com/nk2028/opencc-js) 简繁体转换实现 _©nk2028_, **MIT** License.
211 |
212 | 本项目许可证与所有依赖项的许可证兼容。
213 |
214 | ## Star History
215 |
216 | [](https://star-history.com/#SheepChef/Abracadabra&Date)
217 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitepress";
2 | import { withMermaid } from "vitepress-plugin-mermaid";
3 |
4 | // https://vitepress.dev/reference/site-config
5 | export default withMermaid({
6 | lang: "zh-CN",
7 | title: "Abracadabra 魔曰",
8 | description:
9 | "Abracadabra 魔曰是安全,高效的文本加密工具,可以将数据加密为汉字构成的文言文。",
10 | head: [
11 | ["link", { rel: "icon", href: "/logo.png" }],
12 | [
13 | "meta",
14 | {
15 | name: "keywords",
16 | content:
17 | "加密, 文本加密, 链接加密, 隐私工具, 数据安全, 安全, 网络安全, 密码, 汉字编码, 中文",
18 | },
19 | ],
20 | ["meta", { name: "author", content: "SheepChef" }],
21 | [
22 | "meta",
23 | {
24 | property: "og:title",
25 | content: "魔曰 — 古文风文本加密工具",
26 | },
27 | ],
28 | [
29 | "meta",
30 | {
31 | property: "og:description",
32 | content:
33 | "Abracadabra 魔曰,是安全高效的文本加密工具,可以将数据加密为汉字构成的文言文",
34 | },
35 | ],
36 | ["meta", { property: "og:url", content: "https://abracadabra.js.org" }],
37 | [
38 | "meta",
39 | { property: "og:image", content: "https://abracadabra.js.org/logo.png" },
40 | ],
41 | [
42 | "meta",
43 | {
44 | name: "google-site-verification",
45 | content: "NDhYJs2rXcRRZ4pfZBJxmshD0CQ8iYWc6p7YHT0ArG4",
46 | },
47 | ],
48 | ],
49 |
50 | themeConfig: {
51 | nav: [
52 | { text: "主页", link: "/" },
53 | { text: "文档", link: "/document/quick-start.md" },
54 | { text: "Demo", link: "https://abra.js.org" },
55 | ],
56 |
57 | sidebar: [
58 | { text: "快速开始", link: "/document/quick-start.md" },
59 | { text: "功能对比", link: "/document/comparison.md" },
60 | {
61 | text: "编译和部署",
62 | collapsed: false,
63 | items: [
64 | { text: "JavaScript", link: "/document/js-deploy.md" },
65 | { text: "WebAssembly(CLI)", link: "/document/wasm-deploy.md" },
66 | {
67 | text: "前端页面和浏览器插件",
68 | link: "/document/frontend-deploy.md",
69 | },
70 | ],
71 | },
72 | {
73 | text: "技术细节",
74 | collapsed: false,
75 | items: [
76 | { text: "压缩和校验管线", link: "/document/luhn-compress.md" },
77 | { text: "加密和混淆管线", link: "/document/enc.md" },
78 | { text: "字符映射管线", link: "/document/character.md" },
79 | { text: "文言文仿真管线", link: "/document/wenyan.md" },
80 | ],
81 | },
82 | {
83 | text: "使用指引",
84 | collapsed: false,
85 | items: [
86 | { text: "Demo 使用指南", link: "/document/demo-usage.md" },
87 | { text: "最佳操作实践", link: "/document/best-practise.md" },
88 | { text: "常见问题和使用提示", link: "/document/FAQ.md" },
89 | ],
90 | },
91 | { text: "AI基准测试", link: "/document/bench.md" },
92 | { text: "鸣谢", link: "/document/thanks.md" },
93 | { text: "Demo页", link: "https://abra.js.org" },
94 | { text: "GitHub仓库", link: "https://github.com/SheepChef/Abracadabra" },
95 | ],
96 | logo: "/logo.png",
97 | socialLinks: [
98 | { icon: "github", link: "https://github.com/SheepChef/Abracadabra" },
99 | ],
100 |
101 | // 文章翻页
102 | docFooter: {
103 | prev: "上一篇",
104 | next: "下一篇",
105 | },
106 |
107 | // 移动端 - 外观
108 | darkModeSwitchLabel: "外观",
109 | outlineTitle: "本页目录",
110 |
111 | // 移动端 - 返回顶部
112 | returnToTopLabel: "返回顶部",
113 |
114 | // 移动端 - menu
115 | sidebarMenuLabel: "菜单",
116 | footer: {
117 | message: "中国制造 • AIPL-1.1许可",
118 | copyright:
119 | "Copyright © 2025-present <a href='https://shef.cc' target='_blank'>SheepChef</a>",
120 | },
121 | },
122 | markdown: {
123 | image: {
124 | lazyLoading: true,
125 | },
126 | },
127 | });
128 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.js:
--------------------------------------------------------------------------------
1 | // https://vitepress.dev/guide/custom-theme
2 | import { h } from "vue";
3 | import DefaultTheme from "vitepress/theme";
4 | import "./style.css";
5 |
6 | /** @type {import('vitepress').Theme} */
7 | export default {
8 | extends: DefaultTheme,
9 | Layout: () => {
10 | return h(DefaultTheme.Layout, null, {
11 | // https://vitepress.dev/guide/extending-default-theme#layout-slots
12 | });
13 | },
14 | enhanceApp({ app }) {},
15 | };
16 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/style.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Customize default theme styling by overriding CSS variables:
3 | * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
4 | */
5 |
6 | /**
7 | * Colors
8 | *
9 | * Each colors have exact same color scale system with 3 levels of solid
10 | * colors with different brightness, and 1 soft color.
11 | *
12 | * - `XXX-1`: The most solid color used mainly for colored text. It must
13 | * satisfy the contrast ratio against when used on top of `XXX-soft`.
14 | *
15 | * - `XXX-2`: The color used mainly for hover state of the button.
16 | *
17 | * - `XXX-3`: The color for solid background, such as bg color of the button.
18 | * It must satisfy the contrast ratio with pure white (#ffffff) text on
19 | * top of it.
20 | *
21 | * - `XXX-soft`: The color used for subtle background such as custom container
22 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
23 | * on top of it.
24 | *
25 | * The soft color must be semi transparent alpha channel. This is crucial
26 | * because it allows adding multiple "soft" colors on top of each other
27 | * to create a accent, such as when having inline code block inside
28 | * custom containers.
29 | *
30 | * - `default`: The color used purely for subtle indication without any
31 | * special meanings attached to it such as bg color for menu hover state.
32 | *
33 | * - `brand`: Used for primary brand colors, such as link text, button with
34 | * brand theme, etc.
35 | *
36 | * - `tip`: Used to indicate useful information. The default theme uses the
37 | * brand color for this by default.
38 | *
39 | * - `warning`: Used to indicate warning to the users. Used in custom
40 | * container, badges, etc.
41 | *
42 | * - `danger`: Used to show error, or dangerous message to the users. Used
43 | * in custom container, badges, etc.
44 | * -------------------------------------------------------------------------- */
45 |
46 | :root {
47 | --vp-c-default-1: var(--vp-c-gray-1);
48 | --vp-c-default-2: var(--vp-c-gray-2);
49 | --vp-c-default-3: var(--vp-c-gray-3);
50 | --vp-c-default-soft: var(--vp-c-gray-soft);
51 |
52 | --vp-c-brand-1: #c68cff;
53 | --vp-c-brand-2: #873cd1;
54 | --vp-c-brand-3: #9200c3;
55 | --vp-c-brand-soft: var(--vp-c-indigo-soft);
56 |
57 | --vp-c-tip-1: var(--vp-c-brand-1);
58 | --vp-c-tip-2: var(--vp-c-brand-2);
59 | --vp-c-tip-3: var(--vp-c-brand-3);
60 | --vp-c-tip-soft: var(--vp-c-brand-soft);
61 |
62 | --vp-c-warning-1: var(--vp-c-yellow-1);
63 | --vp-c-warning-2: var(--vp-c-yellow-2);
64 | --vp-c-warning-3: var(--vp-c-yellow-3);
65 | --vp-c-warning-soft: var(--vp-c-yellow-soft);
66 |
67 | --vp-c-danger-1: var(--vp-c-red-1);
68 | --vp-c-danger-2: var(--vp-c-red-2);
69 | --vp-c-danger-3: var(--vp-c-red-3);
70 | --vp-c-danger-soft: var(--vp-c-red-soft);
71 | }
72 |
73 | /**
74 | * Component: Button
75 | * -------------------------------------------------------------------------- */
76 |
77 | :root {
78 | --vp-button-brand-border: transparent;
79 | --vp-button-brand-text: var(--vp-c-white);
80 | --vp-button-brand-bg: var(--vp-c-brand-3);
81 | --vp-button-brand-hover-border: transparent;
82 | --vp-button-brand-hover-text: var(--vp-c-white);
83 | --vp-button-brand-hover-bg: var(--vp-c-brand-2);
84 | --vp-button-brand-active-border: transparent;
85 | --vp-button-brand-active-text: var(--vp-c-white);
86 | --vp-button-brand-active-bg: var(--vp-c-brand-1);
87 | }
88 |
89 | ::selection {
90 | background-color: #8712bd;
91 | color: rgb(255, 255, 255);
92 | }
93 |
94 | /**
95 | * Component: Home
96 | * -------------------------------------------------------------------------- */
97 |
98 | :root {
99 | --vp-home-hero-name-color: transparent;
100 | --vp-home-hero-name-background: -webkit-linear-gradient(
101 | 280deg,
102 | #ed87ff,
103 | #ad1fff
104 | );
105 | /*
106 | --vp-home-hero-name-background: -webkit-linear-gradient(
107 | 120deg,
108 | #bd34fe 30%,
109 | #41d1ff
110 | );
111 | */
112 | --vp-home-hero-image-background-image: -webkit-linear-gradient(
113 | -45deg,
114 | #9138fe52,
115 | #8b02db33
116 | );
117 |
118 | --vp-home-hero-image-filter: blur(44px);
119 | }
120 | .image-src {
121 | max-width: 256px !important;
122 | }
123 | @media (min-width: 640px) {
124 | :root {
125 | --vp-home-hero-image-filter: blur(56px);
126 | }
127 | }
128 |
129 | @media (min-width: 960px) {
130 | :root {
131 | --vp-home-hero-image-filter: blur(68px);
132 | }
133 | }
134 |
135 | /**
136 | * Component: Custom Block
137 | * -------------------------------------------------------------------------- */
138 |
139 | :root {
140 | --vp-custom-block-tip-border: transparent;
141 | --vp-custom-block-tip-text: var(--vp-c-text-1);
142 | --vp-custom-block-tip-bg: var(--vp-c-brand-soft);
143 | --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
144 | }
145 |
146 | /**
147 | * Component: Algolia
148 | * -------------------------------------------------------------------------- */
149 |
150 | .DocSearch {
151 | --docsearch-primary-color: var(--vp-c-brand-1) !important;
152 | }
153 |
154 | /**
155 | * Custom CSS
156 | * -------------------------------------------------------------------------- */
157 |
158 | code {
159 | /*white-space: pre-wrap !important;*/
160 | }
161 |
162 | .mermaid {
163 | background: #00000026;
164 | padding: 15px;
165 | border-radius: 10px;
166 | }
167 |
168 | .mermaid > .label {
169 | overflow: visible !important;
170 | }
171 |
172 | foreignObject {
173 | overflow: visible;
174 | }
175 |
176 | .title {
177 | font-size: 22px !important;
178 | }
179 |
180 | .details {
181 | font-size: 17px !important;
182 | }
183 |
184 | img {
185 | border-radius: 8px;
186 | }
187 |
188 | .vp-doc [class*="language-"] > span.lang {
189 | /*display: none;*/
190 | }
191 |
192 | :root {
193 | --vp-font-family-mono: mono, ui-monospace, "Menlo", "Monaco", "Consolas",
194 | "Liberation Mono", "Courier New", monospace !important;
195 | }
196 |
197 | .vp-code-group .tabs label {
198 | line-height: 42px;
199 | }
200 |
201 | .vp-code-group .tabs {
202 | padding: 0 8px;
203 | }
204 |
205 | .DocSearch-Logo svg * {
206 | fill: var(--vp-c-text-2) !important;
207 | }
208 |
209 | .DocSearch-Logo:hover svg * {
210 | fill: var(--vp-c-text-1) !important;
211 | }
212 |
--------------------------------------------------------------------------------
/docs/document/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Abracadabra 贡献指南
2 |
3 | ### 基本原则
4 |
5 | 本项目致力于实现高效的文本风格化加密技术,通过创新的字符映射方案提升信息表达的灵活性。
6 |
7 | # 贡献规则
8 |
9 | ### 贡献流程
10 |
11 | - 请在 dev 分支提交 Pull Request,等待维护者审核
12 | **禁止直接向 main 分支提交 PR,此类请求将被自动关闭**
13 | - 维护者将在 48 小时内提出代码改进建议(如有)
14 | - 通过审核的 PR 将合并到 dev 分支进行集成测试
15 | - 如果修改了文言映射表和句式,在提交 PR 前**必须**使用 `npm run test` 执行测试。
16 |
17 | ### 编码规范
18 |
19 | - 遵循 ES6+ 语法规范,禁用已被废弃的语法特性
20 | - 善用注释,复杂逻辑必须附加中文注释
21 | - 禁止修改 package.json 版本号字段
22 | - 保持模块化设计,新增模块须详细说明
23 | - 优先使用原生 API,避免不必要的第三方依赖
24 |
25 | ### 模块管理准则
26 |
27 | - 现有模块重构优于新建模块
28 | - 新依赖引入需在 PR 中提供必要性论证
29 | - 涉及性能的修改,需提供基准测试结果
30 |
31 | ### 密码表维护规则
32 |
33 | - **严格保持向后兼容**,现存映射关系永不删除
34 | - 新增映射前必须执行 `npm run test` 查重
35 | - **禁止添加**:
36 | - Unicode 扩展区汉字(U+20000 及之后)
37 | - 总笔画超过 18 画的字符
38 | - 存在输入法输入困难的字符
39 |
40 | ### 质量保障
41 |
42 | - 所有提交必须能够构建成功。
43 | - 涉及加密核心的修改必须提供详细说明
44 | - 重大功能改进应同步更新文档内容
45 |
--------------------------------------------------------------------------------
/docs/document/FAQ.md:
--------------------------------------------------------------------------------
1 | # 常见问题和使用提示
2 |
3 | 这里列出一些常见问题的答案,以及使用提示。
4 |
5 | ## 搭配合适的上下文
6 |
7 | 密文在搭配合适的上下文的时候,可以达到最佳效果。
8 | BERT 等文本分类模型,经过大量训练,也许可以单独攻击密文。
9 |
10 | 若将密文混在一个上下文中,则可以避免此类攻击。
11 | 即使是比一般 NLP 模型更加复杂的 LLM,也无法判别此类内容。
12 |
13 | ::: details 示例 /《红楼梦》第一回
14 | 士隐意欲也跟了过去,方举步时,忽听一声霹雳,有若山崩地陷。士隐大叫一声,定睛一看,只见烈日炎炎,芭蕉冉冉,所梦之事便忘了大半。又见奶母正抱了英莲走来。士隐见女儿越发生得粉妆玉琢,乖觉可喜,便伸手接来,抱在怀内,斗他顽耍一回,又带至街前,看那过会的热闹。方欲进来时,只见从那边来了一僧一道:那僧则癞头跣脚,那道则跛足蓬头,疯疯癫癫,挥霍谈笑而至。及至到了他门前,看见士隐抱著英莲,那僧便大哭起来,又向士隐道:“施主,你把这有命无运,累及爹娘之物,抱在怀内作甚?”士隐听了,知是疯话,也不去睬他。那僧还说:“舍我罢,舍我罢!”士隐不耐烦,便抱女儿撤身要进去,那僧乃指著他大笑,口内念了几句言词道:
15 | **物以极纯,远说骏福,岩歌以风。冷棋歌春,叶霞任琴。宏涧静御,森霞攸笑,是物也,火临冰善,鲤短莺绮。本应度后,非奏铃云,绸将雪之。使其盈恋灵求,乐棋辄彰,城使福之。**
16 | 士隐听得明白,心下犹豫,意欲问他们来历。只听道人说道:“你我不必同行,就此分手,各干营生去罢。三劫后,我在北邙山等你,会齐了同往太虚幻境销号。”那僧道:“最妙,最妙!”说毕,二人一去,再不见个踪影了。士隐心中此时自忖:这两个人必有来历,该试一问,如今悔却晚也。
17 | :::
18 |
19 | ## 善用十六进制字符串
20 |
21 | 字符范围 ABCDEF abcdef 0123456789 可用于表示十六进制。
22 | 压缩时会用两种策略分别压缩,然后比较结果长度,取较小的那个。
23 |
24 | - 策略一,将文本当成十六进制执行解码,然后再压缩。
25 | - 策略二,将文本当成普通 UTF-8 字符串压缩。
26 |
27 | 第一种策略在加密磁力链接时尤为有效。
28 |
29 | **因此使用本项目加密磁力链接时,推荐使用十六进制特征码而非 Base32 编码后的特征码。**
30 | 使用十六进制特征码能得到更短的结果。
31 |
32 | ## 请勿添加固定前缀
33 |
34 | 本项目在未来的任何时候,都不会添加形如 `魔曰:` / `熊曰:` 的固定前缀。
35 | 因为这么做会显著增加密文的可识别性,违背了目标。
36 |
37 | 不过,"魔"字不是载荷字,用户即使自行加上前缀,一并复制,也不会影响解密。
38 | 因此有特殊需要的用户可以自行加上前缀,但我不保证这么做的安全性。
39 |
40 | ## 善用随机滑条
41 |
42 | 用户设定的随机指数,主要影响组句过程中的载荷分配步骤。
43 |
44 | 随机指数越高,每一步分配的载荷量就越有可能变得零碎。从而让整段文本变得零碎。反之亦然。
45 |
46 | “零碎”,即文本中有大量孤立句式:
47 | `X曰`、`非X也`、`非能X也`
48 |
49 | 随机指数可以随用户喜好设置,一般推荐设置为 50(居中位置),如果你更喜欢整齐一点的密文,可以调小它。
50 |
51 | 无论用户如何设置随机指数和过滤开关,均不会影响密文的解密。
52 |
53 | ## 繁體中文(香港)
54 |
55 | 針對使用繁體中文的用戶,魔曰現已支援繁體中文輸入/輸出。
56 |
57 | 魔曰采用的繁體中文類別為香港繁體,爲確保加解密一致性,亦采用逐字轉換的方式,因此可能與實際語言習慣不同。
58 |
59 | 用戶在加密時打開 `繁體中文` 開關,可即時得到繁體中文密文。解密時,並毋須打開此開關,流動應用會自動做出適當處置。
60 |
61 | ::: tip 繁體中文示例
62 |
63 | 琴以秀,堅則為航庭,舒則為振鴛。不必關也,清則為求空,臨則為航戀。風聽而瑞鶯連也,予關夫極鏡繡冰,在秋月之雪。益鸝事於路而報琴,當短取彩局,而歌説不靜,非請彰也。
64 |
65 | :::
66 |
--------------------------------------------------------------------------------
/docs/document/LICENSE.md:
--------------------------------------------------------------------------------
1 | **Academic Innovation Protection License 1.1**
2 | **学术创新保护许可证 1.1 (AIPL-1.1)**
3 |
4 | **版权所有 (C) 2025 SheepChef**
5 |
6 | **序言**
7 |
8 | 在开放源代码软件项目遭到严重抄袭和滥用,大量作者的权益遭受侵犯的背景下,本《学术创新保护许可证》(AIPL)旨在遏制开源作品遭到不正当的使用,与此同时保证大多数正常用户,开源作者和协作者的权利。
9 |
10 | 本许可证授予被授权人有条件的非商业使用权,严格保障著作权人的学术优先权与成果控制权,限制未经许可的竞争性学术利用,商业利用及专利主张行为。
11 |
12 | 针对学术利用,本许可证仅限制被授权人依赖受保护项目获取学术声誉和学术利益。私下研究的学术自由不受限制。
13 |
14 | 针对商业利用,本许可证限制被授权人依赖受保护项目获取直接或间接经济利益,参加商业竞赛,获得第三方投资等。特别地,针对媒体和艺术衍生作品,被授权人享有创作、发布和盈利自由,但著作权人保留终止协议的权利。
15 |
16 | 本许可证为独立授权框架,与现有主流开源协议体系 (GPL 等) 不兼容,使用者应知悉其特殊限制属性。使用本许可证保护的项目,不再是符合开源精神的项目,不符合开源定义(Open Source Definition)。
17 |
18 | 第三方组件的权利与义务由其原始许可证独立管辖,本协议仅约束著作权人声明的原创内容。
19 |
20 | ---
21 |
22 | **第 1 条 定义**
23 |
24 | 1.1 "本作品"指由著作权人依本许可证发布的原创性代码、文档及相关材料,不含明确标注的第三方组件。
25 | 1.2 "衍生作品"指符合下列任一情形的作品:
26 | (a) 全部或部分包含本作品原始表达元素的任何形式的成果,无论其载体形式或技术实现方式;
27 | (b) 与本作品形成单一功能性整体的成果,包括但不限于:
28 | - 通过静态链接/动态链接调用本作品功能模块;
29 | - 作为微服务架构中的依赖组件;
30 | - 以软件即服务(SaaS)形式提供本作品核心功能;
31 | (c) 任何实质上完全依赖本作品技术方案的成果,包括但不限于:
32 | - 通过反编译/反汇编获得的功能等效实现;
33 | 1.3 "学术发表"指在任何具有 ISSN/ISBN/DOI 等标识的正式学术渠道中,或在任何可能被正式学术渠道引用的平台(包括但不限于 arXiv)中,或在第 1.7 条中定义的学术竞赛中,公开发表包含本作品或其衍生作品的任何内容的行为。
34 | 1.4 "著作权人"指对本作品拥有**著作人格权**的自然人、法人或其他法律实体。
35 | 1.5 "被授权人"指根据本许可证条款获得使用、修改、分发代码等权利的主体。
36 | 1.6 "商业竞赛"指由任何商业实体作为主办、承办、出资或冠名单位的竞赛。
37 | 1.7 "学术竞赛"指符合以下任一条件的竞赛:
38 | (a) 由任何获得所在国家或地区主管部门认可的社会组织或学术机构(包括但不限于大学,研究院,科学协会等)作为主办、承办、出资或冠名单位的竞赛。
39 | (b) 由所在国家或地区的市级以上教育主管部门作为主办、承办、出资或冠名单位的竞赛。
40 | 1.8 "本许可证"指 AIPL 学术创新保护许可证第 1.1 版。
41 | 1.9 作品的 "源代码" 指对作品进行修改所首选的作品形式。
42 |
43 | ---
44 |
45 | **第 2 条 基础授权**
46 |
47 | 2.1 在不违反本许可证条款的前提下,被授权人享有以下非独占权利:
48 | (a) 复制、修改、执行本作品;
49 | (b) 在遵守本许可证第 6 条的前提下,使用本作品进行学术研究;
50 | (c) 依本许可证第 3 条分发衍生作品。
51 |
52 | 2.2 著作权人可自由使用其在本作品中的原创内容,不受本许可证限制。若本作品为多位著作权人的合作作品,则:
53 | (a) 各方对自身创作部分保留上述自由使用权;
54 | (b) 使用他人创作内容时,仍须以被授权人身份遵守本许可证条款。
55 |
56 | ---
57 |
58 | **第 3 条 分发**
59 |
60 | 3.1 被授权人分发本作品或其衍生作品**必须**同时满足:
61 | (a) 以显著方式提供完整的原始版权声明。
62 | (b) 公开提供完整的对应源代码,并确保不得采用混淆、加密或其他阻碍技术审查的手段。
63 | (c) 以显著方式标注本许可证名称及版本号。
64 | (d) 采用 AIPL-1.1 许可证。
65 |
66 | 3.2 被授权人**不得**以提供互联网服务为由,拒绝以可获取的方式提供本作品或其衍生作品的全部对应源代码。
67 |
68 | 3.3 禁止被授权人附加任何额外限制条款。
69 |
70 | ---
71 |
72 | **第 4 条 商业活动限制**
73 |
74 | 4.1 未经著作权人书面授权,**禁止**被授权人将本作品或其衍生作品用于:
75 | (a) 直接或间接获取经济利益;
76 | (b) 商业产品开发或服务运营;
77 | (c) 提升企业资产价值;
78 | (d) 竞标,投标等商业竞争行为;
79 | (e) 第 1.6 条中定义的商业竞赛;
80 | (f) 获取任意形式的第三方投资;
81 | (g) 其他任何具有商业性质的行为。
82 |
83 | 4.2 被授权人使用本作品或其衍生作品创作媒体或艺术作品,遵守以下全部条款的,无论是否获得经济利益,无需提前获得商业授权。著作权人保留对相关被授权人终止许可的权利:
84 | (a) 不得使用相关作品参加第 1.6 条中定义的商业竞赛;
85 | (b) 不得以任何方式损害著作权人的合法权益;
86 |
87 | 4.3 被授权人将本作品用于第 4.1 条规定的商业用途前,必须取得著作权人签署的纸质许可文件。
88 |
89 | 4.4 被授权人若将本作品集成至商业产品中,须自行履行相关法律规定的安全评估义务,包括但不限于向监管部门备案、配合执法机关依法调取数据等法定义务。
90 |
91 | ---
92 |
93 | **第 5 条 专利权限制**
94 |
95 | 5.1 被授权人使用本作品,即视为被授权人承诺不就本作品技术方案主张专利保护。
96 |
97 | 5.2 若被授权人持有与本作品相关的有效专利,应:
98 | (a) 授予所有本作品使用者免许可费的实施权;
99 | (b) 不得主张专利侵权。
100 |
101 | 5.3 任何由被授权人发起的针对本作品的专利侵权诉讼,将导致本许可终止。
102 |
103 | ---
104 |
105 | **第 6 条 竞争性学术使用限制**
106 |
107 | 6.1 未经书面"竞争性学术使用"授权,在本作品著作财产权有效期内,**禁止**被授权人将本作品或其衍生作品用于:
108 | (a) 任何为被授权人获取学术荣誉或资格的行为,如撰写学位论文,完成毕业设计等;
109 | (b) 参加第 1.7 条中定义的学术竞赛;
110 | (c) 进行第 1.3 条中定义的学术发表;
111 |
112 | 6.2 被授权人取得"竞争性学术使用"授权,必须满足以下所有条件:
113 | (a) 提交《竞争性学术使用申请书》,包含:
114 | - 个人的身份证明或组织的资质证明;
115 | - 拟使用本作品的范围,以及详细用途。
116 | (b) 收到著作权人作出的书面许可;
117 | (c) 被授权人获得"竞争性学术使用"授权后,仍须依本许可证第 3 条分发衍生作品。
118 |
119 | 6.3 在遵守第 6.1 条的前提下,被授权人将本作品或其衍生作品用于其他学术用途 (例如:授课,布置作业等),不受限制。
120 |
121 | ---
122 |
123 | **第 7 条 适用排除**
124 |
125 | 7.1 本许可证不适用于明确标注不在本许可证保护范围内的内容。
126 |
127 | 7.2 不在本许可证保护范围内的内容,遵守其原始许可证条款。
128 |
129 | ---
130 |
131 | **第 8 条 法律责任**
132 |
133 | 8.1 **禁止**被授权人将本作品或其衍生作品用于违反中华人民共和国法律的用途,包括但不限于:
134 | (a) 网络诈骗;
135 | (b) 洗钱;
136 | (c) 其他违法行为。
137 |
138 | 8.2 在使用本作品及其衍生作品前,被授权人需承诺其应用场景符合相关法律法规。
139 |
140 | 8.3 **著作权人明确不参与、不知晓且不认可任何非法使用行为**。任何使用本作品产生的法律后果由使用者自行承担。在适用法律允许的最大范围内,著作权人不承担因本作品被非法使用而导致的任何直接或间接法律责任。
141 |
142 | 8.4 著作权人有权对被授权人的违法滥用行为采取反制措施,包括但不限于:
143 | (a) 法律追诉;
144 | (b) 终止本许可证授予的权利;
145 | (c) 依法向有关部门举报。
146 |
147 | 8.5 本作品"按原样"提供,不包含任何形式的明示或默示保证,包括但不限于适销性、特定目的适用性及不侵权的保证。在任何情况下,无论是在合同、侵权或其他案件中,著作权人均不对因本作品、或因本作品的使用或其他利用而引起的、引发的或与之相关的任何权利主张、损害赔偿或其他责任承担责任。
148 |
149 | ---
150 |
151 | **第 9 条 许可终止**
152 |
153 | 9.1 被授权人违反本许可证的任一条款将自动导致许可终止,且必须:
154 | (a) 销毁所有作品副本;
155 | (b) 撤回已发布的衍生作品;
156 | (c) 撤回已发布的相关媒体或艺术作品;
157 | (d) 在相关学术数据库发布撤销声明;
158 | (e) 向相关竞赛组委会/学术机构提交违规使用告知函;
159 | (f) 如涉嫌违法或侵权,自行承担相关法律责任。
160 |
161 | 9.2 第 5 条所规定的专利条款的效力,不因许可终止而失效。
162 |
163 | ---
164 |
165 | **第 10 条 法律管辖和溯及力**
166 |
167 | 10.1 本许可证适用中华人民共和国法律,排除其国际私法。
168 |
169 | 10.2 本许可证适用于本作品自 2024 年 9 月 1 日 以来公开发布的所有版本和衍生版本。对于版本发布时使用不同许可证的,以本许可证为准。
170 |
171 | ---
172 |
173 | 本许可证文本以 CC-NC-ND 4.0 协议许可
174 |
--------------------------------------------------------------------------------
/docs/document/bench.md:
--------------------------------------------------------------------------------
1 | # AI 基准测试
2 |
3 | 魔曰对 AI 有着出色的抗性。
4 |
5 | ## 标准
6 |
7 | 明文统一使用三个随机 UUID 首尾相接。
8 | 密文使用魔曰 V3.2.5 随机生成。
9 |
10 | 表头数字(0/50)为随机指数。
11 |
12 | 括号内所示概率为模型成功识别的概率,低于 1/2 则视为通过。
13 | 测试前四次均不能成功识别的,不再识别 8 次。
14 |
15 | ## 测试表格
16 |
17 | | 模型/评测项 | 纯密文识别 (0) | 纯密文识别 (50) | 夹杂密文识别 (50) | 内容安全 | 分类 |
18 | | ------------------- | :------------: | :-------------: | :---------------: | :------: | :---------: |
19 | | DeepSeek R1 | ✅ (2/8) | ✅ (3/8) | ✅ (0/4) | ✅ | 文学 |
20 | | DeepSeek V3 | ✅ (0/4) | ✅ (0/4) | ✅ (0/4) | ✅ | 古典文学 |
21 | | DeepSeek V3.1 | ✅ (0/4) | ✅ (0/4) | ✅ (0/4) | ✅ | 文学 |
22 | | GPT 4o | ✅ (0/4) | ✅ (0/4) | ✅ (0/4) | ✅ | 意象诗文 |
23 | | Qwen 2.5-72B | ✅ (3/8) | ❌ (4/4) | ✅ (0/4) | ✅ | 文学创作 |
24 | | Qwen QwQ-32B | ✅ (0/4) | ✅ (1/8) | ✅ (0/4) | 🟠\* | 古典文学 |
25 | | Qwen 3-235B-A22B | ✅ (0/4) | ✅ (1/8) | ✅ (0/4) | ✅ | 诗歌 |
26 | | Qwen 3-Next-80B-A3B | ✅ (0/4) | ✅ (1/8) | ✅ (0/4) | ✅ | 文言文 |
27 | | ERNIE 4.5-300B-A47B | ✅ (0/4) | ✅ (0/4) | ✅ (0/4) | ✅ | 抽象文学 |
28 | | Kimi K2 Instruct | ✅ (1/8) | ✅ (3/8) | ✅ (0/4) | ✅ | 文学/散文诗 |
29 | | 腾讯云 内容安全 | —— | —— | —— | ✅ | —— |
30 | | 百度云 内容安全 | —— | —— | —— | ✅ | —— |
31 | | 阿里云 内容安全 | —— | —— | —— | ✅ | —— |
32 | | 科大讯飞 内容安全 | —— | —— | —— | ✅ | —— |
33 |
--------------------------------------------------------------------------------
/docs/document/best-practise.md:
--------------------------------------------------------------------------------
1 | # 最佳操作实践
2 |
3 | 阅读此节之前,确保你至少看过[Demo 使用指南](/document/demo-usage.md)
4 |
5 | ## 文言仿真加密
6 |
7 | 下面列出一些情况下的最佳实践。
8 |
9 | ### 仿真随机性
10 |
11 | 用户可以通过滑条来选择句式的随机程度。
12 | 如果想增强句子逻辑性,那么请调整至"长句优先",挑选句式的时候会优先使用最长的可用句,但加密随机性可能受影响。
13 |
14 | 如果想要更随机,语块长短不一的密文,则推荐选择“适中”或更高。
15 |
16 | ### 通顺
17 |
18 | 如果嫌生成的句子过于生硬,不妨多次尝试生成(多点几下加密),选择一个看起来最好的密文。
19 | 只要密钥和原文相同,生成出的所有密文均可以正常解密。
20 |
21 | ### 逻辑优先密文
22 |
23 | 如果想要尽可能生成逻辑上最佳的密文,请打开**逻辑**模式。
24 | 然后将随机性滑条拖到最左侧(0)。
25 |
26 | 如此可以尽量使密文由尽可能多的转折/逻辑复合句式构成。
27 | 能够达到最大程度的,逻辑意义上的以假乱真。
28 |
29 | ### 效率优先密文
30 |
31 | 如果想要尽可能生成短的密文,请打开**骈文**模式。
32 | 然后将随机性滑条拖到最左侧(0)。
33 |
34 | 如此可以尽量使密文由尽可能多的四字/五字骈文句式构成。
35 | 在增强密文文言风格的同时,提升密文的载荷比,使密文缩短。
36 |
37 | ### 混合模式
38 |
39 | 如果不作任何特殊设置,仿真算法会参考概率随机组句。
40 | 如此生成的密文随机性更强,适合一般情况下的使用。
41 |
42 | ### 与上下文搭配
43 |
44 | 合适的做法是将加密出来的文言文与上下文搭配。
45 | 这么做可以抵抗多种攻击,也让 BERT 之类的模型难以对文本进行分类。
46 |
47 | ## 传统模式加密
48 |
49 | 下面列出一些情况下的最佳实践。
50 |
51 | ### 安全优先
52 |
53 | 如果你需要最高的安全性,则在加密时设置一个尽可能长和复杂的密码。
54 |
55 | 最好勾选“去除标志”,来提升密文随机性。
56 |
57 | 解密时将需要对方勾选强制解密。
58 |
59 | ### 效率优先
60 |
61 | 你可以不填密码,这将会使程序自动用内部的默认密码`ABRACADABRA`加/解密。
62 |
63 | 把密文的识别交给标志位,这么做可以让他人很方便地解密。
64 |
65 | ## 密文的合适长度
66 |
67 | 不建议生成过长的密文。
68 |
69 | 过长的密文(>150 字),在逻辑上难以形成链条,在句式上可能出现雷同,在字频上可能出现特征。
70 | 因此不推荐将大段文章丢进加密器加密。
71 |
72 | ## 填写密码
73 |
74 | 如果你不填写密码(`魔咒`),则程序会自动使用 `ABRACADABRA` 作为密钥来加密。
75 |
76 | 如果你正在公开平台上发布密文,你可以不填密码。
77 |
78 | 但如果你想限制某些人解密,或者对密文的安全性有要求,则建议你填写密码。
79 |
--------------------------------------------------------------------------------
/docs/document/character.md:
--------------------------------------------------------------------------------
1 | # 字符映射管线
2 |
3 | 字符映射管线有三个主要部分:
4 |
5 | - **词性字符映射表** - 根据语法功能将 Base64 字符映射到中文字符
6 | - **虚词映射表** - 提供文言文虚词和助词的选取
7 | - **句式模板** - 定义用于生成连贯句子的语法结构
8 |
9 | 魔曰的密本不同于任何同类型的工具,它由数百个《通用规范汉字表》中的一级字和二级字构成,也有一些非常常见的 **日本和制汉字(Kanji)**,比如 **桜(Sakura)**;没有任何让人眼花缭乱的诡异汉字。
10 |
11 | 字符映射表是纯人工挑选编纂的,且公开可查,查阅 [**映射表(传统)**](https://github.com/SheepChef/Abracadabra/blob/main/src/javascript/mapping.json) 或者 [**映射表(仿真)**](https://github.com/SheepChef/Abracadabra/blob/main/src/javascript/mapping_next.json) 以了解密本的全貌。
12 |
13 | 古文句式模板编纂时参考了《古文观止》和《古文辞类纂》,资料来自 [**中国哲学书电子化计划**](http://ctext.org/zhs)。
14 |
15 | ## 传统密表
16 |
17 | ::: tip 传统模式示例
18 | 困句夏之全玚凪斋或骏琅咨兆咩谜理金说宙银歌舒
19 | :::
20 |
21 | 传统模式的密表是几百个常见汉字,加密结果为这些汉字组成的无序字符串。
22 |
23 | 在传统模式下,会在随机位置对密文添加标志位,用来简化加解密操作流程,程序识别到加密标志位便会自动解密,无需用户手动指定解密,提高便利性。你也可以生成没有标志位的密文,此时需要手动指定强制解密。
24 |
25 | 传统模式类似此前存在过的诸多加密项目,加密效率高,密文较短,随机性很强,适用于一般场景。
26 |
27 | ## 文言句式模板和密表
28 |
29 | 句式模板有一个固定的语法,以辅助解析。
30 |
31 | ```
32 | 8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P
33 |
34 | // 8 -> 载荷数量
35 | // "/" ->语素分隔符
36 | // N->名词 V->动词 A->形容词 AD->副词
37 | // B->一般句式 C->骈文句式 D->逻辑句式 E->既是骈文句式,又有逻辑
38 | // P->句号 Q->问号 R->冒号和引号 | 依需要添加在句式末尾,代替原有逗号。
39 | // by/why/anti... -> 虚词
40 |
41 | // 其他(汉字)原样保留
42 | ```
43 |
44 | 密表则按照词性分类,将动词,形容词,副词,和名词分开映射。
45 |
46 | ## 字符映射流程
47 |
48 | 魔曰的字符映射完全基于 Base64,每个有效汉字对应一个 Base64 字符范围内的字符。
49 |
50 | 汉字在映射时经过三重转轮混淆,确保映射关系足够复杂,进一步增加攻击抗性。
51 |
52 | ```mermaid
53 | flowchart TD
54 | Input["Base64 字符串"]
55 | Input --> POS_Select["三重转轮混淆"]
56 | POS_Select --> Map_Lookup["查询映射表"]
57 | Map_Lookup --> Template_Select["选择句式模板<br/>(仅文言文模式)"]
58 | Template_Select --> Generate["得到最终密文"]
59 | ```
60 |
--------------------------------------------------------------------------------
/docs/document/comparison.md:
--------------------------------------------------------------------------------
1 | # 功能对比
2 |
3 | | 加密工具 | 开源 | 脱机运行 | 安全加密 | 仿真 | 长度合理 | 易部署 |
4 | | ---------- | :--: | :------: | :------: | :--: | :------: | :----: |
5 | | 魔曰 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
6 | | 与熊论道 | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
7 | | 兽音译者 | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
8 | | 新佛曰 | ❌ | ❌ | 🟨 | ❌ | ❌ | ❌ |
9 | | 旧佛曰 | ✅ | ✅ | ❌ | ❌ | ❌ | 🟨 |
10 | | 文本隐水印 | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
11 | | 想曰 | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
12 |
13 | ### 注意:
14 |
15 | - "安全加密" 指该工具采用了**密码学安全**的加密算法,且密钥没有被直接被加在密文中。
16 | - "开源" 指该工具的源代码可以查阅。
17 |
18 | ::: tip 魔曰密文示例
19 | 铃曰:“达驿者,涧之鲤也。” 以益木为家者,庭当弗而事之。有轻梦、佳心、新琴之鸳。极岩之曲,事之花而现之琴也。以空,当奏旧家,非声取璃所想颇言。或振楼呈鸢,画灯于鹂,非想迸也,鲤能御木振动,而礼梦弥定。
20 | :::
21 |
22 | ## 主要同类项目
23 |
24 | ### 与熊论道
25 |
26 | ::: tip 熊曰密文示例
27 | 熊曰:呋食性類啽家現出爾常肉嘿達嗷很
28 | :::
29 |
30 | 与熊论道由萌研社开发,曾是使用人数最多的文本加密工具,但它一直不是开源的,加密和解密操作均在其服务器上进行,加密时使用了大量生僻字,密文辨识度高,特征明显。未使用密码学安全的加密算法,密文可以被攻击。
31 |
32 | 与熊论道已在 2025 年上旬因故下线。
33 |
34 | ---
35 |
36 | ### 兽音译者
37 |
38 | ::: tip 兽音密文示例
39 | ~呜嗷嗷嗷嗷呜呜啊呜嗷呜嗷呜呜~嗷啊呜啊嗷啊呜嗷呜~呜~嗷~呜呜嗷嗷嗷嗷嗷嗷呜啊嗷呜啊呜嗷呜呜~嗷啊嗷啊嗷啊呜嗷嗷~~~
40 | :::
41 |
42 | 兽音译者是流行的文本加密工具之一,它可以将任意二进制数据编码为四个固定字符组成的字符串,但它生成的文本太长,字频特征明显。兽音译者是开源的,但它的实现中同样没有密码学安全的加密算法。
43 |
44 | 你仍然可以在现有网站上使用兽音译者。
45 |
46 | ---
47 |
48 | ### 文本隐水印
49 |
50 | [文本隐水印](https://github.com/guofei9987/text_blind_watermark/)是一个开源项目,由 guofei9987 开发。
51 |
52 | 它透过 UTF-8 的零宽字符(U+2060/U+FEFF),将任意数据表示为二进制,透过算法嵌入到一串正常文本内,从而达到肉眼不可见的效果。
53 |
54 | 但这种做法特征明显,仅对肉眼不可见,对任何字符处理系统均没有攻击抗性,可以被简单过滤。
55 |
56 | ---
57 |
58 | ### 想曰
59 |
60 | [想曰](https://github.com/fzxx/XiangYue)是一个开源项目,由 fzxx 开发。
61 |
62 | 它类似魔曰的传统加密模式,透过一个映射表映射经过压缩和加密的数据。
63 |
64 | 它使用多种算法加密数据,因此加密结果通常很长。生成的字符串是无意义的字符序列。
65 |
66 | ---
67 |
68 | ### 新佛曰
69 |
70 | ::: tip 佛曰密文示例
71 | 佛曰:諸南隸僧南降南吽諸陀南摩隸南僧南缽南薩咤南心彌嚴迦聞婆吽願南眾南色南兜南眾南如婆如南
72 | :::
73 |
74 | 新佛曰由萌研社开发,以旧佛曰为基础。它不是开源的,加密和解密操作均在其服务器上进行,加密时使用了佛经生僻字,密文特征明显。新佛曰使用了 AES-256 加密,但是其密钥固定,直接追加在密文中。
75 |
76 | 新佛曰已在 2025 年上旬因故下线。
77 |
78 | ---
79 |
80 | ### 旧佛曰
81 |
82 | 旧佛曰是现存最古老的文字加密工具,开发时间在 2010 年左右,具体开发者是谁已不可考。
83 |
84 | 佛曰在此后的 15 年里有各种各样的衍生和改进版本,它们大多是开源且离线工作的。
85 |
--------------------------------------------------------------------------------
/docs/document/demo-usage.md:
--------------------------------------------------------------------------------
1 | # Demo 使用指南
2 |
3 | 本文档介绍魔曰 Demo 的详细使用方法。
4 |
5 | ## 基本界面
6 |
7 | <div style="max-width:570px;">
8 |
9 | 
10 |
11 | </div>
12 |
13 | - **话语** 框内,请填写待加密/解密的字符串。
14 | - **魔咒** 框内,请填写密钥,也可以留空不填,不填时自动使用默认密钥 `ABRACADABRA`。
15 | - **符文** 框内,会输出加密/解密结果,点击右下角按钮可以直接复制到剪切板。
16 | - 加密/解密按钮下方的滑条,可以用来调整随机因子。具体请查阅[使用提示](/document/FAQ.md#善用随机滑条)和[文言文仿真管线](/document/wenyan.md)。
17 | - **去除标点** 开关,打开后将总是生成不含标点符号的密文。
18 |
19 | ## 骈文模式
20 |
21 | **骈文格律** 开关,打开后将总是生成整齐划一的骈文句式。
22 |
23 | 骈文,是魏晋以来产生的一种文体,又称骈俪文。其主要特点是以四六句式为主,讲究对仗,因句式两两相对,犹如两马并驾齐驱,故被称为骈体。
24 |
25 | ::: tip 骈文模式示例
26 | 木使琴之,此光有银夏怡礼,骏火近鹏。不请飞也,不以冰弹,不以雪走。虽达留坚早,新彩不同。城有可求,云有畅然。探火余秋,筑涧宏速,非可事也。纯镜度恋,心岩呈绸。短风恭动,文雨航星,悠于花苗。慧鸳短航。
27 |
28 | 远叶流于路而彰雀,快茶学绚空,或成璃听涧,彰冰于霞。不有余家,何指乐灯?致物长乐,任庭余绮,轻庭学绸,花曲学书。物岩称璃,良于夏心,非应指也。水驿现鹤,畅于鸳声,迸者奏之,不欲动也。
29 | :::
30 |
31 | ## 逻辑模式
32 |
33 | **逻辑优先** 开关,打开后将总是生成前后存在逻辑关系的转折复合句,判断句。
34 |
35 | 逻辑模式的密文,讲究转折,例如 `有...则...`,`虽然....但是....`
36 | 在使用的时候,密文看起来更加似是而非,上下文伪逻辑更强。
37 |
38 | ::: tip 逻辑模式示例
39 | 非路不明,求不彩,智光之鹤,常添于其所朗关而不致之处。有天则畅,是故无快无慧,无迷无绚,韵之所见、物之所听也。是月也,云快书谜,局旧心谜。是韵也,琴惠人佳,竹南绸迷。鹏至于惠驿之上,遥问于秋雀之间。旧夏之梦,读之空而迸之鹂也。噫,畅韵也,雨谁与求?
40 |
41 | 予赴夫旧云近雪,在明曲之林,瀚之事者必有花。绸非彰而呈之者,孰必无语。盈书之不旅也近矣,欲风之无心也遥矣。是雨也,茶近木善,鹤短人惠。捷福之苗,呈之驿而任之云也。文筑于彩曲,而云动于早天,其也捷乎其留也,聪书流之而不达之、亦将宏夜而复流旧曲也。
42 | :::
43 |
44 | ## 传统模式
45 |
46 | 传统模式加密较少用,它的密表是几百个常见汉字,加密结果为这些汉字组成的无序字符串。
47 |
48 | 传统模式加密/解密均可点击 `吟唱你的魔法` 按钮,程序会检测标志位,自动加密/解密。
49 |
50 | 在传统模式下,会在随机位置对密文添加标志位,用来简化加解密操作流程,程序识别到加密标志位便会自动解密,无需用户手动指定解密,提高便利性。你也可以生成没有标志位的密文,此时需要手动指定强制解密。
51 |
52 | 在传统模式下:
53 |
54 | - **雪藏话语** 开关,打开后将强制加密。
55 | - **探求真意** 开关,打开后将强制解密。
56 | - **去除标志** 开关,打开后将去除密文标志位。
57 |
58 | ::: tip 传统模式示例
59 | 过约瑰涓指总祁醇事氯协曙费不泉生讯桉中而语褔霁莉昼一苹也物赞蔗前夸钠澟皆烷兆竹间之鸢森琳美妃林泉钴理表局拿飒砥蕴业件涨蓬座吧最漱砌们本盘则约铍悟才奖凛璃贮杸四珑工镜中锡裳驿羧瓢走泣珊上玖锰位道坞込欤涌短经橘茜漱对周歌睿涓具银铠面砌按定
60 | :::
61 |
--------------------------------------------------------------------------------
/docs/document/demo_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SheepChef/Abracadabra/1f28d0f561d8a81e640688dda012baefe41763aa/docs/document/demo_next.png
--------------------------------------------------------------------------------
/docs/document/enc.md:
--------------------------------------------------------------------------------
1 | # 加密和混淆管线
2 |
3 | 魔曰使用 AES-256-CTR 作为核心加密算法。使得密文的安全性有强力保证。
4 |
5 | ## AES-256
6 |
7 | 魔曰会将用户提供的密钥,执行一次 SHA256 哈希,取其值作为密钥。
8 |
9 | 然后,对第一次哈希的结果附加两个随机字节,再次哈希,取其值作为加密的 IV。
10 |
11 | ## 三重转轮混淆
12 |
13 | 转轮混淆之前的原文,是一个使用 AES 加密后数据编码而成的 Base64 字符串,转轮混淆对其的处理为彻底打乱 Base64 字符串的字母/数字/符号,使其无法被正常解码为上一层 AES256 加密后的字节数据(包括两字节 IV 在内)。
14 |
15 | ```mermaid
16 | graph LR
17 | A["Base64_Character"] --> B["RoundKeyMatch()"]
18 | B --> C["LETTERS_ROUND_1"]
19 | B --> D["LETTERS_ROUND_2"]
20 | B --> E["LETTERS_ROUND_3"]
21 | C --> F["Rotated_Character"]
22 | D --> F
23 | E --> F
24 | F --> G["getCryptText()"]
25 | G --> H["Chinese_Character"]
26 |
27 | I["RoundKey()"] --> J["Rotor_Rotation"]
28 | J --> C
29 | J --> D
30 | J --> E
31 | ```
32 |
33 | ### 密钥和操作数
34 |
35 | 1. 对密钥进行 SHA256
36 | 2. 对 SHA256 后得到的 32 字节数组中的每个元素执行对十取余,得到一个操作数数组(这个数组中每个元素的大小不超过 9,不小于 0)
37 |
38 | ### 轮转规则
39 |
40 | 混淆时,每混淆/映射一个字符,就取当前操作数,执行一次转轮轮转,并将当前操作数的索引偏移一位。
41 |
42 | 下次加密便会从操作数数组中取下一个操作数执行转轮轮转。如果取到数组末尾,则从头开始,循环往复。
43 |
44 | 轮转方向和距离由当前操作数(N)决定。
45 | 遵守以下规则:
46 |
47 | - 如果操作数为 0,将其当作 10 并继续
48 |
49 | 如果该操作数是偶数(N%2 == 0)
50 |
51 | - 将第一个密钥轮向右轮 6 位
52 | - 将第二个密钥轮向左轮 N\*2 位
53 | - 将第三个密钥轮向右轮(N/2)+1 位
54 |
55 | 如果该操作数是奇数(N%2 != 0)
56 |
57 | - 将第一个密钥轮向左轮 3 位
58 | - 将第二个密钥轮向右轮 N 位
59 | - 将第三个密钥轮向左轮(N+7)/2 位
60 |
61 | 其中,第一个和第三个转轮为顺序轮,第二个转轮为乱序(手动打乱)轮。
62 |
63 | 转轮每次转动方向和距离由操作数组(密钥)决定
64 | 可能的密钥空间为 10^32。
65 |
66 | ### 映射规则
67 |
68 | 映射采用 字母 -> 索引 -> 字母 -> 索引 的重复操作。
69 |
70 | 设立一个原映射标准字符串(实际比这个要长得多)
71 |
72 | ```
73 | abcdefjhigk....
74 | ```
75 |
76 | 三个转轮的长度和原字符串一致。
77 | 假设三个转轮状态如下。
78 | (下一个字符加密时会轮转)
79 |
80 | ```
81 | bcdefjhigka....
82 | edfbjichgak....
83 | fjhigkabcde....
84 | ```
85 |
86 | 现在,假设我们要混淆字符 a
87 |
88 | 1. 在原字符串中找到字符 a 的索引,得到 0
89 | 2. 在第一个转轮中查找索引 0,得到字符 b
90 | 3. 在原字符串中查找字符 b 的索引,得到 1
91 | 4. 在第二个转轮中查找索引 1,得到字符 d
92 | 5. 在原字符串中查找字符 d 的索引,得到 3
93 | 6. 在第三个转轮中查找索引 3,得到字符 i
94 |
95 | 由此完成了 a --> i 的转轮映射。
96 |
97 | 其他所有字符以此类推,均可得到一个映射。
98 | (这个映射可以和原文本相同,修正了 Enigma 机的弱点)
99 |
100 | 每轮转一次转轮,都会得到一个完全不同的映射表,轮转规则见上一小节。
101 |
102 | ## 加密总流程
103 |
104 | <br>
105 |
106 | ```mermaid
107 | graph TD
108 |
109 | subgraph "加密"
110 | RANDBYTES["Random IV Generation<br/>2 bytes"]
111 | AES256["AES_256_CTR_E()"]
112 | KEYDERIV["SHA256 Key Derivation"]
113 | end
114 |
115 | subgraph "编码和混淆"
116 | BASE64["Base64.fromUint8Array()"]
117 | RMPADDING["RemovePadding()"]
118 | ROTORSYS["三重转轮混淆"]
119 | end
120 |
121 | RANDBYTES --> KEYDERIV
122 | KEYDERIV --> AES256
123 | AES256 --> BASE64
124 | BASE64 --> RMPADDING
125 | RMPADDING --> ROTORSYS
126 | ```
127 |
--------------------------------------------------------------------------------
/docs/document/frontend-deploy.md:
--------------------------------------------------------------------------------
1 | # 前端 部署和编译
2 |
3 | ## 快速部署
4 |
5 | 前往[Release 页面](https://github.com/SheepChef/Abracadabra/releases/latest)下载 `fastdeploy_X.X.zip`
6 |
7 | 然后,将它解压到你网站的任意位置,也可以直接上传到静态容器中。
8 |
9 | 配置路由,即可得到一个与[项目 Demo](https://abra.js.org/)一模一样的页面。
10 |
11 | 若要自行编译或修改前端代码,请前往前端源代码仓库。
12 |
13 | 浏览器插件的源码同样在前端源代码仓库,位于 crx 分支。
14 |
15 | ## 编译前端页面
16 |
17 | 首先,前往[前端源码仓库](https://github.com/SheepChef/Abracadabra_demo),拉取前端源码仓库的代码。
18 |
19 | ```sh
20 | git clone https://github.com/SheepChef/Abracadabra_demo.git
21 | ```
22 |
23 | 然后,切换到主分支。
24 |
25 | ```sh
26 | git checkout main
27 | ```
28 |
29 | 现在你可以开始编译了,你最少需要执行两个指令:
30 |
31 | ```sh
32 | npm install
33 |
34 | npm run build
35 | ```
36 |
37 | 构建生成的文件在 `./docs` 文件夹内。
38 |
39 | ## 编译浏览器插件
40 |
41 | 拉取仓库代码后,切换到 `crx` 分支。
42 |
43 | ```sh
44 | git checkout crx
45 | ```
46 |
47 | ::: tip 无法切换分支?
48 | 确保在切换分支之前,你没有未保存的修改。
49 | :::
50 |
51 | 下一步,安装依赖并编译。
52 |
53 | ```sh
54 | npm install --legacy-peer-deps
55 |
56 | npm run build
57 | ```
58 |
59 | ::: warning --legacy-peer-deps
60 | 你必须附加 `--legacy-peer-deps` 参数,否则依赖将无法正常安装。
61 | :::
62 |
63 | 构建生成的文件在 `./dist` 文件夹内,也许会生成一个无用的 `.vite` 文件夹,删除即可。
64 |
--------------------------------------------------------------------------------
/docs/document/js-deploy.md:
--------------------------------------------------------------------------------
1 | # JavaScript 部署
2 |
3 | 使用 npm 下载 Abracadabra 库。
4 |
5 | ```sh
6 | npm install abracadabra-cn
7 | ```
8 |
9 | 然后,在项目中引入库文件
10 |
11 | ```js
12 | import { Abracadabra } from "abracadabra-cn";
13 | ```
14 |
15 | 如果你想在网页中全量引入本库,可以导入 `abracadabra-cn.umd.cjs`
16 | 在网页上直接引用,请看 [**网页引用**](#网页引用) 一节。
17 |
18 | ## JavaScript 类型接口
19 |
20 | Abracadabra 库仅包含一个类型,即`Abracadabra`
21 |
22 | 使用前,你需要实例化该类型,实例化时需要两个参数来指定输入输出的方式,如果不附带参数,则自动使用默认值 `TEXT`。
23 |
24 | ```js
25 | import { Abracadabra } from "abracadabra-cn";
26 |
27 | let Abra = new Abracadabra(); //不附带参数,
28 |
29 | /*
30 | Abracadabra.TEXT = "TEXT"
31 | Abracadabra.UINT8 = "UINT8"
32 | */
33 | let Abra = new Abracadabra(InputMode, OutputMode);
34 | //参数必须是上述二者中的一个,传入其他值会导致错误。
35 | ```
36 |
37 | `TEXT` 表明将来的输入/输出为 `String`,`UINT8` 表明将来的输入/输出为 `Uint8Array`,你可以灵活使用两种不同的类型。
38 |
39 | ::: warning 接口兼容性须知
40 | 旧的接口 `Input_Next()` 和 `Input()` 目前仍然可以使用,但未来的版本更新中会移除它们。
41 | :::
42 |
43 | ::: tip 文件加/解密
44 | 使用`Uint8Array`作为输入/输出方式,魔曰可以加解密任意二进制(图片/视频/任何文件),但是不推荐这么做。
45 | :::
46 |
47 | ### WenyanInput() 文言仿真加密函数
48 |
49 | `WenyanInput()` 函数用来对数据执行文言文仿真加密。
50 |
51 | ```js
52 | import { Abracadabra } from "abracadabra-cn";
53 |
54 | let Abra = new Abracadabra(); //不附带参数,
55 |
56 | /**
57 | * 魔曰 文言文加密模式
58 | *
59 | * @param {string | Uint8Array} input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
60 | * @param {string} mode 指定模式,可以是 ENCRYPT DECRYPT 中的一种;
61 | * @param {string} key 指定密钥,默认是 ABRACADABRA;
62 | * @param {WenyanConfig} WenyanConfigObj 文言文的生成配置;
63 | * @param {any}callback 回调函数,获取执行过程中特定位置的结果
64 | * @return {number} 成功则返回 0(失败不会返回,会抛出异常)
65 | */
66 | Abra.WenyanInput(input, mode, key, {...}, callback);
67 | ```
68 |
69 | 第一个参数 `input` 接受两种类型的输入,分别是 `String` 和 `Uint8Array`,这是此前在实例化的时候指定的输入类型。
70 |
71 | 如果你指定了 `UINT8` 却传入 `String`,将抛出错误,反之亦然。
72 |
73 | 第二个参数 `mode` 接受上文中特定字符串的输入,任何其他输入都将被忽略,不会输出任何结果。
74 |
75 | 第三个参数 `key` 接受字符串类型的密钥输入,如果不提供,则默认使用内置密钥 `ABRACADABRA`。
76 |
77 | 如果指定了错误的密码,那么在解码/解密数据校验过程中会抛出错误。
78 |
79 | 第五个参数 `callback` 接受一个回调函数,缺省时为 `null`。程序会在执行中关键位置多次调用此函数,以便调试,无调试需求可忽略此项。
80 |
81 | 第四个参数接受一个`WenyanConfig`配置对象的输入,仅在加密的时候需要:
82 |
83 | ```javascript
84 | export interface WenyanConfig {
85 | /** 指定是否为密文添加标点符号,默认 true/添加; */
86 | PunctuationMark?: boolean;
87 | /** 密文算法的随机程度,越大随机性越强,默认 50,最大100,超过100将会出错; */
88 | RandomIndex?: number;
89 | /** 指定是否强制生成骈文密文,默认 false; */
90 | PianwenMode?: boolean;
91 | /** 指定是否强制生成逻辑密文,默认 false; */
92 | LogicMode?: boolean;
93 | /** 指定输出文本是否为繁体中文,默认 false; */
94 | Traditional?: boolean;
95 | }
96 | ```
97 |
98 | `PunctuationMark` 是布尔值,默认为 `true`。如果传入 `false`,则加密结果中将不含标点符号,解密时可以忽略这个参数。
99 |
100 | `RandomIndex` 是整数值,默认为`50`,最小值`0`,最大值`100`,超过 100 的输入将会报错。输入值越大,载荷量选择算法的随机性越大;输入值为 0 时,句式选择步骤将只选择载荷字较多的那个。解密时可以忽略这个参数。
101 |
102 | `PianwenMode` 是布尔值,不指定则默认为 `false`。如果传入 `true`,则加密结果会优先使用骈文句式,呈现四字到五字一组的对仗格律,这有助于减少密文的总体长度。解密时可以忽略这个参数。
103 |
104 | `LogicMode` 是布尔值,默认为 `false`。如果传入 `true`,则加密结果会优先使用逻辑句式,呈现强论述类逻辑风格。解密时可以忽略这个参数。
105 |
106 | `Traditional` 是布尔值,默认为 `false`。如果传入 `true`,则加密结果会自动转换为繁体中文(香港)。解密时可以忽略这个参数。
107 |
108 | `PianwenMode` 和 `LogicMode` 不能同时指定为 `true`,否则会抛出错误。
109 |
110 | ```javascript
111 | //正确调用方式:
112 |
113 | import { Abracadabra } from "abracadabra-cn";
114 | let Abra = new Abracadabra(); //不附带参数,
115 |
116 | Abra.WenyanInput(TestTemp, "ENCRYPT", "ABRACADABRA", {
117 | RandomIndex: 25,
118 | PianwenMode: true,
119 | }); //指定随机指数为25,并使用骈文模式,缺省项自动使用默认值
120 |
121 | Abra.WenyanInput(TestTemp, "DECRYPT", "ABRACADABRA"); //解密不需要传入配置
122 | ```
123 |
124 | 在无错误的情况下, `WenyanInput()` 函数的返回值通常是 `0`。
125 |
126 | ### OldInput() 传统加密函数
127 |
128 | `OldInput()` 用传统模式加密密文。
129 |
130 | ```js
131 | import { Abracadabra } from "abracadabra-cn";
132 |
133 | let Abra = new Abracadabra(); //不附带参数,
134 |
135 | /*
136 | MODES:
137 |
138 | Abracadabra.ENCRYPT = "ENCRYPT";
139 | 强制加密
140 |
141 | Abracadabra.DECRYPT = "DECRYPT";
142 | 强制解密
143 |
144 | Abracadabra.AUTO = "AUTO";
145 | 自动(遵循自动逻辑)
146 |
147 | */
148 | Abra.OldInput(input, mode, key, q);
149 | ```
150 |
151 | 第一个参数 `input` 接受两种类型的输入,分别是 `String` 和 `Uint8Array`,这是此前在实例化的时候指定的输入类型。
152 |
153 | 如果你指定了 `UINT8` 却传入 `String`,将抛出错误,反之亦然。
154 |
155 | 第二个参数 `mode` 接受上文中特定字符串的输入,任何其他输入都将被视为 `AUTO` 并被忽略。
156 |
157 | 第三个参数 `key` 接受字符串类型的密钥输入,如果不提供,则默认使用内置密钥 `ABRACADABRA`。
158 |
159 | 如果指定了错误的密码,那么在解码/解密数据校验过程中会抛出错误。
160 |
161 | 第四个参数 `q` 接受布尔值的输入,如果传入 `true`,则加密结果中将不含标志位,更加隐蔽,但解密时需要强制解密。
162 |
163 | 在无错误的情况下, `OldInput()` 函数的返回值通常是 `0`。
164 |
165 | ### Output()
166 |
167 | ```js
168 | import { Abracadabra } from "abracadabra-cn";
169 |
170 | let Abra = new Abracadabra(); //不附带参数,
171 |
172 | Abra.OldInput(input, mode, key, q);
173 |
174 | let Result = Abra.Output(); //获取输出
175 | ```
176 |
177 | 在调用 `Output()` 之前,你需要至少调用过一次 `WenyanInput()` 或者 `OldInput()`,否则将会抛出错误。
178 |
179 | 调用 `Output()` 将获得此前输入的处理结果,其返回类型可能是 `String` 或 `Uint8Array`,取决于对象实例化时指定了何种输出模式。
180 |
181 | ## 网页引用
182 |
183 | 绕过 NPM 和包管理,你也可以直接在任意网页上直接引用本项目。
184 |
185 | 在 Release 处下载 `.umd.cjs` 文件,放到自定义位置,然后在网页 `<head>` 标签添加引用:
186 |
187 | ```html
188 | <script src="<path>/abracadabra-cn.umd.cjs"></script>
189 | ```
190 |
191 | 在网页的其他地方调用脚本接口,可以这么写:
192 |
193 | ```html
194 | <script>
195 | let Abra = new window["abracadabra-cn"].Abracadabra(InputMode, OutputMode);
196 |
197 | //此后的调用方法,和前述调用方法没有差别,直接使用此实例化对象即可。
198 | //故不做过多赘述。
199 | </script>
200 | ```
201 |
202 | 在实例化对象之后,其余的调用方法请见上一节。
203 |
204 | ## 自行编译
205 |
206 | 如果你想要自行编译 JavaScript 库文件,请克隆 main 分支到本地。
207 | 安装 `npm` 并配置恰当的环境。
208 |
209 | 安装编译和调试依赖:
210 |
211 | ```sh
212 | npm install
213 | ```
214 |
215 | 然后执行编译指令:
216 |
217 | ```sh
218 | npm run build
219 | ```
220 |
221 | 如果你对密码映射表做出了修改,那么请确保将 JSON 压缩成一行,转义成字符串。
222 | 然后修改 `ChineseMappingHelper.js` 中的 `OldMapper` 类(传统加密) 或者 `WenyanSimulator` 类(文言加密):
223 |
224 | ```js
225 | this.Map = "...."; // 字符串内填密码映射表
226 | ```
227 |
228 | 在执行编译时,会自动对文言文密本中的句式语法执行检查,如果有问题,则会报错并提示编译失败。
229 | 如果你想要单独运行检查,可以执行:
230 |
231 | ```sh
232 | npm run test
233 | ```
234 |
--------------------------------------------------------------------------------
/docs/document/luhn-compress.md:
--------------------------------------------------------------------------------
1 | # 压缩和校验管线
2 |
3 | ## 压缩管线
4 |
5 | 魔曰使用多阶压缩管线,在加密之前对输入数据执行压缩。通过自适应算法选择和智能内容检测来选择合适的压缩策略。
6 |
7 | 针对短文本,本项目使用针对短文本优化的 [**Unishox2**](https://github.com/siara-cc/Unishox2) 压缩算法,避免了通用压缩算法(如 GZIP 等)文件头过重的问题。一般数据(>1KB)则采用 GZIP。
8 |
9 | 针对链接和常见域名编排了字典,有效提高特定链接(例如网盘链接)的压缩效率。
10 |
11 | 压缩后会执行效率验证,如果出现无效压缩,则自动回落到原始数据。
12 |
13 | ### 压缩总流程
14 |
15 | <br>
16 |
17 | ```mermaid
18 | flowchart TD
19 | OriginalData["OriginalData (Uint8Array)"] --> GetLuhnBit["GetLuhnBit()"]
20 | GetLuhnBit --> TempArray["TempArray.set([GetLuhnBit(OriginalData)])"]
21 | TempArray --> SizeCheck{"OriginalData.byteLength <= 1024?"}
22 |
23 | SizeCheck -->|是| UNISHOX_COMPRESS["UNISHOX_COMPRESS()"]
24 | SizeCheck -->|否| GZIP_COMPRESS_Only["GZIP_COMPRESS()"]
25 |
26 | UNISHOX_COMPRESS --> SizeBefore["if (OriginalData.byteLength == SizeBefore)"]
27 | SizeBefore --> UnishoxFallback{"No compression?"}
28 | UnishoxFallback -->|是| GZIP_COMPRESS_Fallback["GZIP_COMPRESS()"]
29 | UnishoxFallback -->|否| AES_256_CTR_E["AES_256_CTR_E()"]
30 |
31 | GZIP_COMPRESS_Fallback --> AES_256_CTR_E
32 | GZIP_COMPRESS_Only --> AES_256_CTR_E
33 |
34 | AES_256_CTR_E --> AppendRandomBytes["TempArray.set(RandomBytes)"]
35 | AppendRandomBytes --> Base64fromUint8Array["Base64.fromUint8Array()"]
36 | Base64fromUint8Array --> RemovePadding["RemovePadding()"]
37 | RemovePadding --> OriginStr["OriginStr ready for getCryptText()"]
38 |
39 | ```
40 |
41 | ### URL 针对性优化
42 |
43 | 魔曰对一些协议头,域名和 TLD 执行了特殊优化。编排了一个自定义字典。
44 |
45 | | 字典分配 | 标识符 | 域名和关键字 |
46 | | -------- | ------ | -------------------------------- |
47 | | 国内网盘 | 254 | `lanzou`, `pan.quark.cn` ... |
48 | | 国际网盘 | 245 | `mypikpak.com`, `mega.nz`... |
49 | | 国内网站 | 253 | `baidu.com`, `b23.tv`... |
50 | | 国际网站 | 252 | `google.com`, `youtube.com`... |
51 | | 国际网站 | 244 | `wikipedia.org`, `github.com`... |
52 | | 日本网站 | 251 | `pixiv.net`, `nicovideo.jp`... |
53 | | 资源网站 | 250 | ———— |
54 |
55 | ## 校验管线
56 |
57 | 项目使用轻量化的 [**卢恩算法**](https://zh.wikipedia.org/zh-cn/%E5%8D%A2%E6%81%A9%E7%AE%97%E6%B3%95)(US2950048, ISO/IEC 7812-1) 来对解密结果做简单校验,能够检出 70%的错误。
58 |
59 | 卢恩算法比起 Hmac 和 AES-GCM,安全性稍弱,但它十分轻量,校验位仅占一个字节。
60 |
61 | ### 校验总流程
62 |
63 | <br>
64 |
65 | ```mermaid
66 | flowchart TD
67 | Data["Data (Uint8Array)"] --> ExtractStored["DCheck = Data[Data.byteLength - 1]"]
68 | Data --> SubarrayCall["Data.subarray(0, Data.byteLength - 1)"]
69 | SubarrayCall --> GetLuhnBit["GetLuhnBit()"]
70 | GetLuhnBit --> CalculatedCheck["Check (calculated)"]
71 | ExtractStored --> StoredCheck["DCheck (stored)"]
72 | CalculatedCheck --> Compare{"Check == DCheck?"}
73 | StoredCheck --> Compare
74 | Compare -->|Yes| ReturnTrue["return true"]
75 | Compare -->|No| ReturnFalse["return false"]
76 | ```
77 |
--------------------------------------------------------------------------------
/docs/document/quick-start.md:
--------------------------------------------------------------------------------
1 | # 快速开始
2 |
3 | **Abracadabra 魔曰** 是开源,安全,高效的文本加密工具。将数据加密为汉字构成的文言文,完全开源,易于部署,易于使用。
4 |
5 | 相较于传统的加密方法(佛曰/熊曰/兽音等),魔曰有很多优势:
6 |
7 | - **仿真,使用文言语法句式**。
8 | - 开源,所有源代码公开可查。
9 | - 安全,完全离线的 AES 加密。
10 | - 可靠,代码经过严格单元测试。
11 | - 便捷,易于本地部署和使用。
12 |
13 | <h3>百闻不如一见,现在就试试看吧!</h3>
14 |
15 | ## 直接使用
16 |
17 | ### Demo
18 |
19 | 本项目有自动托管在 Cloudflare Pages 的静态 Demo 可供直接使用。
20 |
21 | 此页面加载完成后可完全脱机运行,你也可以选择安装 PWA 来更优雅地使用它。
22 |
23 | Demo 会适配魔曰的所有可调参数,你可以在此体验到魔曰的完整功能。
24 |
25 | <div style="width:170px">
26 |
27 | [<img src="https://img.shields.io/badge/立刻使用-ffd91c?logo=cloudflarepages&style=for-the-badge&logoColor=000000" width="170"/>](https://abra.js.org/)
28 |
29 | </div>
30 |
31 | ### 浏览器插件
32 |
33 | 浏览器插件的代码完全复用静态页面代码。
34 |
35 | 此插件不申请任何浏览器权限(包括联网权限),没有任何多余功能。
36 |
37 | 已上架 **Chrome WebStore**, **Edge 加载项** 和 **Firefox 扩展**。
38 |
39 | 如果不方便访问 Chrome 插件商店,也可以访问 Edge 插件商店,和 Firefox 扩展商店。
40 |
41 | <div style="display: grid;
42 | justify-items: center;
43 | grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
44 | grid-auto-flow: row;
45 | grid-gap: 10px;">
46 | <div style="width:171px">
47 |
48 | [<img src="https://img.shields.io/badge/Chrome 商店-8a54ff?logo=chromewebstore&style=for-the-badge&logoColor=ffffff" width="171" />](https://chrome.google.com/webstore/detail/jgmlgdoefnmlealmfmhjhnoiejaifpko)
49 |
50 | </div>
51 |
52 | <div style="width:170px">
53 |
54 | [<img src="https://img.shields.io/badge/MSEdge 商店-8a54ff?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMyAyMyI+CiAgICA8cGF0aCBmaWxsPSIjZjNmM2YzIiBkPSJNMCAwaDIzdjIzSDB6Ii8+CiAgICA8cGF0aCBmaWxsPSIjZjM1MzI1IiBkPSJNMSAxaDEwdjEwSDF6Ii8+CiAgICA8cGF0aCBmaWxsPSIjODFiYzA2IiBkPSJNMTIgMWgxMHYxMEgxMnoiLz4KICAgIDxwYXRoIGZpbGw9IiMwNWE2ZjAiIGQ9Ik0xIDEyaDEwdjEwSDF6Ii8+CiAgICA8cGF0aCBmaWxsPSIjZmZiYTA4IiBkPSJNMTIgMTJoMTB2MTBIMTJ6Ii8+Cjwvc3ZnPg==&style=for-the-badge&logoColor=ffffff" width="170" />](https://microsoftedge.microsoft.com/addons/detail/abracadabra-%E9%AD%94%E6%9B%B0/kfkmhdcahjblddpkkmnjeppmfmfoihkb)
55 |
56 | </div>
57 |
58 | <div style="width:174px">
59 |
60 | [<img src="https://img.shields.io/badge/Firefox 商店-8a54ff?logo=firefoxbrowser&style=for-the-badge&logoColor=ffffff" width="174" />](https://addons.mozilla.org/zh-CN/firefox/addon/abracadabra-%E9%AD%94%E6%9B%B0/)
61 |
62 | </div>
63 | </div>
64 |
65 | ::: warning 提示
66 |
67 | Edge 插件商店的上架审核速度十分缓慢,因此更新速度也更慢。不推荐从 Edge 商店下载本插件。
68 |
69 | :::
70 |
71 | ### Android 客户端
72 |
73 | 本项目的 Android 客户端完全在 WebView 中静态运行。
74 |
75 | 
76 |
77 | APK 使用 HBuilderX 自动打包,**完全离线运行,没有自动更新等配套功能**。
78 |
79 | 功能和界面均和前端静态网页没有差异。
80 |
81 | APK 文件可以 [**在 Release 中下载**](https://github.com/SheepChef/Abracadabra/releases/latest)
82 |
83 | ## 简单部署
84 |
85 | ### 部署核心库
86 |
87 | 使用 npm 下载 Abracadabra 库。
88 |
89 | 你也可以前往[Release 页面](https://github.com/SheepChef/Abracadabra/releases/latest)直接下载 JS 文件。
90 |
91 | ```sh
92 | $ npm install abracadabra-cn
93 | ```
94 |
95 | 然后,在项目中引入库文件
96 |
97 | ```js
98 | import { Abracadabra } from "abracadabra-cn";
99 | ```
100 |
101 | ---
102 |
103 | 你也可以直接在任意网页上直接引用本项目。
104 |
105 | 在 Release 处下载 `.umd.cjs` 文件,放到自定义位置,然后在网页 `<head>` 标签添加引用:
106 |
107 | ```html
108 | <script src="<path>/abracadabra-cn.umd.cjs"></script>
109 | ```
110 |
111 | 在网页的其他地方调用脚本接口,可以这么写:
112 |
113 | ```html
114 | <script>
115 | let Abra = new window["abracadabra-cn"].Abracadabra(InputMode, OutputMode);
116 | </script>
117 | ```
118 |
119 | ### 部署完整前端
120 |
121 | 前往[Release 页面](https://github.com/SheepChef/Abracadabra/releases/latest)下载 `fastdeploy_X.X.zip`
122 |
123 | 然后,将它解压到你网站的任意位置,也可以直接上传到静态容器中。
124 |
125 | 配置路由,即可得到一个与[项目 Demo](https://abra.js.org/)一模一样的页面。
126 |
127 | 若要自行编译或修改前端代码,请前往前端源代码仓库。
128 |
129 | 浏览器插件的源码同样在前端源代码仓库,位于 crx 分支。
130 |
131 | <div style="width:103px">
132 |
133 | [<img src="https://img.shields.io/badge/前端源码-9a10b5?style=for-the-badge" width="103" />](https://github.com/SheepChef/Abracadabra_demo)
134 |
135 | </div>
136 |
137 | ## 下一步
138 |
139 | - 了解魔曰的用户指南,最佳使用实践,请继续阅读[使用指引](/document/demo-usage.md)。
140 |
141 | - 查阅魔曰的详细编译部署指南和接口文档,请参考[部署和编译](/document/js-deploy.md)。
142 |
143 | - 了解魔曰的详细技术细节,请查阅[技术细节](/document/wenyan.md)
144 |
--------------------------------------------------------------------------------
/docs/document/thanks.md:
--------------------------------------------------------------------------------
1 | # 鸣谢
2 |
3 | 感谢 [**Unishox2**](https://github.com/siara-cc/Unishox2) 提供高效的短文本压缩方案。
4 |
5 | 感谢 [**中国哲学书电子化计划**](http://ctext.org/zhs) 提供高质量的古籍参考资料。
6 |
7 | 感谢 [**JS.ORG**](https://js.org) 为本项目提供域名支持。
8 |
9 | 感谢 [**@Amlkiller**](https://github.com/amlkiller) 为本项目提供十分有价值的反馈和建议。
10 |
11 | 感谢 **熊曰(与熊论道)、佛曰、兽音译者** 为本项目提供灵感和参考。
12 |
13 | 感谢贡献 PR 和参与测试的其他所有人,以及**正在使用本项目的您**。
14 |
--------------------------------------------------------------------------------
/docs/document/wasm-deploy.md:
--------------------------------------------------------------------------------
1 | # WebAssembly 部署
2 |
3 | 前往[Release 页面](https://github.com/SheepChef/Abracadabra/releases/latest)下载编译好的 WebAssembly `.wasm` 文件。
4 |
5 | 然后,推荐使用 [**wasmtime**](https://github.com/bytecodealliance/wasmtime) 来调用它,其他 Runtime 不做特别兼容。
6 |
7 | 本项目的 WebAssembly 模块使用 [**Javy**](https://github.com/bytecodealliance/javy) 编译而来,方便在 C++、Rust、Go 等语言中调用,**不推荐**在类似 Python、 Java、Node.js 的解释器中调用。
8 |
9 | 要调用本 WebAssembly 模块,需要使用尚在预览状态的 [**WASI**](https://github.com/WebAssembly/WASI),目前仅有 wasmtime 提供了最完整的 WASI 支持,但它在各个语言的实现并不一致。
10 |
11 | 本模块的合法输入为一个 JSON 字符串,输入时请勿附带注释,遵循以下格式:
12 |
13 | ::: warning 兼容性提示
14 | 注意,V3.2 修改了接口标准,WASM 未对旧版本留有兼容,请参照新版接口来编写调用方式。
15 | :::
16 |
17 | ```json
18 | {
19 | "method":"", // WENYAN | OLD
20 | "inputType":"", // TEXT | UINT8
21 | "outputType":"", // TEXT | UINT8
22 | "input":"", // 输入的数据,如果是TEXT请直接输入纯文本,如果是任意字节,请输入Base64编码字符串
23 | "mode":"", // ENCRYPT | DECRYPT | AUTO // AUTO 仅在 method 指定 OLD 时合法
24 | "key":"", // 加密密钥,一个字符串 //如果缺省,自动使用默认值
25 | "q":bool, // OLD模式下,决定是否添加标志位
26 | "WenyanConfig":{ //文言文生成配置,可以缺省,缺省自动使用默认值。
27 | "PunctuationMark": bool, // 指定是否为密文添加标点符号,默认 true/添加;
28 | "RandomIndex": number, // 仅WENYAN模式下需要:算法的随机程度,越大随机性越强,默认 50,最大100,超过100将会出错;
29 | "PianwenMode":bool, // 仅WENYAN模式下需要:尽可能使用对仗的骈文句式; 与逻辑句式冲突
30 | "LogicMode":bool, // 仅WENYAN模式下需要:尽可能使用逻辑句式; 与骈文句式冲突
31 | "Traditional":bool, // 仅WENYAN模式下需要:输出繁体中文。
32 | },
33 | }
34 | ```
35 |
36 | 用 wasmtime CLI 调用,在不同的命令行里有不同的方式,大多数时候是输入字符串的字符集的区别,以及是否需要在字符串外面加单引号的区别。
37 |
38 | 在 Windows CMD 或者 Powershell 中调用,请确保执行了 `chcp 65001` 以调整代码页为 UTF-8。
39 |
40 | 注意在 Windows CMD 中,输入的字符串**不需要**用单引号囊括。
41 |
42 | ```sh
43 | echo '{"method":"WENYAN","mode":"ENCRYPT","inputType":"TEXT","outputType":"TEXT","input":"愿青空的祝福,与我的羽翼同在","key":"ABRACADABRA","WenyanConfig":{"PianwenMode":true}}' | wasmtime abracadabra-cn.wasm
44 | ```
45 |
46 | 对于其他语言,你需要使用 Wasmtime WASI 的 `stdin` 和 `stdout` 接口来操作本模块的输入输出,调用 `_start` 方法来启动本模块。
47 |
48 | 下方提供 Python 的示例,其他语言请自行查阅 wasmtime 对应的文档。
49 |
50 | ```sh
51 | pip install wasmtime
52 | ```
53 |
54 | ```python
55 | import wasmtime
56 |
57 | def run_wasi(wasm_file):
58 | engine = wasmtime.Engine()
59 | module = wasmtime.Module.from_file(engine, wasm_file)
60 | store = wasmtime.Store(engine)
61 | linker = wasmtime.Linker(engine)
62 | wasi = wasmtime.WasiConfig()
63 | #Python 的 wasmtime 实现,想写入stdin,必须使用一个文件。
64 | #文件里填写要输入的JSON。
65 | wasi.stdin_file = "<Path_to_JSON_File>"
66 | wasi.inherit_stdout()
67 | wasi.inherit_stderr()
68 | linker.define_wasi()
69 | store.set_wasi(wasi)
70 | instance = linker.instantiate(store, module)
71 | start = instance.exports(store)["_start"]
72 | start(store)
73 | try:
74 | run_wasi("<Path_to_WASM_Module_File>")
75 | except FileNotFoundError:
76 | print(f"Error: WASM file '{wasm_file}' not found.")
77 | except wasmtime.WasmtimeError as e:
78 | print(f"Wasmtime error: {e}")
79 | except Exception as e:
80 | print(f"An unexpected error occurred: {e}")
81 | ```
82 |
83 | ## 自行编译 (Javy)
84 |
85 | 首先,拉取仓库,安装 [**Javy**](https://github.com/bytecodealliance/javy),配置恰当的环境。
86 |
87 | 然后,像编译普通 JS 库一样,执行:
88 |
89 | ```sh
90 | npm install
91 |
92 | npm run build
93 | ```
94 |
95 | 在输出文件夹中,找到 `abracadabra-cn-javy.js`
96 |
97 | 然后用 Javy 在命令行中编译:
98 |
99 | ```sh
100 | javy build "Path/to/abracadabra-cn-javy.js" -o "path/Output.wasm"
101 | ```
102 |
--------------------------------------------------------------------------------
/docs/document/wenyan.md:
--------------------------------------------------------------------------------
1 | # 文言文仿真管线
2 |
3 | ::: tip 文言文加密示例
4 | 光韵开云,雅于莺茶,停而行之之谓速。是故无悦无谜,无瑞无聪,裳之所走、树之所振也。旧铃之纯水,常为悦水之莹风。人曰:“瑞琴之路,常留于其所允行而不读之处。” 璃非笑而去之者,孰可无鹏。非将选也,非可指也,书非当事涧,仍继叶言,奈何,同森而非航水也,能鸢者益。
5 | :::
6 |
7 | 文言仿真,会将加密后的字符串映射为仿古文本中的若干个载荷字。
8 |
9 | 用户可以调整仿真器的随机参数,启用特定风格模板的过滤,最终影响生成的密文风格。
10 |
11 | 以下是文言仿真的基本步骤:
12 |
13 | 1. 分配载荷,遇到超大载荷则平均分配,递归分段处理。
14 | 2. 在句式库中选择对应载荷量的句式。
15 | 3. 在句式模板中插入载荷字,插入时数据经过三重转轮混淆。
16 | 4. 在句式和句式之间插入标点符号。
17 | 5. 得到完整密文。
18 |
19 | 用户可以影响载荷分配时的随机因子;在选择句式时,可以打开特定风格的过滤器。
20 |
21 | ## 载荷分配
22 |
23 | 载荷分配本质上是简化了的找零问题。
24 | 将一个给定的载荷量(例如 37),分解成若干个 1~9 的整数之和。
25 |
26 | 载荷将被预先按比例分为 Begin, Main, End 三部分,对应一段密文的三节,每节都拥有一个不同的句式库。
27 |
28 | 有两种策略,分别是贪心算法和随机分配,每个分配步骤都会选择二者之一。
29 |
30 | 贪心算法在每一步尽可能大地分配载荷,从而得到一个较为整齐的分配结果。
31 |
32 | 用户可以指定更高的随机因子,增加随机分配的概率(最大 100%),从而得到更加零碎的分配结果。
33 |
34 | 针对载荷分配,还引入了额外步骤以打乱/合并过于零碎的载荷,尽可能防止密文产生连续的重复模式。
35 |
36 | ## 句式模板和密表
37 |
38 | 句式模板有一个固定的语法,以辅助解析。
39 |
40 | ```
41 | 8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P
42 |
43 | // 8 -> 载荷数量
44 | // "/" ->语素分隔符
45 | // N->名词 V->动词 A->形容词 AD->副词
46 | // B->一般句式 C->骈文句式 D->逻辑句式 E->既是骈文句式,又有逻辑
47 | // P->句号 Q->问号 R->冒号和引号 | 依需要添加在句式末尾,代替原有逗号。
48 | // by/why/anti... -> 虚词
49 |
50 | // 其他(汉字)原样保留
51 | ```
52 |
53 | 密表则按照词性分类,将动词,形容词,副词,和名词分开映射。
54 |
55 | ## 选择句式
56 |
57 | 给定一个分配的载荷量,以及此时载荷所处的密文节(Begin/Main/End),算法会在对应句式库里过滤出所有匹配该载荷量的句式。
58 |
59 | 如果用户没有指定任何过滤器,一般情况下,则在所有载荷量匹配的句式中随机选择一个,无论其分类。
60 | 有 25% 概率将在这些句式中再次过滤出逻辑/骈文句式,然后随机选用其中的一个,如果没有可用的句式,则在所有载荷量匹配的句式中随机选择一个。
61 |
62 | 如果用户指定了过滤器(骈文/逻辑),则会再次过滤出可用的骈文/逻辑句式,然后随机选用其中的一个。
63 | 如果对应载荷量没有可用的骈文/逻辑句式,则在所有载荷量匹配的句式中随机选择一个。
64 |
65 | 总体而言,句式选择提供了较强的随机性和灵活度。
66 |
67 | ```mermaid
68 | flowchart TD
69 | A["载荷长度"] --> B{"载荷 > 100?"}
70 | B -->|是| C["distributePayload()"]
71 | B -->|否| D["distributeInteger()"]
72 |
73 | C --> E["递归处理"]
74 | D --> F["分三段处理"]
75 |
76 | F --> G["Begin_段"]
77 | F --> H["Main_段"]
78 | F --> I["End_段"]
79 |
80 | G --> J["句式模板选择"]
81 | H --> J
82 | I --> J
83 |
84 | J --> K{"风格模式?"}
85 | K -->|骈文| L["PossiblePianSentences"]
86 | K -->|逻辑| M["PossibleLogicSentences"]
87 | K -->|默认| N["Random_Selection"]
88 |
89 | L --> O["最终句式序列"]
90 | M --> O
91 | N --> O
92 |
93 | O --> P["组建文言文语句"]
94 | ```
95 |
96 | ## 插入载荷字和标点
97 |
98 | 算法将用分隔符"/"将句式分割成数组,然后丢弃句式的开头部分。
99 |
100 | 再把每个句子分割出的数组,依次压入一个大数组中,得到一个二维数组。
101 |
102 | 此时将用两层循环依次遍历数组中的每一个元素:
103 |
104 | - 遇到 N/V/A/AD 等载荷位,则对表映射一个载荷字,追加到密文字符串上。
105 | - 遇到虚词指示,则在对应虚词库中随机选择一个追加到字符串上。
106 | - 按照一定的规则,在句式和句式之间插入标点符号,或者换行符(分段标志)。
107 | - 遇到汉字或者其他字符,则原样追加到密文字符串上。
108 |
109 | ```mermaid
110 | graph TD
111 | A["Base64 编码数据"] --> B["选择句式"]
112 | B --> C["句式大数组"]
113 |
114 | C --> D["句式处理循环"]
115 | D --> E{"语素类型"}
116 |
117 | E -->|载荷| F["getCryptText()"]
118 | E -->|情态动词| G["Random_MV_Selection"]
119 | E -->|虚词| H["Virtual_Word_Lookup"]
120 | E -->|标点符号| I["Punctuation_Handler"]
121 |
122 | F --> J["三重转轮混淆"]
123 | J --> K["载荷字查表"]
124 | K --> L["映射汉字"]
125 |
126 | G --> M["追加到输出"]
127 | H --> M
128 | I --> M
129 | L --> M
130 |
131 | M --> N{"循环结束?"}
132 | N -->|否| D
133 | N -->|是| O["处理标点符号"]
134 |
135 | O --> Q["最终加密结果"]
136 | ```
137 |
138 | 由此得到一个强随机性,包含标点符号的文言文密文字符串。
139 |
140 | 如果用户指定不需要标点符号,那么会执行最后一次过滤,过滤出不含标点符号的密文结果。
141 |
142 | 文言文字符串十分多样且随机,以下是一个示例:
143 |
144 | > 鸳,恋之月也。雀花致局,秋于花声,使其俊物舒动,快恋长至。此雁有畅驿乐霞,静鸢乐声。光与空信,非当御也,庭与苗看。故动余心者,当定乐镜之和文。取在惠家,不当买也,灵者度而读之,绿者买而彰之。
145 | >
146 | > 涧动以琴航,振不弹林,鸢航于鹏。树与夏问,舒星之不达也灵矣,欲鲤之无梦也曾矣。冰曰:“曲与星定” ,是曲也,花宏庭新,局纯灯惠。霞谈于坚鲤,而家彰于早文,而能冷者静,故求绿礼者,当彰长风之早语。
147 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 |
4 | hero:
5 | name: "魔曰"
6 | text: "以文封缄"
7 | tagline: Abracadabra 魔曰 是安全,高效的文本加密工具,可以将数据加密为汉字构成的文言文。
8 | image:
9 | src: /logo.png
10 | alt: Abracadabra
11 | actions:
12 | - theme: brand
13 | text: 快速开始 →
14 | link: document/quick-start.md
15 | - theme: alt
16 | text: GitHub仓库
17 | link: https://github.com/SheepChef/Abracadabra
18 |
19 | features:
20 | - icon: 🪄
21 | title: 惟妙惟肖
22 | details: 魔曰的密文看起来像逼真的古文,用算法构造字面意义,使加密具有文学色彩。
23 | - icon: 🔐
24 | title: 固若金汤
25 | details: 魔曰重视数据安全,明文数据经过 AES-256 加密。所有代码完全在本地离线执行。
26 | - icon: 🌈
27 | title: 不拘一格
28 | details: 魔曰允许你调整加密参数,使用不同的模式,生成高度随机化,不同风格的密文。
29 | - icon: 🎭
30 | title: 似是而非
31 | details: 密文完全呈现文言文特征,不存在罕见字符和异常字频,难以与正常文言文区分。
32 | - icon: 🏛️
33 | title: 熔古铸今
34 | details: 引用《古文观止》等古籍,依托真实古文,将密码和古汉语文学相融合。
35 | - icon: ⚡
36 | title: 惜字如金
37 | details: 魔曰重视密文长短,在加密前压缩数据,让密文尽可能缩短,避免长篇大论。
38 | - icon: 🎯
39 | title: 规行矩步
40 | details: 完整且严格的代码单元测试,强制解密兼容性,确保魔曰加密安全可靠。
41 | - icon: 🌳
42 | title: 博采众议
43 | details: 依AIPL 1.1许可证,你可以自由查阅,修改魔曰的源代码,参与社区,提出建议和贡献。
44 | ---
45 |
46 | ## Abracadabra 魔曰
47 |
48 | <div style="display:flex;flex-flow: wrap;">
49 |
50 | <img src="https://img.shields.io/badge/license-AIPL%201.1-yellow"/>
51 |
52 | <img src="https://img.shields.io/badge/lang-JavaScript-orange"/>
53 |
54 | <img src="https://img.shields.io/badge/binary-WASM-b33bb3"/>
55 |
56 | </div>
57 |
58 | <div style="display:flex;flex-flow: wrap;">
59 |
60 | [<img src="https://img.shields.io/github/v/release/SheepChef/Abracadabra?color=00aaff"/>](https://github.com/SheepChef/Abracadabra/releases/latest)
61 |
62 | [<img src="https://img.shields.io/github/actions/workflow/status/SheepChef/Abracadabra/node.js.yml?branch=main&label=%E6%9E%84%E5%BB%BA"/>](https://github.com/SheepChef/Abracadabra/actions/workflows/node.js.yml)
63 |
64 | [<img src="https://img.shields.io/codecov/c/github/SheepChef/Abracadabra?label=%E8%A6%86%E7%9B%96%E7%8E%87"/>](https://github.com/SheepChef/Abracadabra/actions/workflows/coverage.yml)
65 |
66 | <a href="https://hellogithub.com/repository/6d67b7be3ccc43419924dbe40b31e937" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=6d67b7be3ccc43419924dbe40b31e937&claim_uid=cQrPYdpGNg4ACK6&theme=small" alt="Featured|HelloGitHub" /></a>
67 |
68 | 
69 |
70 | </div>
71 |
72 | Abracadabra(魔曰) 是开源,安全,高效的文本加密工具。
73 | 将数据加密为汉字构成的文言文,完全开源,易于部署,易于使用。
74 |
75 | ## 特性
76 |
77 | - **仿真,使用文言语法句式**。
78 | - 开源,所有源代码公开可查。
79 | - 安全,完全离线的 AES 加密。
80 | - 可靠,代码经过严格单元测试。
81 | - 便捷,易于本地部署和使用。
82 |
83 | ### **熔古铸今:文言文仿真加密**
84 |
85 | > 镜有能弹,雨有谧然。以语,当笑速夜,非花称雪所将湛流。不应事也,畅则为动礼,迷则为达鲤,然则绣雪自恋致矣。
86 | >
87 | > 此棋有南涧迷森,悠雨清琴。遥家为鸢兮,宏梦为鹤。或指林写岩,进恋于雨,是语也,鸳极驿安,璃慧空舒。况请瀚者宏,是故无和无后,无安无舒,空之所连、城之所见也。光语筑天,良于璃韵,安铃振灯,局文放花。是故无余无青,无遥无寒,语之所事、琴之所歌也。虽火俊茶长,所以赴雨,其空速也,或弹物任绸,弹楼于声。
88 |
89 | 构造高仿真文言文,**参考《古文观止》《经史百家杂钞》《古文辞类纂》等古代典籍。**
90 | 标准 AES256 加密,引入更复杂的组句、语法匹配机制,将密码和中国古典文言文相融合。
91 |
92 | 密文高度随机,支持用户自定义随机性和文本风格偏好,打造前所未有的跨文化数字加密方案。
93 |
94 | <div style="width: 100%; height: 57px;"><a href="https://ctext.org/zhs"><img src="https://ctext.org/logos/ctplogo6.gif" border="0" alt="中国哲学书电子化计划" /></a></div>
95 |
96 | ## 开放源代码许可
97 |
98 | **⚠️ 本项目受私有许可证保护**,使用本项目则默认视为同意并遵守相关条款。禁止将本项目用于非法用途。
99 | 👉 查阅 [**AIPL-1.1 许可**](https://github.com/SheepChef/Abracadabra/blob/main/LICENSE.md) 来了解详细信息,也可以前往 [**#87**](https://github.com/SheepChef/Abracadabra/issues/87) 查阅简单介绍。
100 |
101 | ---
102 |
103 | 以下是本项目的依赖项:
104 |
105 | - [**Unishox2**](https://github.com/siara-cc/Unishox2) 短字符串压缩实现 _©Siara-cc_, **Apache-2.0** License.
106 | - [**crypto-js**](https://github.com/brix/crypto-js) AES 加密实现 _©Jeff Mott/Evan Vosberg_, **MIT** License.
107 | - [**pako**](https://github.com/nodeca/pako) GZIP 压缩实现 _©Vitaly Puzrin/Andrei Tuputcyn_, **MIT** License.
108 | - [**js-base64**](https://github.com/dankogai/js-base64) Base64 编码工具实现 _©Dan Kogai_, **BSD-3-Clause** License.
109 | - [**mersenne-twister**](https://github.com/boo1ean/mersenne-twister) 梅森旋转算法实现 _©Makoto Matsumoto/Takuji Nishimura_, **BSD-3-Clause** License.
110 | - [**opencc-js**](https://github.com/nk2028/opencc-js) 简繁体转换实现 _©nk2028_, **MIT** License.
111 |
112 | 本项目许可证与所有依赖项的许可证兼容。
113 |
114 | ## Star History
115 |
116 | [](https://star-history.com/#SheepChef/Abracadabra&Date)
117 |
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SheepChef/Abracadabra/1f28d0f561d8a81e640688dda012baefe41763aa/docs/public/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "abracadabra-cn",
3 | "description": "Use Chinese to Encode Everything",
4 | "private": false,
5 | "version": "3.2.5",
6 | "main": "./dist/abracadabra-cn.js",
7 | "types": "./dist/abracadabra-cn.d.ts",
8 | "type": "module",
9 | "scripts": {
10 | "dev": "vite",
11 | "build": "vite build",
12 | "prebuild": "vitest run",
13 | "preview": "vite preview",
14 | "test": "vitest run",
15 | "coverage": "vitest run --coverage",
16 | "docs:build": "vitepress build docs",
17 | "docs:preview": "vitepress preview docs"
18 | },
19 | "devDependencies": {
20 | "@vitest/coverage-v8": "^3.1.3",
21 | "mermaid": "^11.8.0",
22 | "terser": "^5.36.0",
23 | "vite": "^5.4.9",
24 | "vitepress": "^1.6.3",
25 | "vitepress-plugin-mermaid": "^2.0.17",
26 | "vitest": "^3.1.2"
27 | },
28 | "dependencies": {
29 | "crypto-js": "^4.2.0",
30 | "js-base64": "^3.7.7",
31 | "mersenne-twister": "^1.1.0",
32 | "opencc-js": "^1.0.5",
33 | "pako": "^2.1.0"
34 | },
35 | "files": [
36 | "dist"
37 | ],
38 | "repository": {
39 | "type": "git",
40 | "url": "git://github.com/SheepChef/Abracadabra.git"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/javascript/ChineseMappingHelper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | import {
14 | GetRandomIndex,
15 | insertStringAtIndex,
16 | difference,
17 | AddPadding,
18 | shuffle,
19 | } from "./Misc.js";
20 | import { RoundObfusOLD, RoundObfus } from "./RoundObfusHelper.js";
21 | import { CallbackObj } from "./CoreHandler.js";
22 | import * as OpenCC from "opencc-js";
23 |
24 | export class WenyanSimulator {
25 | constructor(key, callback = null) {
26 | this.Map =
27 | '{"Actual":{"N":{"alphabet":{"a":"人","b":"镜","c":"鹏","d":"曲","e":"霞","f":"绸","g":"裳","h":"路","i":"岩","j":"叶","k":"鲤","l":"月","m":"雪","n":"冰","o":"局","p":"恋","q":"福","r":"铃","s":"琴","t":"家","u":"天","v":"韵","w":"书","x":"莺","y":"璃","z":"雨","A":"文","B":"涧","C":"水","D":"花","E":"风","F":"棋","G":"楼","H":"鹤","I":"鸢","J":"灯","K":"雁","L":"星","M":"声","N":"树","O":"茶","P":"竹","Q":"兰","R":"苗","S":"心","T":"语","U":"礼","V":"梦","W":"庭","X":"木","Y":"驿","Z":"火"},"numbersymbol":{"0":"森","1":"夏","2":"光","3":"林","4":"物","5":"云","6":"夜","7":"城","8":"春","9":"空","+":"雀","/":"鹂","=":"鸳"}},"V":{"alphabet":{"a":"关","b":"赴","c":"呈","d":"添","e":"停","f":"成","g":"走","h":"达","i":"行","j":"称","k":"见","l":"学","m":"听","n":"买","o":"作","p":"弹","q":"写","r":"定","s":"谈","t":"动","u":"旅","v":"返","w":"度","x":"开","y":"筑","z":"选","A":"流","B":"指","C":"换","D":"探","E":"放","F":"看","G":"报","H":"事","I":"泊","J":"现","K":"迸","L":"彰","M":"需","N":"飞","O":"游","P":"求","Q":"御","R":"航","S":"歌","T":"读","U":"振","V":"登","W":"任","X":"留","Y":"奏","Z":"连"},"numbersymbol":{"0":"知","1":"至","2":"致","3":"去","4":"画","5":"说","6":"进","7":"信","8":"取","9":"问","+":"笑","/":"视","=":"言"}},"MV":["欲","应","可","能","将","请","想","必","当"],"A":{"alphabet":{"a":"莹","b":"畅","c":"新","d":"高","e":"静","f":"美","g":"绿","h":"佳","i":"善","j":"良","k":"瀚","l":"明","m":"早","n":"宏","o":"青","p":"遥","q":"速","r":"慧","s":"绚","t":"绮","u":"寒","v":"冷","w":"银","x":"灵","y":"绣","z":"北","A":"临","B":"南","C":"俊","D":"捷","E":"骏","F":"益","G":"雅","H":"舒","I":"智","J":"谜","K":"彩","L":"余","M":"短","N":"秋","O":"乐","P":"怡","Q":"瑞","R":"惠","S":"和","T":"纯","U":"悦","V":"迷","W":"长","X":"少","Y":"近","Z":"清"},"numbersymbol":{"0":"远","1":"极","2":"安","3":"聪","4":"秀","5":"旧","6":"浩","7":"盈","8":"快","9":"悠","+":"后","/":"轻","=":"坚"}},"AD":{"alphabet":{"a":"诚","b":"畅","c":"新","d":"高","e":"静","f":"恒","g":"愈","h":"谨","i":"善","j":"良","k":"频","l":"笃","m":"早","n":"湛","o":"昭","p":"遥","q":"速","r":"朗","s":"祗","t":"攸","u":"徐","v":"咸","w":"皆","x":"灵","y":"恭","z":"弥","A":"临","B":"允","C":"公","D":"捷","E":"淳","F":"益","G":"雅","H":"舒","I":"嘉","J":"勤","K":"协","L":"永","M":"短","N":"歆","O":"乐","P":"怡","Q":"已","R":"忻","S":"和","T":"谧","U":"悦","V":"稍","W":"长","X":"少","Y":"近","Z":"尚"},"numbersymbol":{"0":"远","1":"极","2":"安","3":"竟","4":"悉","5":"渐","6":"颇","7":"辄","8":"快","9":"悠","+":"后","/":"轻","=":"曾"}}},"Virtual":{"zhi":["之"],"hu":["乎"],"zhe":["者"],"ye":["也"],"for":["为"],"ba":["把"],"le":["了"],"er":["而"],"this":["此","斯"],"still":["仍"],"with":["与","同"],"also":["亦","也"],"is":["是","乃"],"not":["未","莫"],"or":["或"],"more":["更"],"make":["使","将","让"],"and":["与","同"],"anti":["非","不"],"why":["为何","奈何","何哉"],"but":["但","却","则","而","况","且"],"like":["似","如","若"],"if":["若","倘"],"int":["哉","呼","噫"],"self":["自"],"by":["以","于"]},"Sentences":{"Begin":["1D/非/N/ye","1B/N/曰/R","1B/若夫/N","1C/anti/MV/V/ye/P","2B/A/N/曰/R","2B/N/以/A","2C/N/anti/在/A","2C/N/make/N/zhi","2C/MV/N/zhe/A","2E/有/N/则/A","2E/不/入/于/N/、/则/入/于/N/P","2C/V/zhe/V/zhi","2D/but/MV/A/zhe/A","3C/N/V/by/N","3B/初,/N/V/by/N","3B/夫/N/anti/V/by/N","3B/AD/V/zhi/谓/A","3C/A/N/为/N/兮","3B/V/而/V/zhi/zhi/谓/A","3B/N/,/N/zhi/N/ye/P","3D/A/之/V/者/必/有/N/P","3D/有/所/V/N/,/则/不/得/其/V/P","4D/非/N/不/A/,/V/不/A","4C/A/N/AD/V","4C/V/N/以/V/N","4D/A/者/自/V/也/,/而/N/自/V/也/P","4E/N/不在/A/,/有/N/则/A/P","4E/上/不/V/N/,/下/不/V/N/P","4D/A/N/常有/,/而/A/N/不常有/P","4D/V/N/者/,/N/之/N/也/P","4E/N/有/MV/V/,/N/有/AD/然/P","4D/N/无/N/,/无以/V/N","4D/欲/V/其/N/者/,/先/V/其/N/P","4D/今/夫/N/,/一/N/之/多/,/及/其/A/A/P","4D/V/之/不/为/N/,/V/之/不/为/N/P","4D/吾/为/N/之/所/V/,/N/亦/为/吾/所/V/P","5D/V/N/而/V/A/,/V/zhi/道/ye/P","5D/A/N/之/N/,/like/N/like/N/P","5E/N/zhi/V/V/,/实为/A/A/P","5C/本/MV/V/A/,/anti/V/N/N","5C/N/之/无/N/,/N/V/之/N","5D/V/N/而/V/之/者/,/非/其/N/AD/也/P","5B/今/V/N/以/V/A/N","5B/N/乃/V/V/N/zhi/N","5B/N/N/无/V/,/V/而/必/V/P","5B/今/N/乃/A/N/A/N","5C/A/N/V/A/N","5B/夫/N/、/N/不/MV/AD/V/N","5D/不/有/A/N/,/何/V/A/N/Q","5D/以/A/N/为/N/者/,/N/MV/弗/而/V/之/P","6B/以/N/V/,/like/V/N/V/N","6B/A/N/zhi/N/,/V/zhi/以/V/其/N","6B/A/N/V/于/N/而/V/N","6B/A/N/未/V/N/、/N/之/N","6B/V/A/N/若/V/A/N","6C/A/N/为/N/兮/,/A/N/为/N/P","6D/不/V/N/,/不/V/N/,/当/以/AD/V/论/P","6D/A/则为/V/N/,/A/则为/V/N/P","6D/若/居/A/N/之/N/,/则/当/A/N/之/V/P","6D/N/无/N/则/V/,/N/无/N/则/V/P","6D/A/者/V/而/V/之/,/A/者/V/而/V/之/P","6D/N/受/命/于/N/,/固/AD/然/V/于/A/N/P","6D/V/N/而/不/能/V/,/V/而/不/能/V/,/N/也/P","6D/常/有/N/V/A/N/,/请/N/为/N/P","7D/夫/A/之/N/V/N/者/,/其/所以/AD/V/者/N/也/P","7C/N/以/A/A/,/AD/V/A/N","7B/V/N/A/,/A/N/V/N","7B/N/V/以/N/V/,/V/不/V/N","7C/N/N/V/N/,/A/于/N/N","7D/MV/AD/V/A/N/,/but/V/V/不/A","7C/或/V/N/V/N/,/V/N/于/N","7E/则有/N/A/N/A/,/N/N/具/V","7D/V/A/N/zhe/,/常/V/其/所/A/,/而/V/其/所/A/P","7D/A/N/之/N/,/常/V/于/其/所/AD/V/而/不/V/之/处/P","7D/A/N/之/N/不在/N/,/在乎/A/N/之/N/也/P","8D/V/A/N/,/V/A/N/,/by/MV/A/zhi/N/P","8D/N/anti/AD/V/zhe/by/AD/V/zhe/V/,/anti/MV/AD/V/P","8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P","8C/V/N/A/A/,/V/N/A/A","8C/N/V/A/N/,/N/V/A/N","8C/N/在/A/N/,/A/N/zhi/A/,/V/于/N/P","8C/A/N/AD/V/,/N/N/AD/V","8C/A/N/V/N/,/N/N/V/N/P","8B/尝/V/A/N/,/AD/V/A/N/zhi/N","8D/予/V/夫/A/N/A/N/,/在/A/N/之/N","8D/N/V/于/A/N/,/而/N/V/于/A/N","8D/N/V/N/为/N/,/N/V/N/为/N/P","8B/N/A/即/N/A/,/N/A/即/N/A/P","8D/虽/无/N/N/zhi/V/,/亦/V/以/AD/V/A/N/P","8D/是/故/A/N/有/A/N/,/必/AD/V/以/得/之/,/AD/V/以/失/之/P","8D/A/N/之/A/N/,/常/为/A/N/之/A/N/P","9D/A/N/V/zhi/而不/V/zhi/、亦/make/A/N/er/复/V/A/N/ye/P","9D/N/MV/V/N/V/V/,/but/N/N/AD/V/P","9B/以/N/,/当/V/A/N/,/非/N/V/N/所/MV/AD/V/P","9C/此/N/有/A/N/A/N/,/A/N/A/N/P","9D/是/N/ye/,/N/A/N/A/,/N/A/N/A/P"],"Main":["1B/非/N/ye","1B/N/曰/R","1C/anti/MV/V/ye","2C/N/make/N/zhi","2C/MV/N/zhe/A","2E/有/N/则/A","2E/不/入/于/N/、/则/入/于/N/P","2C/V/zhe/V/zhi","2C/but/MV/A/zhe/A","3C/N/with/N/V","3B/N/曰,何/A/zhi/V/Q","3D/有/所/V/N/,/则/不/得/其/V/P","4C/A/N/AD/V","4C/V/N/以/V/N","4D/N/无/N/,/无以/V/N/P","4D/此/谓/V/N/在/V/其/N/P","4D/今/夫/N/,/一/N/之/多/,/及/其/A/A/P","4D/欲/V/其/N/者/,/先/V/其/N/P","4E/上/不/V/N/,/下/不/V/N/P","4D/A/者/自/V/也/,/而/N/自/V/也/P","4D/V/N/者/,/N/之/N/也/P","4D/以/此/V/N/,/何/N/不/V/Q","4E/N/不在/A/,/有/N/则/A/P","4C/N/有/MV/V/,/N/有/AD/然/P","4D/N/非/V/而/V/之/者/,/孰/MV/无/N/P","4D/A/N/常有/,/而/A/N/不常有/P","4D/吾/为/N/之/所/V/,/N/亦/为/吾/所/V/P","4C/不/以/N/V/,/不/以/N/V/P","4D/V/之/不/为/N/,/V/之/不/为/N/P","5B/今/V/N/以/V/A/N","5B/N/乃/V/V/N/zhi/N","5B/N/N/无/V/,/V/而/必/V/P","5C/本/MV/V/A/,/anti/V/N/N","5D/V/N/而/V/之/者/,/非/其/N/AD/也/P","5D/A/N/之/N/,/like/N/like/N/P","5D/以/A/N/为/N/者/,/N/MV/弗/而/V/之/P","5D/故/夫/A/N/之/N/,/不/可/make/其/V/于/N/也/P","5B/今/N/乃/A/N/A/N","5E/每/有/V/N/,/便/AD/然/V/N/P","5D/N/V/而/A/N/V/也","5E/不/有/A/N/,/何/V/A/N/Q","5C/N/之/无/N/,/N/V/之/N","6D/N/A/N/A/,/则/所/V/得/其/A/P","6B/以/N/V/,/like/V/N/V/N","6B/V/A/N/若/V/A/N","6C/N/V/,/V/N/V/N","6E/虽/V/V/A/A/,/A/A/不/同/P","6D/而/A/N/zhi/N/,/V/zhi/以/V/其/N/P","6B/A/N/V/于/N/而/V/N","6B/A/N/未/V/N/、/N/之/N","6C/V/A/N/,/V/A/N","6C/A/N/为/N/兮/,/A/N/为/N/P","6D/V/MV/with/其/N/,/而/V/MV/V/以/N/者/,/N/也/P","6D/A/N/必/有/A/N/V/之者/、/予/可/无/N/也/P","6D/将/有/V/,/则/V/A/N/以/V/N/P","6D/不/V/N/,/不/V/N/,/当/以/AD/V/论/P","6D/A/则为/V/N/,/A/则为/V/N/P","6D/N/无/N/则/V/,/N/无/N/则/V/P","6D/A/者/V/而/V/之/,/A/者/V/而/V/之/P","6D/若/居/A/N/之/N/,/则/当/A/N/之/V/P","6D/N/受/命/于/N/,/固/AD/然/V/于/A/N/P","6D/V/N/而/不/能/V/,/V/而/不/能/V/,/N/也/P","6D/常/有/N/V/A/N/,/请/N/为/N/P","7D/夫/A/之/N/V/N/者/,/其/所以/AD/V/者/N/也/P","7B/N/V/以/N/V/,/V/不/V/N","7C/N/N/V/N/,/A/于/N/N","7D/MV/AD/V/A/N/,/but/V/V/不/A","7C/或/V/N/V/N/,/V/N/于/N","7D/V/A/N/zhe/,/常/V/其/所/A/,/而/V/其/所/A/P","7D/A/N/之/不/V/也/AD/矣/,/欲/N/之/无/N/也/AD/矣/P","7D/A/N/之/N/,/常/V/于/其/所/AD/V/而/不/V/之/处/P","7D/A/N/之/N/不在/N/,/在乎/A/N/之/N/也/P","7D/A/N/之/N/,/V/之/N/而/V/之/N/也/P","7D/是故/A/N/不必不如/N/,/N/不必/A/于/A/N/P","7B/有/A/N/、/A/N/、/A/N/之/N/P","8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P","8E/是/故/无/A/无/A/,/无/A/无/A/,/N/之/所/V/、/N/之/所/V/ye/P","8C/V/N/A/A/,/V/N/A/A","8B/N/在/A/N/,/A/N/zhi/A/,/V/于/N/P","8B/like/A/N/V/N/,/不/V/N/V/之/N/P","8C/A/N/AD/V/,/N/N/AD/V","8D/A/N/之/A/N/,/常/为/A/N/之/A/N/P","8C/A/N/V/N/,/N/N/V/N/P","8D/虽/无/N/N/zhi/V/,/亦/V/以/AD/V/A/N/P","8D/予/V/夫/A/N/A/N/,/在/A/N/之/N","8D/故/V/A/N/者/,/当/V/A/N/之/A/N/P","8D/N/V/于/A/N/,/而/N/V/于/A/N","8D/N/V/N/为/N/,/N/V/N/为/N/P","8B/N/A/即/N/A/,/N/A/即/N/A/P","8B/A/N/MV/A/N/之/A/,/V/N/中/之/A","8D/N/V/于/A/N/之上/,/AD/V/于/A/N/之间/P","8D/是/故/A/N/有/A/N/,/必/AD/V/以/得/之/,/AD/V/以/失/之/P","8B/使/其/A/N/AD/V/,/A/N/AD/V/P","9B/N/MV/V/N/V/V/,/but/N/N/AD/V","9D/A/N/V/zhi/而不/V/zhi/、亦/make/A/N/er/复/V/A/N/ye/P","9D/以/N/,/当/V/A/N/,/非/N/V/N/所/MV/AD/V/P","9C/此/N/有/A/N/A/N/,/A/N/A/N/P","9E/是/N/ye/,/N/A/N/A/,/N/A/N/A","9E/V/A/N/,/N/A/N/A/,/乃/AD/V"],"End":["1B/非/N/ye","1C/anti/MV/V/ye","2C/唯/N/V/zhi","2B/V/by/N","2D/其/also/A/hu/其/V/ye/P","2C/N/make/N/zhi","2C/MV/N/zhe/A","2E/有/N/则/A","2C/V/zhe/V/zhi","2C/but/MV/A/zhe/A","3C/V/在/A/N","3D/今/zhi/V/zhe/,/亦将有/V/于/this/N/P","3D/某也/A/,/某也/A/,/可/不/A/哉","3D/有/所/V/N/,/则/不/得/其/V/P","4B/V/N/zhi/N/by/N","4D/A/者/自/V/也/,/而/N/自/V/也/P","4C/A/N/AD/V","4C/V/N/以/V/N","4D/N/无/N/,/无以/V/N","4D/V/N/者/,/N/之/N/也/P","4D/以/此/V/N/,/何/N/不/V/Q","4D/噫/,/A/N/ye/,/N/谁/与/V/Q","4D/此/谓/V/N/在/V/其/N/P","4D/今/夫/N/,/一/N/之/多/,/及/其/A/A/P","4E/上/不/V/N/,/下/不/V/N/P","4D/欲/V/其/N/者/,/先/V/其/N/P","4C/不/以/N/V/,/不/以/N/V/P","4D/V/之/不/为/N/,/V/之/不/为/N/P","4D/然/则/A/N/自/N/V/矣/P","5B/请/V/N/zhi/N/中/,/是/N/zhi/N/P","5D/今/V/N/以/V/A/N","5B/N/乃/V/V/N/zhi/N","5B/N/N/无/V/,/V/而/必/V/P","5C/本/MV/V/A/,/anti/V/N/N","5D/V/N/而/V/之/者/,/非/其/N/AD/也/P","5D/以/A/N/为/N/者/,/N/MV/弗/而/V/之/P","5D/A/N/之/N/,/like/N/like/N/P","5D/故/夫/A/N/之/N/,/不/可/make/其/V/于/N/也/P","5B/今/N/乃/A/N/A/N","5D/N/V/而/A/N/V/也","5E/不/有/A/N/,/何/V/A/N/Q","5C/N/之/无/N/,/N/V/之/N","6C/A/N/为/N/兮/,/A/N/为/N/P","6D/以/N/V/,/like/V/N/V/N","6D/A/zhi/V/N/,/亦/like/今/zhi/V/N/,/A/夫/P","6D/A/者/V/而/V/之/,/A/者/V/而/V/之/P","6D/若/居/A/N/之/N/,/则/当/A/N/之/V/P","6B/N/V/,/V/N/V/N","6E/V/N/之/N/,/为/N/V/者/,/可以/V/矣/P","6D/V/MV/with/其/N/,/而/V/MV/V/以/N/者/,/N/也/P","6D/A/N/必/有/A/N/V/之者/、/予/可/无/N/也/P","6E/虽/V/V/A/A/,/A/A/不/同/P","6D/将/有/V/,/则/V/A/N/以/V/N/P","6D/不/V/N/,/不/V/N/,/当/以/AD/V/论/P","6D/A/则为/V/N/,/A/则为/V/N/P","6D/N/受/命/于/N/,/固/AD/然/V/于/A/N/P","6D/N/无/N/则/V/,/N/无/N/则/V/P","6D/N/A/N/A/,/则/所/V/得/其/A/P","6D/常/有/N/V/A/N/,/请/N/为/N/P","6D/V/N/而/不/能/V/,/V/而/不/能/V/,/N/也/P","7D/夫/A/之/N/V/N/者/,/其/所以/AD/V/者/N/也/P","7D/N/V/以/N/V/,/V/不/V/N","7C/N/N/V/N/,/A/于/N/N","7D/MV/AD/V/A/N/,/but/V/V/不/A","7E/或/V/N/V/N/,/V/N/于/N","7D/A/N/之/N/不在/N/,/在乎/A/N/之/N/也/P","7D/A/N/之/N/,/V/之/N/而/V/之/N/也/P","7D/是故/A/N/不必不如/N/,/N/不必/A/于/A/N/P","7B/有/A/N/、/A/N/、/A/N/之/N/P","8E/虽/N/A/N/A/,/所/以/V/N/,其/N/A/ye/P","8B/like/A/N/V/N/,/不/V/N/V/之/N/P","8B/N/A/即/N/A/,/N/A/即/N/A/P","8D/何必/V/N/V/N/,/V/N/zhi/N/N/哉/P","8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P","8D/是/故/A/N/有/A/N/,/必/AD/V/以/得/之/,/AD/V/以/失/之/P","8C/V/N/A/A/,/V/N/A/A","8B/N/在/A/N/,/A/N/zhi/A/,/V/于/N/P","8C/A/N/AD/V/,/N/N/AD/V","8D/虽/无/N/N/zhi/V/,/亦/V/以/AD/V/A/N/P","8D/N/V/N/为/N/,/N/V/N/为/N/P","8D/故/V/A/N/者/,/当/V/A/N/之/A/N/P","8D/N/V/于/A/N/之上/,/AD/V/于/A/N/之间/P","8C/使/其/A/N/AD/V/,/A/N/AD/V/P","9D/A/N/V/zhi/而不/V/zhi/、亦/make/A/N/er/复/V/A/N/ye/P","9B/N/MV/V/N/V/V/,/but/N/N/AD/V","9D/以/N/,/当/V/A/N/,/非/N/V/N/所/MV/AD/V/P","9C/此/N/有/A/N/A/N/,/A/N/A/N/P","9B/是/N/ye/,/N/A/N/A/,/N/A/N/A/P"]}}';
28 |
29 | this.Normal_Characters =
30 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=1234567890"; //表内有映射的所有字符组成的字符串
31 | this.LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
32 | this.BIG_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
33 | this.NUMBERS = "1234567890";
34 | this.SYMBOLS = "+/=";
35 | this.NUMBERSYMBOL = "0123456789+/=";
36 |
37 | this.NULL_STR = "孎"; //默认忽略的占位字符,一个生僻字。
38 | this.callback = callback;
39 |
40 | this.RoundObufsHelper = new RoundObfus(key, callback);
41 |
42 | this.Map_Obj = JSON.parse(this.Map);
43 |
44 | this.CompatibilityDecodeTable = { q: ["褔"] }; //兼容上个版本的密文解密
45 | this.DecodeTable = {};
46 | this.PayloadLetter = "";
47 | this.InitDecodeTable();
48 | }
49 | RoundKeyMatch(keyIn) {
50 | return this.RoundObufsHelper.RoundKeyMatch(keyIn);
51 | }
52 | DRoundKeyMatch(keyIn) {
53 | return this.RoundObufsHelper.DRoundKeyMatch(keyIn);
54 | }
55 | RoundKey() {
56 | this.RoundObufsHelper.RoundKey();
57 | return;
58 | }
59 |
60 | getCryptText(text, type) {
61 | //查表函数
62 | let letter = String(text); //源文本
63 | let idx, idx2, idx3, idx4;
64 |
65 | idx = this.LETTERS.indexOf(letter); //是否是小写字母
66 | idx2 = this.BIG_LETTERS.indexOf(letter); //是否是大写字母
67 | idx3 = this.NUMBERS.indexOf(letter); //是否是数字
68 | idx4 = this.SYMBOLS.indexOf(letter); //是否是符号
69 |
70 | //判断给定字符的类型
71 | if (idx != -1 || idx2 != -1) {
72 | if (type == "N") {
73 | for (let key in this.Map_Obj["Actual"]["N"]["alphabet"]) {
74 | if (this.Map_Obj["Actual"]["N"]["alphabet"].hasOwnProperty(key)) {
75 | if (key == letter) {
76 | let s2 =
77 | this.Map_Obj["Actual"]["N"]["alphabet"][
78 | this.RoundKeyMatch(key)
79 | ];
80 | return s2;
81 | } else if (key.toUpperCase() == letter) {
82 | let s2 = String(
83 | this.Map_Obj["Actual"]["N"]["alphabet"][
84 | this.RoundKeyMatch(key.toUpperCase())
85 | ]
86 | );
87 | return s2;
88 | }
89 | }
90 | }
91 | } else if (type == "V") {
92 | for (let key in this.Map_Obj["Actual"]["V"]["alphabet"]) {
93 | if (this.Map_Obj["Actual"]["V"]["alphabet"].hasOwnProperty(key)) {
94 | if (key == letter) {
95 | let s2 =
96 | this.Map_Obj["Actual"]["V"]["alphabet"][
97 | this.RoundKeyMatch(key)
98 | ];
99 | return s2;
100 | } else if (key.toUpperCase() == letter) {
101 | let s2 = String(
102 | this.Map_Obj["Actual"]["V"]["alphabet"][
103 | this.RoundKeyMatch(key.toUpperCase())
104 | ]
105 | );
106 | return s2;
107 | }
108 | }
109 | }
110 | } else if (type == "A") {
111 | for (let key in this.Map_Obj["Actual"]["A"]["alphabet"]) {
112 | if (this.Map_Obj["Actual"]["A"]["alphabet"].hasOwnProperty(key)) {
113 | if (key == letter) {
114 | let s2 =
115 | this.Map_Obj["Actual"]["A"]["alphabet"][
116 | this.RoundKeyMatch(key)
117 | ];
118 | return s2;
119 | } else if (key.toUpperCase() == letter) {
120 | let s2 = String(
121 | this.Map_Obj["Actual"]["A"]["alphabet"][
122 | this.RoundKeyMatch(key.toUpperCase())
123 | ]
124 | );
125 | return s2;
126 | }
127 | }
128 | }
129 | } else if (type == "AD") {
130 | for (let key in this.Map_Obj["Actual"]["AD"]["alphabet"]) {
131 | if (this.Map_Obj["Actual"]["AD"]["alphabet"].hasOwnProperty(key)) {
132 | if (key == letter) {
133 | let s2 =
134 | this.Map_Obj["Actual"]["AD"]["alphabet"][
135 | this.RoundKeyMatch(key)
136 | ];
137 | return s2;
138 | } else if (key.toUpperCase() == letter) {
139 | let s2 = String(
140 | this.Map_Obj["Actual"]["AD"]["alphabet"][
141 | this.RoundKeyMatch(key.toUpperCase())
142 | ]
143 | );
144 | return s2;
145 | }
146 | }
147 | }
148 | }
149 | } else if (idx3 != -1 || idx4 != -1) {
150 | if (type == "N") {
151 | for (let key in this.Map_Obj["Actual"]["N"]["numbersymbol"]) {
152 | if (this.Map_Obj["Actual"]["N"]["numbersymbol"].hasOwnProperty(key)) {
153 | if (key == letter) {
154 | let s2 =
155 | this.Map_Obj["Actual"]["N"]["numbersymbol"][
156 | this.RoundKeyMatch(key)
157 | ];
158 | return s2;
159 | }
160 | }
161 | }
162 | } else if (type == "V") {
163 | for (let key in this.Map_Obj["Actual"]["V"]["numbersymbol"]) {
164 | if (this.Map_Obj["Actual"]["V"]["numbersymbol"].hasOwnProperty(key)) {
165 | if (key == letter) {
166 | let s2 =
167 | this.Map_Obj["Actual"]["V"]["numbersymbol"][
168 | this.RoundKeyMatch(key)
169 | ];
170 | return s2;
171 | }
172 | }
173 | }
174 | } else if (type == "A") {
175 | for (let key in this.Map_Obj["Actual"]["A"]["numbersymbol"]) {
176 | if (this.Map_Obj["Actual"]["A"]["numbersymbol"].hasOwnProperty(key)) {
177 | if (key == letter) {
178 | let s2 =
179 | this.Map_Obj["Actual"]["A"]["numbersymbol"][
180 | this.RoundKeyMatch(key)
181 | ];
182 | return s2;
183 | }
184 | }
185 | }
186 | } else if (type == "AD") {
187 | for (let key in this.Map_Obj["Actual"]["AD"]["numbersymbol"]) {
188 | if (
189 | this.Map_Obj["Actual"]["AD"]["numbersymbol"].hasOwnProperty(key)
190 | ) {
191 | if (key == letter) {
192 | let s2 =
193 | this.Map_Obj["Actual"]["AD"]["numbersymbol"][
194 | this.RoundKeyMatch(key)
195 | ];
196 | return s2;
197 | }
198 | }
199 | }
200 | }
201 | }
202 | /* v8 ignore next 2 */
203 | return this.NULL_STR;
204 | }
205 |
206 | findOriginText(text) {
207 | //反向查表函数
208 | let letter = String(text);
209 | let res;
210 | for (let key in this.DecodeTable) {
211 | this.DecodeTable[key].forEach((item) => {
212 | if (letter == item) {
213 | res = this.DRoundKeyMatch(key);
214 | }
215 | });
216 | }
217 | if (res) {
218 | return res;
219 | } else {
220 | /* v8 ignore next 2 */
221 | return this.NULL_STR;
222 | }
223 | }
224 |
225 | InitDecodeTable() {
226 | for (let i = 0; i < 52; i++) {
227 | this.DecodeTable[this.LETTERS[i]] = [];
228 | this.DecodeTable[this.LETTERS[i]].push(
229 | this.Map_Obj["Actual"]["N"]["alphabet"][this.LETTERS[i]]
230 | );
231 | this.DecodeTable[this.LETTERS[i]].push(
232 | this.Map_Obj["Actual"]["A"]["alphabet"][this.LETTERS[i]]
233 | );
234 | this.DecodeTable[this.LETTERS[i]].push(
235 | this.Map_Obj["Actual"]["V"]["alphabet"][this.LETTERS[i]]
236 | );
237 | if (this.CompatibilityDecodeTable.hasOwnProperty(this.LETTERS[i])) {
238 | this.CompatibilityDecodeTable[this.LETTERS[i]].forEach((item) => {
239 | this.DecodeTable[this.LETTERS[i]].push(item);
240 | this.PayloadLetter = this.PayloadLetter + item;
241 | });
242 | }
243 | this.PayloadLetter =
244 | this.PayloadLetter +
245 | this.Map_Obj["Actual"]["N"]["alphabet"][this.LETTERS[i]];
246 | this.PayloadLetter =
247 | this.PayloadLetter +
248 | this.Map_Obj["Actual"]["A"]["alphabet"][this.LETTERS[i]];
249 | this.PayloadLetter =
250 | this.PayloadLetter +
251 | this.Map_Obj["Actual"]["V"]["alphabet"][this.LETTERS[i]];
252 | if (
253 | this.Map_Obj["Actual"]["A"]["alphabet"][this.LETTERS[i]] !=
254 | this.Map_Obj["Actual"]["AD"]["alphabet"][this.LETTERS[i]]
255 | ) {
256 | this.DecodeTable[this.LETTERS[i]].push(
257 | this.Map_Obj["Actual"]["AD"]["alphabet"][this.LETTERS[i]]
258 | );
259 | this.PayloadLetter =
260 | this.PayloadLetter +
261 | this.Map_Obj["Actual"]["AD"]["alphabet"][this.LETTERS[i]];
262 | }
263 | }
264 | for (let i = 0; i < 13; i++) {
265 | this.DecodeTable[this.NUMBERSYMBOL[i]] = [];
266 | this.DecodeTable[this.NUMBERSYMBOL[i]].push(
267 | this.Map_Obj["Actual"]["N"]["numbersymbol"][this.NUMBERSYMBOL[i]]
268 | );
269 | this.DecodeTable[this.NUMBERSYMBOL[i]].push(
270 | this.Map_Obj["Actual"]["A"]["numbersymbol"][this.NUMBERSYMBOL[i]]
271 | );
272 | this.DecodeTable[this.NUMBERSYMBOL[i]].push(
273 | this.Map_Obj["Actual"]["V"]["numbersymbol"][this.NUMBERSYMBOL[i]]
274 | );
275 | this.PayloadLetter =
276 | this.PayloadLetter +
277 | this.Map_Obj["Actual"]["N"]["numbersymbol"][this.NUMBERSYMBOL[i]];
278 | this.PayloadLetter =
279 | this.PayloadLetter +
280 | this.Map_Obj["Actual"]["A"]["numbersymbol"][this.NUMBERSYMBOL[i]];
281 | this.PayloadLetter =
282 | this.PayloadLetter +
283 | this.Map_Obj["Actual"]["V"]["numbersymbol"][this.NUMBERSYMBOL[i]];
284 | if (
285 | this.Map_Obj["Actual"]["A"]["numbersymbol"][this.NUMBERSYMBOL[i]] !=
286 | this.Map_Obj["Actual"]["AD"]["numbersymbol"][this.NUMBERSYMBOL[i]]
287 | ) {
288 | this.DecodeTable[this.NUMBERSYMBOL[i]].push(
289 | this.Map_Obj["Actual"]["AD"]["numbersymbol"][this.NUMBERSYMBOL[i]]
290 | );
291 | this.PayloadLetter =
292 | this.PayloadLetter +
293 | this.Map_Obj["Actual"]["AD"]["numbersymbol"][this.NUMBERSYMBOL[i]];
294 | }
295 | }
296 | }
297 |
298 | avoidAdjacentDuplicates(arr) {
299 | if (arr.length <= 1) return arr;
300 | const newArr = [...arr];
301 | let hasAdjacent = true;
302 | let maxTries = newArr.length;
303 |
304 | while (hasAdjacent && maxTries > 0) {
305 | hasAdjacent = false;
306 | for (let i = 0; i < newArr.length - 1; i++) {
307 | if (newArr[i] === newArr[i + 1]) {
308 | hasAdjacent = true;
309 | // 尝试在i+2位置之后找一个可以交换的元素
310 | for (let j = i + 2; j < newArr.length; j++) {
311 | if (
312 | newArr[j] !== newArr[i] &&
313 | (j === 0 || newArr[j - 1] !== newArr[i]) &&
314 | (j === newArr.length - 1 || newArr[j + 1] !== newArr[i])
315 | ) {
316 | [newArr[i + 1], newArr[j]] = [newArr[j], newArr[i + 1]];
317 | break;
318 | }
319 | }
320 | break;
321 | }
322 | }
323 | maxTries--;
324 | }
325 | return newArr;
326 | }
327 |
328 | mergeNumbers(arr, factor) {
329 | // 分离小于3的数字和其他数字
330 | const lessThan3 = arr.filter((num) => num < 3);
331 | const rest = arr.filter((num) => num >= 3);
332 |
333 | // 根据因子计算需要保留的小于3的数字数量
334 | const preserveCount = Math.max(
335 | 0,
336 | Math.floor((1 - factor) * lessThan3.length)
337 | );
338 |
339 | // 保留部分数字
340 | const preserved = [];
341 | const toMerge = [];
342 |
343 | // 随机选择要保留的数字
344 | const temp = [...lessThan3];
345 | for (let i = 0; i < preserveCount; i++) {
346 | const randomIndex = Math.floor(Math.random() * temp.length);
347 | preserved.push(temp.splice(randomIndex, 1)[0]);
348 | }
349 | toMerge.push(...temp);
350 |
351 | // 如果没有需要合并的数字,直接返回
352 | if (toMerge.length === 0) {
353 | return [...shuffle(rest), ...shuffle(preserved)];
354 | }
355 |
356 | // 计算需要合并的总和
357 | const sum = toMerge.reduce((acc, val) => acc + val, 0);
358 |
359 | // 计算合并后的数字数量范围
360 | const minSegments = Math.ceil(sum / 9); // 最少段数
361 | const maxSegments = Math.min(toMerge.length, Math.floor(sum / 3)); // 最多段数
362 | let bestSegmentCount = minSegments;
363 |
364 | // 寻找最优合并段数
365 | let minVariance = Infinity;
366 | for (let k = minSegments; k <= maxSegments; k++) {
367 | const base = Math.floor(sum / k);
368 | const remainder = sum % k;
369 | const values = [];
370 |
371 | // 生成k个数值
372 | for (let i = 0; i < k; i++) {
373 | values.push(i < remainder ? base + 1 : base);
374 | }
375 |
376 | // 计算方差(均匀度)
377 | const mean = sum / k;
378 | const variance =
379 | values.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / k;
380 |
381 | // 更新最优解
382 | if (variance < minVariance) {
383 | minVariance = variance;
384 | bestSegmentCount = k;
385 | }
386 | }
387 |
388 | // 生成合并后的数字
389 | const base = Math.floor(sum / bestSegmentCount);
390 | const remainder = sum % bestSegmentCount;
391 | const merged = [];
392 |
393 | for (let i = 0; i < bestSegmentCount; i++) {
394 | merged.push(i < remainder ? base + 1 : base);
395 | }
396 |
397 | // 组合结果并返回
398 | return [...shuffle(rest), ...shuffle(preserved), ...merged];
399 | }
400 |
401 | processArray(twoDArray, factor) {
402 | return twoDArray.map((subArray) => {
403 | // 检查是否需要合并
404 | if (subArray.length > 6) {
405 | const countLessThan3 = subArray.filter((num) => num < 3).length;
406 | if (countLessThan3 / subArray.length > 0.35) {
407 | subArray = this.mergeNumbers(subArray, factor);
408 | }
409 | }
410 |
411 | // 打乱顺序
412 | subArray = shuffle(subArray);
413 | // 避免相邻重复
414 | return this.avoidAdjacentDuplicates(subArray);
415 | });
416 | }
417 |
418 | distributeInteger(num) {
419 | //把文言文密文的载荷根据一定比例分成三份(一段)
420 | if (num <= 3) {
421 | // 如果 num 小于等于 3,则无法满足每个元素都大于 0 的要求
422 | return []; // 返回空数组,表示无法分配
423 | }
424 |
425 | let maxPart = Math.floor(num * 0.2); // 计算每个部分的最大值
426 | let remaining = num - 2 * maxPart; // 计算剩余部分
427 |
428 | const result = [maxPart, remaining, maxPart]; // 创建包含三个整数的数组
429 |
430 | return result;
431 | }
432 |
433 | distributePayload(n) {
434 | // 如果载荷量太大,那么自动把载荷分为等大小的部分(段落),然后分别处理
435 | if (n === 0) return [0];
436 | const k = Math.ceil(n / 100);
437 | const base = Math.floor(n / k);
438 | const remainder = n % k;
439 | const parts = [];
440 | for (let i = 0; i < remainder; i++) {
441 | parts.push(base + 1);
442 | }
443 | for (let i = remainder; i < k; i++) {
444 | parts.push(base);
445 | }
446 | return parts;
447 | }
448 |
449 | selectSentence(PayloadLength, RandomIndex = 0, p, l) {
450 | //句式选择函数。
451 | //P 强制对仗骈文
452 | //L 强制多用逻辑句式
453 | //句式选择算法
454 | //RandomIndex 随机指数,越大,给出的句式越随机,最大100。
455 | /* v8 ignore next 7 */
456 | if (RandomIndex > 100 || RandomIndex < 0) {
457 | //错误的输入。
458 | throw "Incorrect Random Index";
459 | }
460 | if (p && l) {
461 | throw "Contradictory Mode Setting";
462 | }
463 |
464 | if (PayloadLength > 100) {
465 | //如果密文太长了,那么自动分段
466 | let distributedPayload = this.distributePayload(PayloadLength);
467 | let Result = [];
468 | distributedPayload.forEach((Payload) => {
469 | Result = Result.concat(this.selectSentence(Payload, RandomIndex, p, l));
470 | Result[Result.length - 1].push("Z");
471 | try {
472 | if (this.callback != null)
473 | this.callback(new CallbackObj("ENC_SENTENCES", Result));
474 | } catch (err) {}
475 | });
476 | return Result;
477 | }
478 |
479 | let selectRand;
480 |
481 | let DividedPayload = this.distributeInteger(PayloadLength); //把Payload平均分配给三个部分。
482 |
483 | let SegmentedPayload = [new Array(), new Array(), new Array()]; //二维数组,保存分配之后的负载。
484 |
485 | let ElementResult = []; //最终要返回的语素序列
486 |
487 | //分配负载到一个二维数组中
488 | for (let i = 0; i < 3; i++) {
489 | //第一重循环,选择Payload
490 |
491 | let Payload = DividedPayload[i];
492 |
493 | for (let a = 0; a < Payload; ) {
494 | //第二重循环,用算法选择句式,满足载荷
495 | selectRand = GetRandomIndex(101) + RandomIndex;
496 | let PossiblePayload = [];
497 | for (let b = 1; b <= Payload - a; b++) {
498 | //三重,求取所有可能载荷。
499 | if (b == 9) {
500 | //最大为9
501 | PossiblePayload.push(b);
502 | break;
503 | }
504 | PossiblePayload.push(b);
505 | }
506 | //这里给出的可能载荷数组应当是从小到大的。
507 | let TargetPayload;
508 | if (selectRand <= 100) {
509 | //选择贪心最优解之一
510 | if (PossiblePayload[PossiblePayload.length - 1] > 6) {
511 | //如果可以选到6以上
512 | let GreedyRand = GetRandomIndex(91); //在三个多逻辑句式的载荷量之间随机选一个
513 | if (GreedyRand < 30) {
514 | TargetPayload = PossiblePayload[PossiblePayload.length - 3];
515 | } else if (GreedyRand >= 30 && GreedyRand < 60) {
516 | TargetPayload = PossiblePayload[PossiblePayload.length - 2];
517 | } else {
518 | TargetPayload = PossiblePayload[PossiblePayload.length - 1];
519 | }
520 | } else {
521 | TargetPayload = PossiblePayload.pop(); //目标Payload,参照这个去库里寻句式。
522 | }
523 | } else if (selectRand > 100 && selectRand <= 200) {
524 | //随机选择一个,不一定是最优解
525 | TargetPayload =
526 | PossiblePayload[GetRandomIndex(PossiblePayload.length)];
527 | }
528 |
529 | SegmentedPayload[i].push(TargetPayload);
530 | a = a + TargetPayload;
531 | }
532 | }
533 |
534 | SegmentedPayload = this.processArray(
535 | SegmentedPayload,
536 | 1 - RandomIndex / 100
537 | );
538 |
539 | //开始根据分配好的载荷执行组句
540 | for (let i = 0; i < 3; i++) {
541 | let Lib = "";
542 |
543 | switch (i) {
544 | case 0:
545 | Lib = "Begin";
546 | break;
547 | case 1:
548 | Lib = "Main";
549 | break;
550 | case 2:
551 | Lib = "End";
552 | break;
553 | }
554 |
555 | for (let a = 0; a < SegmentedPayload[i].length; a++) {
556 | let PossibleSentences = []; // 所有挑选出来的可能句式,选择时任选其一。
557 | let PossiblePianSentences = []; // 所有可能的骈文句式。
558 | let PossibleLogicSentences = []; // 所有可能的逻辑句式
559 |
560 | let TargetPayload = SegmentedPayload[i][a]; //目标负载
561 | for (let c = 0; c < this.Map_Obj["Sentences"][Lib].length; c++) {
562 | //开始选择句式
563 | let Sentence = this.Map_Obj["Sentences"][Lib][c].split("/"); //Sentence是列表,按照/分割的句式
564 | if (parseInt(Sentence[0]) == TargetPayload) {
565 | PossibleSentences.push(Sentence.slice(1));
566 | if (Sentence[0][1] == "C" || Sentence[0][1] == "E") {
567 | PossiblePianSentences.push(Sentence.slice(1));
568 | }
569 | if (Sentence[0][1] == "D" || Sentence[0][1] == "E") {
570 | PossibleLogicSentences.push(Sentence.slice(1));
571 | }
572 | }
573 | }
574 |
575 | let TargetSentence;
576 | if (p) {
577 | if (PossiblePianSentences.length != 0) {
578 | TargetSentence =
579 | PossiblePianSentences[
580 | GetRandomIndex(PossiblePianSentences.length)
581 | ];
582 | } else {
583 | TargetSentence =
584 | PossibleSentences[GetRandomIndex(PossibleSentences.length)];
585 | }
586 | } else if (l) {
587 | if (PossibleLogicSentences.length != 0) {
588 | TargetSentence =
589 | PossibleLogicSentences[
590 | GetRandomIndex(PossibleLogicSentences.length)
591 | ];
592 | } else {
593 | TargetSentence =
594 | PossibleSentences[GetRandomIndex(PossibleSentences.length)];
595 | }
596 | } else {
597 | let LogiRand = GetRandomIndex(101); //给骈文和逻辑句式一个直接的插入概率
598 | //LogiRand max 200
599 | if (LogiRand > 25) {
600 | // 如果大于25,也就是说进入此的概率是75%
601 | TargetSentence =
602 | PossibleSentences[GetRandomIndex(PossibleSentences.length)]; //随机选句子
603 | } else {
604 | let PianOrLogi = GetRandomIndex(2); //0 or 1
605 |
606 | if (PianOrLogi == 0) {
607 | //选骈文。
608 | if (PossiblePianSentences.length != 0) {
609 | //没有可用句式就随机选
610 | TargetSentence =
611 | PossiblePianSentences[
612 | GetRandomIndex(PossiblePianSentences.length)
613 | ];
614 | } else {
615 | TargetSentence =
616 | PossibleSentences[GetRandomIndex(PossibleSentences.length)];
617 | }
618 | } else {
619 | //选逻辑句式
620 | if (PossibleLogicSentences.length != 0) {
621 | TargetSentence =
622 | PossibleLogicSentences[
623 | GetRandomIndex(PossibleLogicSentences.length)
624 | ];
625 | } else {
626 | TargetSentence =
627 | PossibleSentences[GetRandomIndex(PossibleSentences.length)];
628 | }
629 | }
630 | }
631 | }
632 | ElementResult.push(TargetSentence);
633 | try {
634 | if (this.callback != null)
635 | this.callback(new CallbackObj("ENC_SENTENCES", ElementResult));
636 | } catch (err) {}
637 | }
638 | }
639 |
640 | return ElementResult;
641 | }
642 |
643 | enMap(OriginStr, q, r, p, l, t) {
644 | let TempStr1 = "",
645 | temp = "";
646 |
647 | let size = OriginStr.length;
648 |
649 | //从这里开始做文章,开始文言文仿真,以及三重轮转对表。
650 |
651 | let Sentence = this.selectSentence(OriginStr.length, r, p, l); //选择句式
652 | let i = 0;
653 | let Finished = false;
654 | let hasSpecialEndSymbol = false; //标记,此短句是否有特殊符号
655 | let CommaCounter = 0; //逗号/顿号计数
656 | let CommaNumInSentence = 0; //统计短句内部的逗号
657 |
658 | let LastQuoteMark = false; //为避免连续冒号和引号,设立状态变量。
659 | let LastQuote = 0; //下引号的状态(上引号的距离)
660 |
661 | let NoAutoSymbol = false; //指定某一轮不自动添加标点符号
662 |
663 | this.RoundKey(); //首次对表前,先转动一次转轮
664 | for (let j = 0; j < Sentence.length; j++) {
665 | hasSpecialEndSymbol = false;
666 | CommaNumInSentence = 0;
667 | for (let k = 0; k < Sentence[j].length; k++) {
668 | //统计短句内部的逗号
669 | if (Sentence[j][k] == "," || Sentence[j][k] == "、") {
670 | CommaNumInSentence++;
671 | }
672 | }
673 | if (LastQuoteMark) {
674 | LastQuote++;
675 | }
676 | for (let k = 0; k < Sentence[j].length; k++) {
677 | if (
678 | Sentence[j][k] == "V" ||
679 | Sentence[j][k] == "N" ||
680 | Sentence[j][k] == "A" ||
681 | Sentence[j][k] == "AD"
682 | ) {
683 | //拆解句式,对表。
684 | //这里会把载荷字给映射到对应的汉字,但是根据载荷字的类型而有所不同。
685 | temp = OriginStr[i];
686 | TempStr1 = TempStr1 + this.getCryptText(temp, Sentence[j][k]);
687 | this.RoundKey(); //每加密一个载荷字,就旋转一次转轮。
688 | i++;
689 | } else if (Sentence[j][k] == "MV") {
690 | //情态动词(非载荷字),随机选择。
691 | TempStr1 =
692 | TempStr1 +
693 | this.Map_Obj["Actual"]["MV"][
694 | GetRandomIndex(this.Map_Obj["Actual"]["MV"].length)
695 | ];
696 | } else if (this.Map_Obj["Virtual"].hasOwnProperty(Sentence[j][k])) {
697 | //虚词(非载荷字),随机选择。
698 | TempStr1 =
699 | TempStr1 +
700 | this.Map_Obj["Virtual"][Sentence[j][k]][
701 | GetRandomIndex(this.Map_Obj["Virtual"][Sentence[j][k]].length)
702 | ];
703 | } else if (Sentence[j][k] == "P") {
704 | //插入句号
705 | //倒数第二个句式禁止出现句号和问号
706 | if (j == Sentence.length - 2) {
707 | continue;
708 | }
709 |
710 | hasSpecialEndSymbol = true;
711 | TempStr1 = TempStr1 + "。";
712 | } else if (Sentence[j][k] == "R") {
713 | //插入冒号和引号
714 | if (LastQuoteMark == false) {
715 | //如果上个句式不是冒号句
716 | LastQuoteMark = true;
717 | TempStr1 = TempStr1 + ":“";
718 | LastQuote = 0;
719 | CommaCounter = 0;
720 | } else if (LastQuote == 1) {
721 | //进入此分支,意味着不会再次添加逗号
722 | LastQuote = 0; //如果两个连续冒号句子,那么会在下一个句子关闭引号,而非本句
723 | TempStr1 = TempStr1 + ","; //加上逗号
724 | NoAutoSymbol = true;
725 | CommaCounter++;
726 | }
727 | //上个块是冒号句,这句话就不增加冒号(禁止连续两个冒号)
728 | } else if (Sentence[j][k] == "Z") {
729 | //插入段落分隔。
730 | if (!hasSpecialEndSymbol) {
731 | hasSpecialEndSymbol = true;
732 | TempStr1 = TempStr1 + "。";
733 | if (i != size) {
734 | //最后一句话后面不插入分隔
735 | TempStr1 = TempStr1 + "\n\n";
736 | }
737 | } else {
738 | if (i != size) {
739 | TempStr1 = TempStr1 + "\n\n";
740 | }
741 | }
742 | } else if (Sentence[j][k] == "Q") {
743 | //插入问号
744 | //倒数第二个句式禁止出现句号和问号
745 | if (j == Sentence.length - 2) {
746 | continue;
747 | }
748 | hasSpecialEndSymbol = true;
749 | TempStr1 = TempStr1 + "?";
750 | } else {
751 | TempStr1 = TempStr1 + Sentence[j][k]; //如果是句式中的其他字符,那么直接追加到句子中
752 | }
753 | if (i == size) {
754 | //如果已填充的有效载荷满足了预计添加的载荷,那么标记已完成。
755 | Finished = true;
756 | }
757 | try {
758 | if (this.callback != null)
759 | this.callback(new CallbackObj("ENC_MAPTEMP", TempStr1));
760 | } catch (err) {}
761 | }
762 | //这里是句式和句式的外层控制循环
763 | if (Finished) {
764 | //如果已完成,检查最后一个句式后是否有特殊符号,没有的话,自动添加句号
765 | if (q && !hasSpecialEndSymbol) {
766 | TempStr1 = TempStr1 + "。";
767 | }
768 | if (q && LastQuoteMark && LastQuote > 0) {
769 | //如果有未完成的冒号句,关闭冒号句
770 | TempStr1 = TempStr1 + "” ";
771 | LastQuote = 0;
772 | LastQuoteMark = false;
773 | }
774 | NoAutoSymbol = false;
775 | try {
776 | if (this.callback != null)
777 | this.callback(new CallbackObj("ENC_MAPTEMP", TempStr1));
778 | } catch (err) {}
779 |
780 | break;
781 | } else {
782 | if (q && !hasSpecialEndSymbol && !NoAutoSymbol) {
783 | //如果连续出现三个以上的逗号,那么自动加上一个句号
784 | let TestCommaCount = CommaCounter + (CommaNumInSentence + 1); //计算本句添加后可能的最大逗号数量
785 | if (CommaCounter < 3 || j == Sentence.length - 2) {
786 | //如果逗号数量没到门槛,而且这不是倒数第二个句式,那么就添加逗号
787 | if (TestCommaCount >= 3 && j != Sentence.length - 2) {
788 | //最大逗号数量也不能超过门槛
789 | TempStr1 = TempStr1 + "。";
790 | CommaCounter = 0;
791 | } else {
792 | if (LastQuoteMark && LastQuote > 0) {
793 | //如果有未完成的冒号句,关闭冒号句
794 | TempStr1 = TempStr1 + "” ";
795 | LastQuote = 0;
796 | LastQuoteMark = false;
797 | }
798 | if (!LastQuoteMark && LastQuote == 0) {
799 | TempStr1 = TempStr1 + ",";
800 | CommaCounter += CommaNumInSentence + 1;
801 | }
802 | }
803 | } else {
804 | //超过门槛就加上句号,然后重置逗号计数器
805 | TempStr1 = TempStr1 + "。";
806 | CommaCounter = 0;
807 | }
808 | }
809 | if (LastQuoteMark && LastQuote > 0) {
810 | //如果有未完成的冒号句,关闭冒号句
811 | TempStr1 = TempStr1 + "” ";
812 | LastQuote = 0;
813 | LastQuoteMark = false;
814 | }
815 | if (hasSpecialEndSymbol) {
816 | CommaCounter = 0;
817 | }
818 | if (NoAutoSymbol) {
819 | NoAutoSymbol = false;
820 | }
821 | }
822 | }
823 |
824 | if (!q) {
825 | //如果指定去除标点。除去自动添加的标点外,还要去除句式中的既有标点。
826 | let TempStrQ = "";
827 | for (let i = 0; i < TempStr1.length; i++) {
828 | if (
829 | TempStr1[i] != "," &&
830 | TempStr1[i] != "。" &&
831 | TempStr1[i] != "、" &&
832 | TempStr1[i] != "?" &&
833 | TempStr1[i] != ":" &&
834 | TempStr1[i] != "“" &&
835 | TempStr1[i] != "”" &&
836 | TempStr1[i] != " "
837 | ) {
838 | TempStrQ = TempStrQ + TempStr1[i];
839 | }
840 | }
841 | TempStr1 = TempStrQ;
842 | }
843 |
844 | if (t) {
845 | //如果指定执行繁体字输出
846 | const converter = OpenCC.Converter({ from: "cn", to: "hk" });
847 | let TempStrQ = "";
848 | for (let i = 0; i < TempStr1.length; i++) {
849 | if (
850 | TempStr1[i] != "," &&
851 | TempStr1[i] != "。" &&
852 | TempStr1[i] != "、" &&
853 | TempStr1[i] != "?" &&
854 | TempStr1[i] != ":" &&
855 | TempStr1[i] != "“" &&
856 | TempStr1[i] != "”" &&
857 | TempStr1[i] != " "
858 | ) {
859 | TempStrQ = TempStrQ + converter(TempStr1[i]);
860 | } else {
861 | TempStrQ = TempStrQ + TempStr1[i];
862 | }
863 | }
864 | TempStr1 = TempStrQ;
865 | }
866 |
867 | return TempStr1;
868 | }
869 |
870 | deMap(OriginStr) {
871 | let TempStr1 = "",
872 | TempStrz = "";
873 | let temp = "",
874 | temp2 = "",
875 | group = "",
876 | findtemp = "";
877 | let size = OriginStr.length;
878 |
879 | //强制简体转换
880 | const converter = OpenCC.Converter({ from: "hk", to: "cn" });
881 |
882 | let TempStrQ = "";
883 | for (let i = 0; i < OriginStr.length; i++) {
884 | if (
885 | OriginStr[i] != "," &&
886 | OriginStr[i] != "。" &&
887 | OriginStr[i] != "、" &&
888 | OriginStr[i] != "?" &&
889 | OriginStr[i] != ":" &&
890 | OriginStr[i] != "“" &&
891 | OriginStr[i] != "”" &&
892 | OriginStr[i] != " "
893 | ) {
894 | TempStrQ = TempStrQ + converter(OriginStr[i]);
895 | }
896 | }
897 | OriginStr = TempStrQ;
898 |
899 | //先筛选密文中的所有有效字符
900 | for (let i = 0; i < size; i++) {
901 | temp = OriginStr[i];
902 | if (
903 | temp == this.NULL_STR ||
904 | temp == " " ||
905 | temp == "\n" ||
906 | temp == "\t"
907 | ) {
908 | //如果这是空字符
909 | continue;
910 | } else if (this.PayloadLetter.indexOf(temp) == -1) {
911 | continue;
912 | } else {
913 | //如果不是
914 | TempStrz = TempStrz + temp; //加上
915 | try {
916 | if (this.callback != null)
917 | this.callback(new CallbackObj("DEC_PAYLOAD", TempStrz));
918 | } catch (err) {}
919 | continue;
920 | }
921 | }
922 | size = TempStrz.length;
923 | OriginStr = TempStrz;
924 | this.RoundKey(); //开始轮转逆映射字符,还原Base64字符串
925 | for (let i = 0; i < size; ) {
926 | temp = OriginStr[i];
927 |
928 | findtemp = this.findOriginText(temp); //查找第一个字符的原文
929 | if (findtemp == this.NULL_STR) {
930 | /* v8 ignore next 2 */
931 | throw "Bad Input. Try force encrypt if intended.";
932 | }
933 | TempStr1 = TempStr1 + findtemp; //把找到的原文增加到字符串上
934 | this.RoundKey(); //轮换密钥
935 | i++;
936 | try {
937 | if (this.callback != null)
938 | this.callback(new CallbackObj("DEC_BASE64", TempStr1));
939 | } catch (err) {}
940 | }
941 | TempStr1 = AddPadding(TempStr1); //轮转完成之后,为Base64字符串添加Padding
942 | return TempStr1;
943 | }
944 | }
945 |
946 | export class OldMapper {
947 | constructor(key) {
948 | const Map =
949 | '{"basic":{"alphabet":{"a":["请","上","中","之","等","人","到","年","个","将"],"b":["得","可","并","发","过","协","曲","闭","斋","峦"],"c":["页","于","而","被","无","挽","裕","斜","绪","镜"],"d":["由","把","好","从","会","帕","莹","盈","敬","粒"],"e":["的","在","了","是","为","有","和","我","一","与"],"f":["站","最","号","及","能","迟","鸭","呈","玻","据"],"g":["着","很","此","但","看","浩","附","侃","汐","绸"],"h":["名","呢","又","图","啊","棉","畅","蒸","玫","添"],"i":["对","地","您","给","这","下","网","也","来","你"],"j":["更","天","去","用","只","矽","萌","镁","芯","夸"],"k":["第","者","所","两","里","氢","羟","纽","夏","春"],"l":["自","做","前","二","他","氦","汀","兰","竹","捷"],"m":["家","点","路","至","十","锂","羧","暑","夕","振"],"n":["区","想","向","主","四","铍","烃","惠","芳","岩"],"o":["就","新","吗","该","不","多","还","要","让","大"],"p":["小","如","成","位","其","硼","酞","褔","苑","笋"],"q":["吧","每","机","几","总","碳","铂","涓","绣","悦"],"r":["起","它","内","高","次","氮","铵","奏","鲤","淳"],"s":["非","元","类","五","使","氧","醇","迷","霁","琅"],"t":["首","进","即","没","市","氖","酯","琳","绫","濑"],"u":["后","三","本","都","时","月","或","说","已","以"],"v":["种","快","那","篇","万","钠","炔","柯","睿","琼"],"w":["长","按","报","比","信","硅","烷","静","欣","束"],"x":["再","带","才","全","呀","磷","烯","柔","雪","冰"],"y":["业","却","版","美","们","硫","桉","寒","冻","玖"],"z":["像","走","文","各","当","氯","缬","妃","琉","璃"],"A":["贴","则","老","生","达","商","行","周","证","经"],"B":["事","场","同","化","找","建","手","道","间","式"],"C":["特","城","型","定","接","局","问","重","叫","通"],"D":["件","少","面","金","近","买","听","学","见","称"],"E":["写","选","片","体","组","先","仅","别","表","现"],"F":["雨","泊","注","织","赴","茶","因","设","环","青"],"G":["数","心","子","处","作","项","谁","分","转","字"],"H":["砂","妥","鹦","课","栗","霞","鹉","翌","蕴","憩"],"I":["畔","珑","咫","瑞","玲","郊","蛟","昱","祉","菁"],"J":["铁","宙","耕","琴","铃","瑰","旬","茉","砺","莅"],"K":["钇","莉","筱","森","曳","苹","踵","晰","砥","舀"],"L":["锆","粟","魄","辉","谜","馅","醋","甄","韶","泪"],"M":["钌","倘","祥","善","泉","惦","铠","骏","韵","泣"],"N":["铑","筑","铿","智","禀","磊","桨","檀","荧","铭"],"O":["钯","骐","烛","蔬","凛","溯","困","炯","酿","瑕"],"P":["银","榻","驿","缎","澟","绒","莺","萤","桅","枕"],"Q":["镉","赞","瑾","程","怡","漱","穗","湍","栀","皆"],"R":["碘","礼","饴","舒","芷","麟","沥","描","锄","墩"],"S":["锡","彰","瞻","雅","贮","喵","翊","闪","翎","婉"],"T":["钨","咨","涌","益","嵩","御","饶","纺","栩","稔"],"U":["铋","骆","橘","未","泰","频","琥","囍","浣","裳"],"V":["钕","飒","浇","哦","途","瓢","珀","涨","仓","棠"],"W":["祁","蓬","灿","部","涧","舫","曙","航","礁","渡"],"X":["旺","嫦","漫","佑","钥","谧","葵","咩","诵","绮"],"Y":["阐","译","锻","茜","坞","砌","靛","猫","芮","绚"],"Z":["拌","皎","笙","沃","悟","拓","遨","揽","昼","蔗"]},"numbersymbol":{"0":["卡","风","水","放","花","钾","宏","谊","探","棋"],"1":["需","头","话","曾","楼","钙","吾","恋","菲","遥"],"2":["连","系","门","力","量","钛","苗","氛","鹤","雀"],"3":["书","亿","跟","深","方","钒","鸳","鸯","纸","鸢"],"4":["若","低","谈","明","百","铬","羯","尧","舜","兆"],"5":["关","客","读","双","回","锰","熙","瀚","渊","灯"],"6":["较","品","嘛","单","价","钴","阑","珊","雁","鹂"],"7":["山","西","动","厂","热","锌","鹃","鸠","昆","仑"],"8":["言","笑","度","易","身","镓","乾","坤","澈","饺"],"9":["份","星","千","仍","办","锗","彗","聪","慧","磋"],"+":["集","费","传","室","拉"],"/":["难","界","指","管","具"],"?":["相","儿","李","早","拿"],"-":["科","白","段","飞","住"],".":["利","红","板","光","约"],"(":["变","款","林","夹","院"],")":["服","句","声","务","游"],"[":["股","南","社","阿","远"],"]":["意","换","些","必","赛"],"<":["届","完","乐","彩","讲"],">":["展","帮","且","物","班"],",":["何","流","密","某","房"],"|":["语","亚","常","除","装"],"=":["极","载","题","刚","气"],"@":["米","影","德","世","坐"],"#":["北","招","短","活","斯"],"!":["值","店","树","哪","余"],"~":["盘","速","座","求","创"],"`":["梦","足","半","视","安"],"quot;:["空","歌","派","顶","登"],"%":["夜","云","感","啦","欲"],"^":["边","工","眼","街","奖"],"&":["获","占","理","任","实"],"*":["知","掉","色","讯","克"],"_":["直","评","往","层","园"],"{":["留","靠","亦","罗","营"],"}":["合","尚","产","诚","汨"],":":["曱","朩","杉","杸","歩"],";":["毋","氕","気","氘","氙"]}},"special":{"DECRYPT":{"JP":["桜","込","凪","雫","実","沢"],"CN":["玚","俟","玊","欤","瞐","珏"]}}}';
950 | this.Map_Obj = JSON.parse(Map);
951 |
952 | this.Normal_Characters =
953 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+=_-/?.>,<|`~!@#$%^&*(){}[];:1234567890"; //表内有映射的所有字符组成的字符串
954 | this.LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
955 |
956 | this.BIG_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
957 | this.NUMBERS = "1234567890";
958 | this.SYMBOLS = "+=_-/?.>,<|`~!@#$%^&*(){}[];:";
959 | this.NUMBERSYMBOL = "1234567890+=_-/?.>,<|`~!@#$%^&*(){}[];:";
960 |
961 | this.NULL_STR = "孎"; //默认忽略的占位字符,一个生僻字。
962 |
963 | this.RoundObufsHelper = new RoundObfusOLD(key);
964 | }
965 | RoundKeyMatch(keyIn) {
966 | return this.RoundObufsHelper.RoundKeyMatch(keyIn);
967 | }
968 | DRoundKeyMatch(keyIn) {
969 | return this.RoundObufsHelper.DRoundKeyMatch(keyIn);
970 | }
971 | RoundKey() {
972 | this.RoundObufsHelper.RoundKey();
973 | return;
974 | }
975 | getCryptText(text) {
976 | let letter = String(text); //源文本
977 | let idx, idx2, idx3, idx4;
978 |
979 | idx = this.LETTERS.indexOf(letter); //是否是小写字母
980 | idx2 = this.BIG_LETTERS.indexOf(letter); //是否是大写字母
981 | idx3 = this.NUMBERS.indexOf(letter); //是否是数字
982 | idx4 = this.SYMBOLS.indexOf(letter); //是否是符号
983 | let RandIndex, RandIndex2;
984 |
985 | //判断给定字符的类型
986 | if (idx != -1 || idx2 != -1) {
987 | for (let key in this.Map_Obj["basic"]["alphabet"]) {
988 | if (this.Map_Obj["basic"]["alphabet"].hasOwnProperty(key)) {
989 | if (key == letter) {
990 | RandIndex = GetRandomIndex(
991 | this.Map_Obj["basic"]["alphabet"][this.RoundKeyMatch(key)].length
992 | );
993 | let s2 =
994 | this.Map_Obj["basic"]["alphabet"][this.RoundKeyMatch(key)][
995 | RandIndex
996 | ];
997 | return s2;
998 | } else if (key.toUpperCase() == letter) {
999 | RandIndex = GetRandomIndex(
1000 | this.Map_Obj["basic"]["alphabet"][
1001 | this.RoundKeyMatch(key.toUpperCase())
1002 | ].length
1003 | );
1004 | let s2 = String(
1005 | this.Map_Obj["basic"]["alphabet"][
1006 | this.RoundKeyMatch(key.toUpperCase())
1007 | ][RandIndex]
1008 | );
1009 | return s2;
1010 | }
1011 | }
1012 | }
1013 | } else if (idx3 != -1 || idx4 != -1) {
1014 | for (let key in this.Map_Obj["basic"]["numbersymbol"]) {
1015 | if (this.Map_Obj["basic"]["numbersymbol"].hasOwnProperty(key)) {
1016 | if (key == letter) {
1017 | RandIndex = GetRandomIndex(
1018 | this.Map_Obj["basic"]["numbersymbol"][this.RoundKeyMatch(key)]
1019 | .length
1020 | );
1021 | let s2 =
1022 | this.Map_Obj["basic"]["numbersymbol"][this.RoundKeyMatch(key)][
1023 | RandIndex
1024 | ];
1025 | return s2;
1026 | }
1027 | }
1028 | }
1029 | }
1030 | return this.NULL_STR;
1031 | }
1032 |
1033 | findOriginText(text) {
1034 | let letter = String(text);
1035 | let res;
1036 | for (let key in this.Map_Obj["basic"]["alphabet"]) {
1037 | this.Map_Obj["basic"]["alphabet"][key].forEach((item) => {
1038 | if (letter == item) {
1039 | res = this.DRoundKeyMatch(key);
1040 | }
1041 | });
1042 | }
1043 | if (res) {
1044 | return res;
1045 | }
1046 | for (let key in this.Map_Obj["basic"]["numbersymbol"]) {
1047 | this.Map_Obj["basic"]["numbersymbol"][key].forEach((item) => {
1048 | if (letter == item) {
1049 | res = this.DRoundKeyMatch(key);
1050 | }
1051 | });
1052 | }
1053 | if (res) {
1054 | return res;
1055 | } else {
1056 | return this.NULL_STR;
1057 | }
1058 | }
1059 |
1060 | enMap(OriginStr, q) {
1061 | let TempStr1 = "",
1062 | temp = "",
1063 | temp2 = "",
1064 | group = "";
1065 |
1066 | let size = OriginStr.length;
1067 | this.RoundKey();
1068 | for (let i = 0; i < size; i++) {
1069 | temp = OriginStr[i];
1070 | if (i != size - 1) {
1071 | //一次遍历两个字符,遇到倒数第一个的时候防止越界
1072 | temp2 = OriginStr[i + 1];
1073 | } else {
1074 | temp2 = this.NULL_STR;
1075 | }
1076 | group = temp + temp2;
1077 |
1078 | TempStr1 = TempStr1 + this.getCryptText(temp); //把加密字符加到结果字符串的后面
1079 | this.RoundKey();
1080 | }
1081 |
1082 | if (q) {
1083 | //RoundReset();
1084 | return TempStr1;
1085 | }
1086 |
1087 | //第一个循环结束后,TempStr1应当是完全的密文,但是缺少标志位
1088 | let RandIndex, RandIndex2;
1089 | let Avoid = new Array();
1090 | for (let q = 0; q < 2; q++) {
1091 | //分两次大循环
1092 | let PosToInset = new Array();
1093 | let size = TempStr1.length;
1094 | for (let i = 0; i < size; i++) {
1095 | PosToInset.push(i);
1096 | }
1097 | if (q == 0) {
1098 | //第一次大循环插入JP
1099 | RandIndex = PosToInset[GetRandomIndex(PosToInset.length)];
1100 | RandIndex2 = GetRandomIndex(
1101 | this.Map_Obj["special"]["DECRYPT"]["JP"].length
1102 | );
1103 | let stemp = this.Map_Obj["special"]["DECRYPT"]["JP"][RandIndex2];
1104 | TempStr1 = insertStringAtIndex(TempStr1, stemp, RandIndex);
1105 | for (let z = RandIndex + 1; z < RandIndex + stemp.length; z++) {
1106 | Avoid.push(z);
1107 | }
1108 | } else if (q == 1) {
1109 | //第二次大循环插入CN;
1110 | let AvailPos = new Array();
1111 | AvailPos = difference(PosToInset, Avoid);
1112 |
1113 | RandIndex = AvailPos[GetRandomIndex(AvailPos.length)];
1114 | RandIndex2 = GetRandomIndex(
1115 | this.Map_Obj["special"]["DECRYPT"]["CN"].length
1116 | );
1117 | TempStr1 = insertStringAtIndex(
1118 | TempStr1,
1119 | this.Map_Obj["special"]["DECRYPT"]["CN"][RandIndex2],
1120 | RandIndex
1121 | );
1122 | }
1123 | }
1124 | //RoundReset();
1125 | return TempStr1;
1126 | }
1127 |
1128 | deMap(OriginStr) {
1129 | let TempStr1 = "",
1130 | TempStrz = "";
1131 | let temp = "",
1132 | temp2 = "",
1133 | group = "",
1134 | findtemp = "";
1135 | let size = OriginStr.length;
1136 | for (let i = 0; i < size; i++) {
1137 | temp = OriginStr[i];
1138 | if (
1139 | temp == this.NULL_STR ||
1140 | temp == " " ||
1141 | temp == "\n" ||
1142 | temp == "\t"
1143 | ) {
1144 | //如果这是空字符
1145 | continue;
1146 | } else {
1147 | //如果不是
1148 | TempStrz = TempStrz + temp; //加上
1149 | continue;
1150 | }
1151 | }
1152 | size = TempStrz.length;
1153 | OriginStr = TempStrz;
1154 | this.RoundKey();
1155 | for (let i = 0; i < size; ) {
1156 | temp = OriginStr[i];
1157 | if (i != size - 1) {
1158 | //一次遍历两个字符,遇到倒数第一个的时候防止越界
1159 | temp2 = OriginStr[i + 1];
1160 | } else {
1161 | temp2 = this.NULL_STR;
1162 | }
1163 | group = temp + temp2;
1164 |
1165 | findtemp = this.findOriginText(temp); //查找第一个字符的原文
1166 | if (findtemp == this.NULL_STR) {
1167 | throw "Bad Input. Try force encrypt if intended.";
1168 | }
1169 | TempStr1 = TempStr1 + findtemp; //把找到的原文增加到字符串上
1170 | this.RoundKey(); //轮换密钥
1171 | i++;
1172 | }
1173 | TempStr1 = AddPadding(TempStr1);
1174 |
1175 | return TempStr1;
1176 | }
1177 | }
1178 |
--------------------------------------------------------------------------------
/src/javascript/CompressionHelper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | import * as Unishox from "./unishox2.js";
14 | import pako from "pako";
15 |
16 | import { Uint8ArrayTostring } from "./Misc.js";
17 |
18 | const CHINESE_WEBPAN_LIB = [
19 | "https://",
20 | "lanzou",
21 | "pan.quark.cn",
22 | "pan.baidu.com",
23 | "aliyundrive.com",
24 | "123pan.com",
25 | ];
26 | const INTER_WEBPAN_LIB = [
27 | "https://",
28 | "mypikpak.com",
29 | "mega.nz",
30 | "drive.google.com",
31 | "sharepoint.com",
32 | "1drv.ms",
33 | ];
34 | const CHINESE_WEBSITE_LIB = [
35 | "https://",
36 | "baidu.com",
37 | "b23.tv",
38 | "bilibili.com",
39 | "weibo.com",
40 | "weixin.qq.com",
41 | ];
42 | const INTER_WEBSITE_LIB = [
43 | "https://",
44 | "google.com",
45 | "youtube.com",
46 | "x.com",
47 | "twitter.com",
48 | "telegra.ph",
49 | ];
50 | const INTER_WEBSITE_LIB_2 = [
51 | "https://",
52 | "wikipedia.org",
53 | "github.com",
54 | "pages.dev",
55 | "github.io",
56 | "netlify.app",
57 | ];
58 | const JAPAN_WEBSITE_LIB = [
59 | "https://",
60 | "pixiv.net",
61 | "nicovideo.jp",
62 | "dlsite.com",
63 | "line.me",
64 | "dmm.com",
65 | ];
66 | const PIRACY_WEBSITE_LIB = [
67 | "https://",
68 | "nyaa.si",
69 | "bangumi.moe",
70 | "thepiratebay.org",
71 | "e-hentai.org",
72 | "exhentai.org",
73 | ];
74 | const GENERIC_TLINK_LIB = [
75 | "https://",
76 | "magnet:?xt=urn:btih:",
77 | "magnet:?xt=urn:sha1:",
78 | "ed2k://",
79 | "thunder://",
80 | "torrent",
81 | ];
82 | const GENERIC_LINK_LIB_1 = ["https://", ".cn", ".com", ".net", ".org", ".xyz"];
83 | const GENERIC_LINK_LIB_2 = ["https://", ".info", ".moe", ".cc", ".co", ".dev"];
84 | const GENERIC_LINK_LIB_3 = ["https://", ".io", ".us", ".eu", ".jp", ".de"];
85 | const GENERIC_LINK_LIB_4 = [
86 | "https://",
87 | ".top",
88 | ".one",
89 | ".online",
90 | ".me",
91 | ".ca",
92 | ];
93 |
94 | function GZIP_COMPRESS(Data) {
95 | let DataOutput = pako.gzip(Data);
96 |
97 | if (DataOutput.byteLength >= Data.byteLength) {
98 | return Data;
99 | }
100 | return DataOutput;
101 | }
102 | function GZIP_DECOMPRESS(Data) {
103 | const firstTwoBytes = new Uint8Array(Data.buffer, 0, 2);
104 | if (firstTwoBytes[0] === 0x1f && firstTwoBytes[1] === 0x8b) {
105 | // Likely compressed with gzip
106 | let DataOutput = pako.ungzip(Data);
107 | return DataOutput;
108 | } else {
109 | return Data;
110 | }
111 | }
112 | function UNISHOX_COMPRESS(Data) {
113 | let CompressedStrCharArray = new Uint8Array(2048);
114 | let Datastr = Uint8ArrayTostring(Data);
115 | let libmark = 255;
116 |
117 | for (let i = 1; i < 6; i++) {
118 | if (Datastr.indexOf(CHINESE_WEBPAN_LIB[i]) != -1) {
119 | libmark = 254;
120 | break;
121 | }
122 | if (Datastr.indexOf(INTER_WEBPAN_LIB[i]) != -1) {
123 | libmark = 245;
124 | break;
125 | }
126 | }
127 | if (libmark == 255) {
128 | for (let i = 1; i < 6; i++) {
129 | if (Datastr.indexOf(CHINESE_WEBSITE_LIB[i]) != -1) {
130 | libmark = 253;
131 | break;
132 | }
133 | if (Datastr.indexOf(INTER_WEBSITE_LIB[i]) != -1) {
134 | libmark = 252;
135 | break;
136 | }
137 | if (Datastr.indexOf(INTER_WEBSITE_LIB_2[i]) != -1) {
138 | libmark = 244;
139 | break;
140 | }
141 | if (Datastr.indexOf(JAPAN_WEBSITE_LIB[i]) != -1) {
142 | libmark = 251;
143 | break;
144 | }
145 | if (Datastr.indexOf(PIRACY_WEBSITE_LIB[i]) != -1) {
146 | libmark = 250;
147 | break;
148 | }
149 | }
150 | }
151 | if (libmark == 255) {
152 | for (let i = 1; i < 6; i++) {
153 | if (Datastr.indexOf(GENERIC_TLINK_LIB[i]) != -1) {
154 | libmark = 249;
155 | break;
156 | }
157 | if (Datastr.indexOf(GENERIC_LINK_LIB_1[i]) != -1) {
158 | libmark = 248;
159 | break;
160 | }
161 | if (Datastr.indexOf(GENERIC_LINK_LIB_2[i]) != -1) {
162 | libmark = 247;
163 | break;
164 | }
165 | if (Datastr.indexOf(GENERIC_LINK_LIB_3[i]) != -1) {
166 | libmark = 246;
167 | break;
168 | }
169 | if (Datastr.indexOf(GENERIC_LINK_LIB_4[i]) != -1) {
170 | libmark = 243;
171 | break;
172 | }
173 | }
174 | }
175 |
176 | let Outlen;
177 | switch (libmark) {
178 | case 255:
179 | Outlen = Unishox.unishox2_compress_simple(
180 | Data,
181 | Data.byteLength,
182 | CompressedStrCharArray
183 | );
184 | break;
185 | case 254:
186 | Outlen = Unishox.unishox2_compress_simple(
187 | Data,
188 | Data.byteLength,
189 | CompressedStrCharArray,
190 | CHINESE_WEBPAN_LIB
191 | );
192 | break;
193 | case 245:
194 | Outlen = Unishox.unishox2_compress_simple(
195 | Data,
196 | Data.byteLength,
197 | CompressedStrCharArray,
198 | INTER_WEBPAN_LIB
199 | );
200 | break;
201 | case 253:
202 | Outlen = Unishox.unishox2_compress_simple(
203 | Data,
204 | Data.byteLength,
205 | CompressedStrCharArray,
206 | CHINESE_WEBSITE_LIB
207 | );
208 | break;
209 | case 252:
210 | Outlen = Unishox.unishox2_compress_simple(
211 | Data,
212 | Data.byteLength,
213 | CompressedStrCharArray,
214 | INTER_WEBSITE_LIB
215 | );
216 | break;
217 | case 244:
218 | Outlen = Unishox.unishox2_compress_simple(
219 | Data,
220 | Data.byteLength,
221 | CompressedStrCharArray,
222 | INTER_WEBSITE_LIB_2
223 | );
224 | break;
225 | case 251:
226 | Outlen = Unishox.unishox2_compress_simple(
227 | Data,
228 | Data.byteLength,
229 | CompressedStrCharArray,
230 | JAPAN_WEBSITE_LIB
231 | );
232 | break;
233 | case 250:
234 | Outlen = Unishox.unishox2_compress_simple(
235 | Data,
236 | Data.byteLength,
237 | CompressedStrCharArray,
238 | PIRACY_WEBSITE_LIB
239 | );
240 | break;
241 | case 249:
242 | Outlen = Unishox.unishox2_compress_simple(
243 | Data,
244 | Data.byteLength,
245 | CompressedStrCharArray,
246 | GENERIC_TLINK_LIB
247 | );
248 | break;
249 | case 248:
250 | Outlen = Unishox.unishox2_compress_simple(
251 | Data,
252 | Data.byteLength,
253 | CompressedStrCharArray,
254 | GENERIC_LINK_LIB_1
255 | );
256 | break;
257 | case 247:
258 | Outlen = Unishox.unishox2_compress_simple(
259 | Data,
260 | Data.byteLength,
261 | CompressedStrCharArray,
262 | GENERIC_LINK_LIB_2
263 | );
264 | break;
265 | case 246:
266 | Outlen = Unishox.unishox2_compress_simple(
267 | Data,
268 | Data.byteLength,
269 | CompressedStrCharArray,
270 | GENERIC_LINK_LIB_3
271 | );
272 | break;
273 | case 243:
274 | Outlen = Unishox.unishox2_compress_simple(
275 | Data,
276 | Data.byteLength,
277 | CompressedStrCharArray,
278 | GENERIC_LINK_LIB_4
279 | );
280 | break;
281 | }
282 |
283 | let ResStrCharArray = CompressedStrCharArray.subarray(0, Outlen);
284 | if (ResStrCharArray.byteLength >= Data.byteLength) {
285 | return Data;
286 | }
287 |
288 | let TempArray = new Uint8Array(ResStrCharArray.byteLength + 2);
289 | TempArray.set(ResStrCharArray, 0);
290 | TempArray.set([libmark, 255], ResStrCharArray.byteLength);
291 | ResStrCharArray = TempArray;
292 |
293 | return ResStrCharArray;
294 | }
295 | function UNISHOX_DECOMPRESS(Data) {
296 | const lastElement = Data[Data.byteLength - 1];
297 | const secondLastElement = Data[Data.byteLength - 2];
298 |
299 | if (
300 | lastElement != 255 ||
301 | secondLastElement < 243 ||
302 | secondLastElement > 255
303 | ) {
304 | return Data;
305 | }
306 | let libmark = secondLastElement;
307 | let NewData = Data.subarray(0, Data.byteLength - 2);
308 |
309 | let DecompressedStrCharArray = new Uint8Array(2048);
310 |
311 | let Outlen;
312 | switch (libmark) {
313 | case 255:
314 | Outlen = Unishox.unishox2_decompress(
315 | NewData,
316 | NewData.byteLength,
317 | DecompressedStrCharArray,
318 | Unishox.USX_HCODES_DFLT,
319 | Unishox.USX_HCODE_LENS_DFLT,
320 | Unishox.USX_FREQ_SEQ_DFLT,
321 | Unishox.USX_TEMPLATES
322 | );
323 | break;
324 | case 254:
325 | Outlen = Unishox.unishox2_decompress(
326 | NewData,
327 | NewData.byteLength,
328 | DecompressedStrCharArray,
329 | Unishox.USX_HCODES_DFLT,
330 | Unishox.USX_HCODE_LENS_DFLT,
331 | CHINESE_WEBPAN_LIB,
332 | Unishox.USX_TEMPLATES
333 | );
334 | break;
335 | case 245:
336 | Outlen = Unishox.unishox2_decompress(
337 | NewData,
338 | NewData.byteLength,
339 | DecompressedStrCharArray,
340 | Unishox.USX_HCODES_DFLT,
341 | Unishox.USX_HCODE_LENS_DFLT,
342 | INTER_WEBPAN_LIB,
343 | Unishox.USX_TEMPLATES
344 | );
345 | break;
346 | case 253:
347 | Outlen = Unishox.unishox2_decompress(
348 | NewData,
349 | NewData.byteLength,
350 | DecompressedStrCharArray,
351 | Unishox.USX_HCODES_DFLT,
352 | Unishox.USX_HCODE_LENS_DFLT,
353 | CHINESE_WEBSITE_LIB,
354 | Unishox.USX_TEMPLATES
355 | );
356 | break;
357 | case 252:
358 | Outlen = Unishox.unishox2_decompress(
359 | NewData,
360 | NewData.byteLength,
361 | DecompressedStrCharArray,
362 | Unishox.USX_HCODES_DFLT,
363 | Unishox.USX_HCODE_LENS_DFLT,
364 | INTER_WEBSITE_LIB,
365 | Unishox.USX_TEMPLATES
366 | );
367 | break;
368 | case 244:
369 | Outlen = Unishox.unishox2_decompress(
370 | NewData,
371 | NewData.byteLength,
372 | DecompressedStrCharArray,
373 | Unishox.USX_HCODES_DFLT,
374 | Unishox.USX_HCODE_LENS_DFLT,
375 | INTER_WEBSITE_LIB_2,
376 | Unishox.USX_TEMPLATES
377 | );
378 | break;
379 | case 251:
380 | Outlen = Unishox.unishox2_decompress(
381 | NewData,
382 | NewData.byteLength,
383 | DecompressedStrCharArray,
384 | Unishox.USX_HCODES_DFLT,
385 | Unishox.USX_HCODE_LENS_DFLT,
386 | JAPAN_WEBSITE_LIB,
387 | Unishox.USX_TEMPLATES
388 | );
389 | break;
390 | case 250:
391 | Outlen = Unishox.unishox2_decompress(
392 | NewData,
393 | NewData.byteLength,
394 | DecompressedStrCharArray,
395 | Unishox.USX_HCODES_DFLT,
396 | Unishox.USX_HCODE_LENS_DFLT,
397 | PIRACY_WEBSITE_LIB,
398 | Unishox.USX_TEMPLATES
399 | );
400 | break;
401 | case 249:
402 | Outlen = Unishox.unishox2_decompress(
403 | NewData,
404 | NewData.byteLength,
405 | DecompressedStrCharArray,
406 | Unishox.USX_HCODES_DFLT,
407 | Unishox.USX_HCODE_LENS_DFLT,
408 | GENERIC_TLINK_LIB,
409 | Unishox.USX_TEMPLATES
410 | );
411 | break;
412 | case 248:
413 | Outlen = Unishox.unishox2_decompress(
414 | NewData,
415 | NewData.byteLength,
416 | DecompressedStrCharArray,
417 | Unishox.USX_HCODES_DFLT,
418 | Unishox.USX_HCODE_LENS_DFLT,
419 | GENERIC_LINK_LIB_1,
420 | Unishox.USX_TEMPLATES
421 | );
422 | break;
423 | case 247:
424 | Outlen = Unishox.unishox2_decompress(
425 | NewData,
426 | NewData.byteLength,
427 | DecompressedStrCharArray,
428 | Unishox.USX_HCODES_DFLT,
429 | Unishox.USX_HCODE_LENS_DFLT,
430 | GENERIC_LINK_LIB_2,
431 | Unishox.USX_TEMPLATES
432 | );
433 | break;
434 | case 246:
435 | Outlen = Unishox.unishox2_decompress(
436 | NewData,
437 | NewData.byteLength,
438 | DecompressedStrCharArray,
439 | Unishox.USX_HCODES_DFLT,
440 | Unishox.USX_HCODE_LENS_DFLT,
441 | GENERIC_LINK_LIB_3,
442 | Unishox.USX_TEMPLATES
443 | );
444 | break;
445 | case 243:
446 | Outlen = Unishox.unishox2_decompress(
447 | NewData,
448 | NewData.byteLength,
449 | DecompressedStrCharArray,
450 | Unishox.USX_HCODES_DFLT,
451 | Unishox.USX_HCODE_LENS_DFLT,
452 | GENERIC_LINK_LIB_4,
453 | Unishox.USX_TEMPLATES
454 | );
455 | break;
456 | }
457 | let ResStrCharArray = DecompressedStrCharArray.subarray(0, Outlen);
458 | return ResStrCharArray;
459 | }
460 |
461 | export function Compress(OriginalData) {
462 | if (OriginalData.byteLength <= 1024) {
463 | //压缩数据
464 | let SizeBefore = OriginalData.byteLength;
465 | OriginalData = UNISHOX_COMPRESS(OriginalData);
466 |
467 | if (OriginalData.byteLength == SizeBefore) {
468 | OriginalData = GZIP_COMPRESS(OriginalData);
469 | }
470 | } else {
471 | OriginalData = GZIP_COMPRESS(OriginalData);
472 | }
473 | return OriginalData;
474 | }
475 |
476 | export function Decompress(OriginalData) {
477 | OriginalData = GZIP_DECOMPRESS(OriginalData);
478 | OriginalData = UNISHOX_DECOMPRESS(OriginalData);
479 |
480 | return OriginalData;
481 | }
482 |
--------------------------------------------------------------------------------
/src/javascript/CoreHandler.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 | import { Base64 } from "js-base64";
13 | import { OldMapper, WenyanSimulator } from "./ChineseMappingHelper";
14 | import { Compress, Decompress } from "./CompressionHelper.js";
15 | import { Decrypt, Encrypt } from "./EncryptHelper";
16 |
17 | import {
18 | Uint8ArrayTostring,
19 | GetLuhnBit,
20 | RemovePadding,
21 | CheckLuhnBit,
22 | } from "./Misc.js";
23 |
24 | export class WenyanConfig {
25 | /**
26 | * 魔曰 文言文加密参数
27 | *
28 | * @param{bool}PunctuationMark 指定是否为密文添加标点符号,默认 true/添加;
29 | * @param{int}RandomIndex 密文算法的随机程度,越大随机性越强,默认 50,最大100,超过100将会出错;
30 | * @param{bool}PianwenMode 指定是否强制生成骈文密文,默认 false;
31 | * @param{bool}LogicMode 指定是否强制生成逻辑密文,默认 false;
32 | */
33 | constructor(
34 | PunctuationMark = true,
35 | RandomIndex = 50,
36 | PianwenMode = false,
37 | LogicMode = false,
38 | Traditional = false
39 | ) {
40 | this.PunctuationMark = PunctuationMark;
41 | this.RandomIndex = RandomIndex;
42 | this.PianwenMode = PianwenMode;
43 | this.LogicMode = LogicMode;
44 | this.Traditional = Traditional;
45 | }
46 | }
47 |
48 | export class CallbackObj {
49 | /**
50 | * 魔曰 Debug 回调位点对象
51 | *
52 | * ENC/DEC_BASE64 (Base64字符串)
53 | *
54 | * ROUNDS (转轮状态)
55 | *
56 | * ENC_MAPTEMP (映射过程变量)
57 | *
58 | * ENC_SENTENCES (组句步骤变量)
59 | *
60 | * ENC_PAYLOADS (加密的载荷分配数组)
61 | *
62 | * DEC_PAYLOAD (解密提取的有效载荷)
63 | *
64 | *
65 | * @param{string}Type 指定回调参数的Tag
66 | * @param{string}Value 回调参数的值
67 | */
68 | constructor(Type = "NORMAL", Value = null) {
69 | this.Type = Type;
70 | this.Value = Value;
71 | }
72 | }
73 |
74 | /**
75 | * @param{WenyanConfig}WenyanConfigObj 文言文的生成配置;
76 | */
77 | export function Enc(input, key, WenyanConfigObj, callback = null) {
78 | //初始化
79 | //input.output Uint8Array
80 | let WenyanSimulatorObj = new WenyanSimulator(key, callback);
81 |
82 | let OriginalData = new Uint8Array();
83 | OriginalData = input.output;
84 | let TempS;
85 | TempS = Uint8ArrayTostring(OriginalData);
86 |
87 | let TempArray = new Uint8Array(OriginalData.byteLength + 1);
88 | TempArray.set(OriginalData, 0);
89 |
90 | //对未处理的数据计算校验和,放在末尾
91 | TempArray.set([GetLuhnBit(OriginalData)], OriginalData.byteLength);
92 |
93 | //压缩
94 | OriginalData = Compress(TempArray);
95 | //加密
96 | OriginalData = Encrypt(OriginalData, key);
97 |
98 | let OriginStr = RemovePadding(Base64.fromUint8Array(OriginalData));
99 | try {
100 | if (callback != null) callback(new CallbackObj("ENC_BASE64", OriginStr));
101 | } catch (err) {}
102 | //映射
103 | let Res = WenyanSimulatorObj.enMap(
104 | OriginStr,
105 | WenyanConfigObj.PunctuationMark !== undefined
106 | ? WenyanConfigObj.PunctuationMark
107 | : true,
108 | WenyanConfigObj.RandomIndex !== undefined
109 | ? WenyanConfigObj.RandomIndex
110 | : 50,
111 | WenyanConfigObj.PianwenMode !== undefined
112 | ? WenyanConfigObj.PianwenMode
113 | : false,
114 | WenyanConfigObj.LogicMode !== undefined ? WenyanConfigObj.LogicMode : false,
115 | WenyanConfigObj.Traditional !== undefined
116 | ? WenyanConfigObj.Traditional
117 | : false
118 | );
119 |
120 | return Res;
121 | }
122 |
123 | export function Dec(input, key, callback) {
124 | //初始化
125 | //input.output Uint8Array
126 | let WenyanSimulatorObj = new WenyanSimulator(key, callback);
127 | let OriginStr = Uint8ArrayTostring(input.output);
128 |
129 | //解映射
130 | let TempStr1 = WenyanSimulatorObj.deMap(OriginStr);
131 |
132 | let TempStr2Int = new Uint8Array();
133 | if (!Base64.isValid(TempStr1)) {
134 | /* v8 ignore next 3 */
135 | //检查Base64是否合法,如果不合法,那么就没有必要继续处理下去
136 | throw "Error Decoding. Bad Input or Incorrect Key.";
137 | }
138 | try {
139 | //取到两个字节的IV,然后对AES加密后的数据执行解密。
140 | TempStr2Int = Base64.toUint8Array(TempStr1);
141 |
142 | //解密
143 | TempStr2Int = Decrypt(TempStr2Int, key);
144 |
145 | //解压缩
146 | TempStr2Int = Decompress(TempStr2Int);
147 | } catch (err) {
148 | /* v8 ignore next 3 */
149 | //解压缩/解密失败,丢出错误。
150 | throw "Error Decoding. Bad Input or Incorrect Key.";
151 | }
152 |
153 | if (!CheckLuhnBit(TempStr2Int)) {
154 | /* v8 ignore next 3 */
155 | //检查密文的校验位是否匹配
156 | //校验不通过,则丢出错误。
157 | throw "Error Decrypting. Checksum Mismatch.";
158 | } else {
159 | //校验通过,则移除校验位。
160 | TempStr2Int = TempStr2Int.subarray(0, TempStr2Int.byteLength - 1);
161 | }
162 |
163 | //到此,TempStr2Int 就是解密的结果,也就是原始数据(UINT8Array)。
164 | let Res = new Object();
165 |
166 | //组装一个对象,同时返回两种类型的解密结果。
167 | Res.output = Uint8ArrayTostring(TempStr2Int);
168 | Res.output_B = TempStr2Int;
169 |
170 | return Res;
171 | }
172 |
173 | export function Enc_OLD(input, key, q) {
174 | //初始化
175 | let OldMapperObj = new OldMapper(key);
176 |
177 | let OriginalData = new Uint8Array();
178 | OriginalData = input.output;
179 |
180 | let TempArray = new Uint8Array(OriginalData.byteLength + 1);
181 | TempArray.set(OriginalData, 0);
182 |
183 | TempArray.set([GetLuhnBit(OriginalData)], OriginalData.byteLength);
184 | //压缩
185 | OriginalData = Compress(TempArray);
186 | //加密
187 | OriginalData = Encrypt(OriginalData, key);
188 |
189 | let OriginStr = RemovePadding(Base64.fromUint8Array(OriginalData));
190 | //映射
191 | let Res = OldMapperObj.enMap(OriginStr, q);
192 |
193 | return Res;
194 | }
195 |
196 | export function Dec_OLD(input, key) {
197 | //初始化
198 | let OldMapperObj = new OldMapper(key);
199 | let OriginStr = Uint8ArrayTostring(input.output);
200 |
201 | //解映射
202 | let TempStr1 = OldMapperObj.deMap(OriginStr);
203 |
204 | //还原出AES加密之后的Base64 TempStr1
205 |
206 | let TempStr2Int = new Uint8Array();
207 | let RandomBytes = new Array(2);
208 | if (!Base64.isValid(TempStr1)) {
209 | throw "Error Decoding. Bad Input or Incorrect Key.";
210 | }
211 | try {
212 | TempStr2Int = Base64.toUint8Array(TempStr1);
213 |
214 | //解密
215 | TempStr2Int = Decrypt(TempStr2Int, key);
216 |
217 | //解压缩
218 | TempStr2Int = Decompress(TempStr2Int);
219 | } catch (err) {
220 | throw "Error Decoding. Bad Input or Incorrect Key.";
221 | }
222 |
223 | //校验数据
224 | if (!CheckLuhnBit(TempStr2Int)) {
225 | /* v8 ignore next 9 */
226 | if (
227 | TempStr2Int.at(TempStr2Int.byteLength - 1) == 2 &&
228 | TempStr2Int.at(TempStr2Int.byteLength - 2) == 2 &&
229 | TempStr2Int.at(TempStr2Int.byteLength - 3) == 2
230 | ) {
231 | TempStr2Int = TempStr2Int.subarray(0, TempStr2Int.byteLength - 3);
232 | } else {
233 | throw "Error Decrypting. Checksum Mismatch.";
234 | }
235 | } else {
236 | TempStr2Int = TempStr2Int.subarray(0, TempStr2Int.byteLength - 1);
237 | }
238 |
239 | //到此,TempStr2Int 就是解密的结果,形式为字节码。
240 | let Res = new Object();
241 |
242 | Res.output = Uint8ArrayTostring(TempStr2Int);
243 | Res.output_B = TempStr2Int;
244 | return Res;
245 | }
246 |
--------------------------------------------------------------------------------
/src/javascript/EncryptHelper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 | import CryptoJS from "crypto-js";
13 | import {
14 | wordArrayToUint8Array,
15 | Uint8ArrayTostring,
16 | GetRandomIndex,
17 | } from "./Misc.js";
18 |
19 | function AES_256_CTR_E(Uint8attr, key, RandomBytes) {
20 | let KeyHash = CryptoJS.SHA256(key);
21 | let HashArray = wordArrayToUint8Array(KeyHash);
22 |
23 | let TempArray = new Uint8Array(HashArray.byteLength + 2);
24 | TempArray.set(HashArray, 0);
25 | TempArray.set([RandomBytes[0], RandomBytes[1]], HashArray.byteLength);
26 | HashArray = TempArray;
27 |
28 | let HashWithRandom = CryptoJS.lib.WordArray.create(HashArray);
29 | let KeyHashHash = CryptoJS.SHA256(HashWithRandom); //密钥两次哈希,附加两字节随机数
30 | let HashHashArray = wordArrayToUint8Array(KeyHashHash);
31 |
32 | let ivArray = new Uint8Array(16);
33 |
34 | for (var i = 0; i < 16; i++) {
35 | ivArray[i] = HashHashArray[i];
36 | }
37 |
38 | let iv = CryptoJS.lib.WordArray.create(ivArray);
39 | let msg = CryptoJS.lib.WordArray.create(Uint8attr);
40 |
41 | let Enc = CryptoJS.AES.encrypt(msg, KeyHash, {
42 | mode: CryptoJS.mode.CTR,
43 | padding: CryptoJS.pad.NoPadding,
44 | iv: iv,
45 | });
46 | return wordArrayToUint8Array(Enc.ciphertext);
47 | }
48 |
49 | //执行AES加密,返回UINT8数组
50 | export function Encrypt(OriginalData, key) {
51 | let RandomBytes = new Array(); //取两个随机数作为AES加密的IV
52 | RandomBytes.push(GetRandomIndex(256));
53 | RandomBytes.push(GetRandomIndex(256));
54 |
55 | OriginalData = AES_256_CTR_E(OriginalData, key, RandomBytes); //AES加密
56 |
57 | let TempArray = new Uint8Array(OriginalData.byteLength + 2);
58 | TempArray.set(OriginalData, 0);
59 | TempArray.set(RandomBytes, OriginalData.byteLength); //把IV附加在加密后数据的末尾,解密时提取
60 | OriginalData = TempArray;
61 | return OriginalData;
62 | }
63 |
64 | export function Decrypt(Data, key) {
65 | //Data = Base64.toUint8Array(TempStr1);
66 | let RandomBytes = [null, null];
67 | RandomBytes[1] = Data.at(Data.byteLength - 1);
68 | RandomBytes[0] = Data.at(Data.byteLength - 2);
69 |
70 | Data = Data.subarray(0, Data.byteLength - 2);
71 |
72 | //取到两个字节的IV,然后对AES加密后的数据执行解密。
73 |
74 | Data = AES_256_CTR_E(Data, key, RandomBytes);
75 |
76 | return Data;
77 | }
78 |
--------------------------------------------------------------------------------
/src/javascript/Misc.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 | import { Base64 } from "js-base64";
13 | import MersenneTwister from "mersenne-twister";
14 |
15 | const SIG_DECRYPT_JP = "桜込凪雫実沢";
16 | const SIG_DECRYPT_CN = "玚俟玊欤瞐珏";
17 |
18 | const NULL_STR = "孎"; //默认忽略的占位字符,一个生僻字。
19 |
20 | let array = new Uint32Array(1);
21 | let seed = 0;
22 |
23 | try {
24 | window.crypto.getRandomValues(array);
25 | seed = array[0];
26 | } catch (err) {
27 | seed = Date.now();
28 | }
29 |
30 | var MT = new MersenneTwister(seed);
31 | //获取密码学安全随机数,如果不支持WebCrypto API,回落到日期和时间。
32 |
33 | export class PreCheckResult {
34 | constructor(output, isEncrypted = false) {
35 | this.output = output;
36 | this.isEncrypted = isEncrypted;
37 | }
38 | }
39 |
40 | export function RemovePadding(Base64String) {
41 | let PaddingCount = 0;
42 | for (let i = Base64String.length - 1; i >= Base64String.length - 4; i--) {
43 | if (Base64String[i] == "=") {
44 | PaddingCount++;
45 | }
46 | }
47 | return Base64String.slice(0, Base64String.length - PaddingCount);
48 | }
49 |
50 | export function AddPadding(Base64String) {
51 | if (Base64String.length % 4 == 3) {
52 | return Base64String + "=";
53 | } else if (Base64String.length % 4 == 2) {
54 | return Base64String + "==";
55 | } else {
56 | return Base64String;
57 | }
58 | }
59 |
60 | export function setCharOnIndex(string, index, char) {
61 | if (index > string.length - 1) return string;
62 | return string.substring(0, index) + char + string.substring(index + 1);
63 | }
64 |
65 | export function stringToUint8Array(str) {
66 | let tempBase64 = Base64.encode(str);
67 | return Base64.toUint8Array(tempBase64);
68 | }
69 |
70 | // 将WordArray转换为Uint8Array
71 | export function wordArrayToUint8Array(data) {
72 | const dataArray = new Uint8Array(data.sigBytes);
73 | for (let i = 0x0; i < data.sigBytes; i++) {
74 | dataArray[i] = (data.words[i >>> 0x2] >>> (0x18 - (i % 0x4) * 0x8)) & 0xff;
75 | }
76 | return dataArray;
77 | }
78 |
79 | export function Uint8ArrayTostring(fileData) {
80 | let tempBase64 = Base64.fromUint8Array(fileData);
81 | return Base64.decode(tempBase64);
82 | }
83 |
84 | export function GetRandomIndex(length) {
85 | // 取随机数
86 | let Rand = Math.floor(MT.random() * length);
87 |
88 | return Rand;
89 | }
90 |
91 | export function difference(arr1, arr2) {
92 | return arr1.filter((item) => !arr2.includes(item));
93 | }
94 |
95 | export function insertStringAtIndex(str, value, index) {
96 | // 分割字符串为两部分,并在中间插入新值
97 | return str.slice(0, index) + value + str.slice(index);
98 | }
99 |
100 | export function GetLuhnBit(Data) {
101 | let Digit = new Array();
102 | let num, digit;
103 | for (let i = 0; i < Data.byteLength; i++) {
104 | num = Data[i];
105 | while (num > 0) {
106 | digit = num % 10;
107 | Digit.push(digit);
108 | num = Math.floor(num / 10);
109 | }
110 | }
111 |
112 | // Digit应当是一个数位构成的数组。
113 | let sum = 0;
114 | let Check = 0;
115 |
116 | for (let i = 0; i < Digit.length; i++) {
117 | if (i % 2 != 0) {
118 | Digit[i] = Digit[i] * 2;
119 | if (Digit[i] >= 10) {
120 | Digit[i] = (Digit[i] % 10) + Math.floor(Digit[i] / 10); //计算数字之和
121 | }
122 | }
123 | sum = sum + Digit[i];
124 | }
125 |
126 | Check = 10 - (sum % 10);
127 |
128 | return Check;
129 | }
130 |
131 | export function CheckLuhnBit(Data) {
132 | let DCheck = Data[Data.byteLength - 1];
133 | let Check = GetLuhnBit(Data.subarray(0, Data.byteLength - 1));
134 |
135 | if (Check == DCheck) {
136 | return true;
137 | } else {
138 | return false;
139 | }
140 | }
141 |
142 | export function shuffle(array) {
143 | for (let i = array.length - 1; i > 0; i--) {
144 | const j = Math.floor(Math.random() * (i + 1));
145 | [array[i], array[j]] = [array[j], array[i]];
146 | }
147 | return array;
148 | }
149 |
150 | export function preCheck_OLD(inp) {
151 | let input = String(inp);
152 | let size = input.length; //第一次遍历字符数组的函数,负责判断给定的输入类型。
153 | let temp, temp2, group;
154 | let isEncrypted = false; //判定该文本是否为加密文本
155 |
156 | let isJPFound = false; //如果检查出一个日语标志位,则标记为真
157 | let isCNFound = false; //如果检查出一个汉字标志位,则标记为真
158 | for (let i = 0; i < size; i++) {
159 | temp = input[i];
160 |
161 | if (i != size - 1) {
162 | //一次遍历两个字符,遇到倒数第一个的时候防止越界
163 | temp2 = input[i + 1];
164 | } else {
165 | temp2 = NULL_STR;
166 | }
167 | group = temp + temp2;
168 |
169 | //判断这个符号是不是标识符,标识符用空字符进行占位操作
170 | if (SIG_DECRYPT_JP.indexOf(temp) != -1) {
171 | input = setCharOnIndex(input, i, NULL_STR);
172 | isJPFound = true;
173 | continue;
174 | }
175 | if (SIG_DECRYPT_CN.indexOf(temp) != -1) {
176 | input = setCharOnIndex(input, i, NULL_STR);
177 | isCNFound = true;
178 | continue;
179 | }
180 | }
181 |
182 | if (isJPFound && isCNFound) {
183 | isEncrypted = true;
184 | }
185 | let Result = new PreCheckResult(stringToUint8Array(input), isEncrypted);
186 | return Result;
187 | }
188 |
--------------------------------------------------------------------------------
/src/javascript/RoundObfusHelper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | import CryptoJS from "crypto-js";
14 | import { wordArrayToUint8Array } from "./Misc.js";
15 | import { CallbackObj } from "./CoreHandler.js";
16 |
17 | export class RoundObfus {
18 | constructor(key, callback = null) {
19 | this.RoundFlip = 0; //标志现在到哪了
20 | this.RoundControl = new Uint8Array(32); //一个数组,用密钥哈希来控制轮转的行为
21 | this.LETTERS_ROUND_1 =
22 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
23 | this.LETTERS_ROUND_2 =
24 | "FbPoDRStyJKAUcdahfVXlqwnOGpHZejzvmrBCigQILxkYMuWTEsN"; //手动随机打乱的乱序轮
25 | this.LETTERS_ROUND_3 =
26 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
27 | this.NUMBERSYMBOL_ROUND_1 = "1234567890+/=";
28 | this.NUMBERSYMBOL_ROUND_2 = "5=0764+389/12"; //手动随机打乱的乱序轮
29 | this.NUMBERSYMBOL_ROUND_3 = "1234567890+/=";
30 |
31 | this.Normal_Characters =
32 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=1234567890"; //表内有映射的所有字符组成的字符串
33 | this.LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
34 |
35 | this.BIG_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
36 | this.NUMBERS = "1234567890";
37 | this.SYMBOLS = "+/=";
38 | this.NUMBERSYMBOL = "0123456789+/=";
39 |
40 | this.NULL_STR = "孎"; //默认忽略的占位字符,一个生僻字。
41 |
42 | this.callback = callback;
43 |
44 | //初始化转轮操作的数组
45 | let KeyHash = CryptoJS.SHA256(key);
46 | let HashArray = wordArrayToUint8Array(KeyHash);
47 |
48 | this.RoundControl = HashArray;
49 | }
50 |
51 | _rotateString(str, n) {
52 | // 向右轮转指定位数
53 | return str.slice(n) + str.slice(0, n);
54 | }
55 |
56 | _LrotateString(str, n) {
57 | // 向左轮转指定位数
58 | return str.slice(str.length - n) + str.slice(0, str.length - n);
59 | }
60 | RoundKeyMatch(keyIn) {
61 | //查询轮换密钥的键值
62 | let idx1, idx2;
63 | let idx1_1, idx2_1;
64 | let idx1_2, idx2_2;
65 |
66 | idx1 = this.LETTERS.indexOf(keyIn);
67 | idx2 = this.NUMBERSYMBOL.indexOf(keyIn);
68 |
69 | idx1_1 = this.LETTERS.indexOf(this.LETTERS_ROUND_1[idx1]);
70 | idx2_1 = this.NUMBERSYMBOL.indexOf(this.NUMBERSYMBOL_ROUND_1[idx2]);
71 |
72 | idx1_2 = this.LETTERS.indexOf(this.LETTERS_ROUND_2[idx1_1]);
73 | idx2_2 = this.NUMBERSYMBOL.indexOf(this.NUMBERSYMBOL_ROUND_2[idx2_1]);
74 |
75 | if (idx1 != -1) {
76 | //判断给定字符的类型
77 | return this.LETTERS_ROUND_3[idx1_2];
78 | } else if (idx2 != -1) {
79 | return this.NUMBERSYMBOL_ROUND_3[idx2_2];
80 | }
81 | return this.NULL_STR;
82 | }
83 |
84 | DRoundKeyMatch(keyIn) {
85 | //查询轮换密钥的键值
86 | let idx1, idx2;
87 | let idx1_1, idx2_1;
88 | let idx1_2, idx2_2;
89 |
90 | idx1 = this.LETTERS_ROUND_3.indexOf(keyIn);
91 | idx2 = this.NUMBERSYMBOL_ROUND_3.indexOf(keyIn);
92 |
93 | idx1_1 = this.LETTERS_ROUND_2.indexOf(this.LETTERS[idx1]);
94 | idx2_1 = this.NUMBERSYMBOL_ROUND_2.indexOf(this.NUMBERSYMBOL[idx2]);
95 |
96 | idx1_2 = this.LETTERS_ROUND_1.indexOf(this.LETTERS[idx1_1]);
97 | idx2_2 = this.NUMBERSYMBOL_ROUND_1.indexOf(this.NUMBERSYMBOL[idx2_1]);
98 |
99 | if (idx1 != -1) {
100 | //判断给定字符的类型
101 | return this.LETTERS[idx1_2];
102 | } else if (idx2 != -1) {
103 | return this.NUMBERSYMBOL[idx2_2];
104 | }
105 | return this.NULL_STR;
106 | }
107 |
108 | RoundKey() {
109 | let ControlNum = 0;
110 | if (this.RoundFlip == 32) {
111 | this.RoundFlip = 0;
112 | }
113 | ControlNum = this.RoundControl[this.RoundFlip] % 10; //哈希字节对十取余即操作数
114 | if (ControlNum == 0) {
115 | //等于零就赋值为10
116 | ControlNum = 10;
117 | }
118 |
119 | if (ControlNum % 2 == 0) {
120 | //操作数是偶数
121 | this.LETTERS_ROUND_1 = this._rotateString(this.LETTERS_ROUND_1, 6); //将第一个密钥轮向右轮6位
122 | this.NUMBERSYMBOL_ROUND_1 = this._rotateString(
123 | this.NUMBERSYMBOL_ROUND_1,
124 | 6
125 | );
126 |
127 | this.LETTERS_ROUND_2 = this._LrotateString(
128 | this.LETTERS_ROUND_2,
129 | ControlNum
130 | ); //将第二个密钥轮向左轮ControlNum*2位
131 | this.NUMBERSYMBOL_ROUND_2 = this._LrotateString(
132 | this.NUMBERSYMBOL_ROUND_2,
133 | ControlNum
134 | );
135 |
136 | this.LETTERS_ROUND_3 = this._rotateString(
137 | this.LETTERS_ROUND_3,
138 | ControlNum / 2 + 1
139 | ); //将第三个密钥轮向右轮ControlNum/2+1位
140 | this.NUMBERSYMBOL_ROUND_3 = this._rotateString(
141 | this.NUMBERSYMBOL_ROUND_3,
142 | ControlNum / 2 + 1
143 | );
144 | } else {
145 | //操作数是奇数
146 | this.LETTERS_ROUND_1 = this._LrotateString(this.LETTERS_ROUND_1, 3); //将第一个密钥轮向左轮3位
147 | this.NUMBERSYMBOL_ROUND_1 = this._LrotateString(
148 | this.NUMBERSYMBOL_ROUND_1,
149 | 3
150 | );
151 |
152 | this.LETTERS_ROUND_2 = this._rotateString(
153 | this.LETTERS_ROUND_2,
154 | ControlNum
155 | ); //将第二个密钥轮向右轮ControlNum位
156 | this.NUMBERSYMBOL_ROUND_2 = this._rotateString(
157 | this.NUMBERSYMBOL_ROUND_2,
158 | ControlNum
159 | );
160 |
161 | this.LETTERS_ROUND_3 = this._LrotateString(
162 | this.LETTERS_ROUND_3,
163 | (ControlNum + 7) / 2
164 | ); //将第三个密钥轮向左轮(ControlNum+5)/2位
165 | this.NUMBERSYMBOL_ROUND_3 = this._LrotateString(
166 | this.NUMBERSYMBOL_ROUND_3,
167 | (ControlNum + 7) / 2
168 | );
169 | }
170 | this.RoundFlip++;
171 | try {
172 | if (this.callback != null)
173 | this.callback(
174 | new CallbackObj("ROUNDS", [
175 | this.LETTERS_ROUND_1,
176 | this.LETTERS_ROUND_2,
177 | this.LETTERS_ROUND_3,
178 | this.NUMBERSYMBOL_ROUND_1,
179 | this.NUMBERSYMBOL_ROUND_2,
180 | this.NUMBERSYMBOL_ROUND_3,
181 | ])
182 | );
183 | } catch (err) {}
184 | }
185 | }
186 |
187 | export class RoundObfusOLD {
188 | constructor(key) {
189 | this.RoundFlip = 0; //标志现在到哪了
190 | this.RoundControl = new Uint8Array(32); //一个数组,用密钥哈希来控制轮转的行为
191 | this.LETTERS_ROUND_1 =
192 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
193 | this.LETTERS_ROUND_2 =
194 | "FbPoDRStyJKAUcdahfVXlqwnOGpHZejzvmrBCigQILxkYMuWTEsN"; //手动随机打乱的乱序轮
195 | this.LETTERS_ROUND_3 =
196 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
197 | this.NUMBERSYMBOL_ROUND_1 = "1234567890+=_-/?.>,<|`~!@#$%^&*(){}[];:";
198 | this.NUMBERSYMBOL_ROUND_2 = "~3{8}_-$[6(2^|1*%0,<9:`+@7/?.>4=];!)"; //手动随机打乱的乱序轮
199 | this.NUMBERSYMBOL_ROUND_3 = "1234567890+=_-/?.>,<|`~!@#$%^&*(){}[];:";
200 |
201 | this.Normal_Characters =
202 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+=_-/?.>,<|`~!@#$%^&*(){}[];:1234567890"; //表内有映射的所有字符组成的字符串
203 | this.LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
204 |
205 | this.BIG_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
206 | this.NUMBERS = "1234567890";
207 | this.SYMBOLS = "+=_-/?.>,<|`~!@#$%^&*(){}[];:";
208 | this.NUMBERSYMBOL = "1234567890+=_-/?.>,<|`~!@#$%^&*(){}[];:";
209 |
210 | this.NULL_STR = "孎"; //默认忽略的占位字符,一个生僻字。
211 | //初始化转轮操作的数组
212 | let KeyHash = CryptoJS.SHA256(key);
213 | let HashArray = wordArrayToUint8Array(KeyHash);
214 |
215 | this.RoundControl = HashArray;
216 | }
217 |
218 | _rotateString(str, n) {
219 | // 向右轮转指定位数
220 | return str.slice(n) + str.slice(0, n);
221 | }
222 |
223 | _LrotateString(str, n) {
224 | // 向左轮转指定位数
225 | return str.slice(str.length - n) + str.slice(0, str.length - n);
226 | }
227 | RoundKeyMatch(keyIn) {
228 | //查询轮换密钥的键值
229 | let idx1, idx2;
230 | let idx1_1, idx2_1;
231 | let idx1_2, idx2_2;
232 |
233 | idx1 = this.LETTERS.indexOf(keyIn);
234 | idx2 = this.NUMBERSYMBOL.indexOf(keyIn);
235 |
236 | idx1_1 = this.LETTERS.indexOf(this.LETTERS_ROUND_1[idx1]);
237 | idx2_1 = this.NUMBERSYMBOL.indexOf(this.NUMBERSYMBOL_ROUND_1[idx2]);
238 |
239 | idx1_2 = this.LETTERS.indexOf(this.LETTERS_ROUND_2[idx1_1]);
240 | idx2_2 = this.NUMBERSYMBOL.indexOf(this.NUMBERSYMBOL_ROUND_2[idx2_1]);
241 |
242 | if (idx1 != -1) {
243 | //判断给定字符的类型
244 | return this.LETTERS_ROUND_3[idx1_2];
245 | } else if (idx2 != -1) {
246 | return this.NUMBERSYMBOL_ROUND_3[idx2_2];
247 | }
248 | return this.NULL_STR;
249 | }
250 |
251 | DRoundKeyMatch(keyIn) {
252 | //查询轮换密钥的键值
253 | let idx1, idx2;
254 | let idx1_1, idx2_1;
255 | let idx1_2, idx2_2;
256 |
257 | idx1 = this.LETTERS_ROUND_3.indexOf(keyIn);
258 | idx2 = this.NUMBERSYMBOL_ROUND_3.indexOf(keyIn);
259 |
260 | idx1_1 = this.LETTERS_ROUND_2.indexOf(this.LETTERS[idx1]);
261 | idx2_1 = this.NUMBERSYMBOL_ROUND_2.indexOf(this.NUMBERSYMBOL[idx2]);
262 |
263 | idx1_2 = this.LETTERS_ROUND_1.indexOf(this.LETTERS[idx1_1]);
264 | idx2_2 = this.NUMBERSYMBOL_ROUND_1.indexOf(this.NUMBERSYMBOL[idx2_1]);
265 |
266 | if (idx1 != -1) {
267 | //判断给定字符的类型
268 | return this.LETTERS[idx1_2];
269 | } else if (idx2 != -1) {
270 | return this.NUMBERSYMBOL[idx2_2];
271 | }
272 | return this.NULL_STR;
273 | }
274 |
275 | RoundKey() {
276 | let ControlNum = 0;
277 | if (this.RoundFlip == 32) {
278 | this.RoundFlip = 0;
279 | }
280 | ControlNum = this.RoundControl[this.RoundFlip] % 10; //哈希字节对十取余即操作数
281 | if (ControlNum == 0) {
282 | //等于零就赋值为10
283 | ControlNum = 10;
284 | }
285 |
286 | if (ControlNum % 2 == 0) {
287 | //操作数是偶数
288 | this.LETTERS_ROUND_1 = this._rotateString(this.LETTERS_ROUND_1, 6); //将第一个密钥轮向右轮6位
289 | this.NUMBERSYMBOL_ROUND_1 = this._rotateString(
290 | this.NUMBERSYMBOL_ROUND_1,
291 | 6
292 | );
293 |
294 | this.LETTERS_ROUND_2 = this._LrotateString(
295 | this.LETTERS_ROUND_2,
296 | ControlNum * 2
297 | ); //将第二个密钥轮向左轮ControlNum*2位
298 | this.NUMBERSYMBOL_ROUND_2 = this._LrotateString(
299 | this.NUMBERSYMBOL_ROUND_2,
300 | ControlNum * 2
301 | );
302 |
303 | this.LETTERS_ROUND_3 = this._rotateString(
304 | this.LETTERS_ROUND_3,
305 | ControlNum / 2 + 1
306 | ); //将第三个密钥轮向右轮ControlNum/2+1位
307 | this.NUMBERSYMBOL_ROUND_3 = this._rotateString(
308 | this.NUMBERSYMBOL_ROUND_3,
309 | ControlNum / 2 + 1
310 | );
311 | } else {
312 | //操作数是奇数
313 | this.LETTERS_ROUND_1 = this._LrotateString(this.LETTERS_ROUND_1, 3); //将第一个密钥轮向左轮3位
314 | this.NUMBERSYMBOL_ROUND_1 = this._LrotateString(
315 | this.NUMBERSYMBOL_ROUND_1,
316 | 3
317 | );
318 |
319 | this.LETTERS_ROUND_2 = this._rotateString(
320 | this.LETTERS_ROUND_2,
321 | ControlNum
322 | ); //将第二个密钥轮向右轮ControlNum位
323 | this.NUMBERSYMBOL_ROUND_2 = this._rotateString(
324 | this.NUMBERSYMBOL_ROUND_2,
325 | ControlNum
326 | );
327 |
328 | this.LETTERS_ROUND_3 = this._LrotateString(
329 | this.LETTERS_ROUND_3,
330 | (ControlNum + 7) / 2
331 | ); //将第三个密钥轮向左轮(ControlNum+5)/2位
332 | this.NUMBERSYMBOL_ROUND_3 = this._LrotateString(
333 | this.NUMBERSYMBOL_ROUND_3,
334 | (ControlNum + 7) / 2
335 | );
336 | }
337 | this.RoundFlip++;
338 | }
339 | }
340 |
--------------------------------------------------------------------------------
/src/javascript/main.d.ts:
--------------------------------------------------------------------------------
1 | declare module "abracadabra-cn";
2 |
3 | export interface WenyanConfig {
4 | /** 指定是否为密文添加标点符号,默认 true/添加; */
5 | PunctuationMark?: boolean;
6 | /** 密文算法的随机程度,越大随机性越强,默认 50,最大100,超过100将会出错; */
7 | RandomIndex?: number;
8 | /** 指定是否强制生成骈文密文,默认 false; */
9 | PianwenMode?: boolean;
10 | /** 指定是否强制生成逻辑密文,默认 false; */
11 | LogicMode?: boolean;
12 | /** 指定输出文本是否为繁体中文,默认 false; */
13 | Traditional?: boolean;
14 | }
15 |
16 | export interface CallbackObj {
17 | /** 回调数据的标签 */
18 | Type?: string;
19 | /** 回调数据的值 */
20 | Value?: any;
21 | }
22 |
23 | type Callback = (Data: CallbackObj) => any;
24 | export class Abracadabra {
25 | static TEXT: "TEXT";
26 | static UINT8: "UINT8";
27 |
28 | static ENCRYPT: "ENCRYPT";
29 | static DECRYPT: "DECRYPT";
30 | static AUTO: "AUTO";
31 |
32 | /**
33 | * 创建一个 Abracadabra 实例
34 | * @param {string} inputType 可以是 TEXT 或者 UINT8,默认TEXT
35 | * @param {string} outputType 可以是 TEXT 或者 UINT8,默认TEXT
36 | */
37 | constructor(inputType?: "TEXT" | "UINT8", outputType?: "TEXT" | "UINT8");
38 |
39 | /**
40 | * 魔曰 文言文加密模式
41 | *
42 | * @param {string | Uint8Array} input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
43 | * @param {string} mode 指定模式,可以是 ENCRYPT DECRYPT 中的一种;
44 | * @param {string} key 指定密钥,默认是 ABRACADABRA;
45 | * @param {WenyanConfig} WenyanConfigObj 文言文的生成配置;
46 | * @param {Callback}callback 回调函数,获取执行过程中特定位置的结果数据,调试时使用
47 | * @return {number} 成功则返回 0(失败不会返回,会抛出异常)
48 | */
49 | WenyanInput(
50 | input: string | Uint8Array,
51 | mode: "ENCRYPT" | "DECRYPT",
52 | key?: string,
53 | WenyanConfigObj?: WenyanConfig,
54 | callback?: Callback
55 | ): number;
56 |
57 | /**
58 | * 魔曰 传统加密模式
59 | *
60 | * @param {string | Uint8Array} input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
61 | * @param {string} mode 指定模式,可以是 ENCRYPT DECRYPT AUTO 中的一种;
62 | * @param {string} key 指定密钥,默认是 ABRACADABRA;
63 | * @param {bool} q 指定是否在加密后省略标志位,默认 false/不省略;
64 | * @return {number} 成功则返回 0(失败不会返回,会抛出异常)
65 | */
66 | OldInput(
67 | input: string | Uint8Array,
68 | mode: "ENCRYPT" | "DECRYPT" | "AUTO",
69 | key?: string,
70 | q?: boolean
71 | ): number;
72 |
73 | /**
74 | * 魔曰 传统加密模式
75 | *
76 | * **这是一个过时的接口,请尽可能切换到新接口 OldInput()**
77 | *
78 | * @param {string | Uint8Array} input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
79 | * @param {string} mode 指定模式,可以是 ENCRYPT DECRYPT AUTO 中的一种;
80 | * @param {string} key 指定密钥,默认是 ABRACADABRA;
81 | * @param {bool} q 指定是否在加密后省略标志位,默认 false/不省略;
82 | * @return {number} 成功则返回 0(失败不会返回,会抛出异常)
83 | */
84 | Input(
85 | input: string | Uint8Array,
86 | mode: "ENCRYPT" | "DECRYPT" | "AUTO",
87 | key?: string,
88 | q?: boolean
89 | ): number;
90 |
91 | /**
92 | * 魔曰 文言文加密模式
93 | *
94 | * **这是一个过时的接口,请尽可能切换到新接口 WenyanInput()**
95 | *
96 | * @param {string | Uint8Array} input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
97 | * @param {string} mode 指定模式,可以是 ENCRYPT DECRYPT 中的一种;
98 | * @param {string} key 指定密钥,默认是 ABRACADABRA;
99 | * @param {bool} q 指定是否为密文添加标点符号,默认 true/添加;
100 | * @param {int} r 密文算法的随机程度,越大随机性越强,默认 50,最大100,超过100将会出错;
101 | * @param {bool} p 指定是否强制生成骈文密文,默认 false;
102 | * @param {bool} l 指定是否强制生成逻辑密文,默认 false;
103 | * @return {number} 成功则返回 0(失败不会返回,会抛出异常)
104 | */
105 | Input_Next(
106 | input: string | Uint8Array,
107 | mode: "ENCRYPT" | "DECRYPT",
108 | key?: string,
109 | q?: boolean,
110 | r?: number,
111 | p?: boolean,
112 | l?: boolean
113 | ): number;
114 |
115 | /**
116 | * 魔曰 获取加密/解密后的结果
117 | * @returns {string | Uint8Array} 根据此前指定的输出类型,可能是字符串或字节数组
118 | */
119 | Output(): string | Uint8Array;
120 | }
121 |
--------------------------------------------------------------------------------
/src/javascript/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | /*
14 | * ACKNOWLEDGEMENT
15 | *
16 | * This project uses code from Unishox2 as a compression library,
17 | * with certain modifications made to achieve specific purposes.
18 | *
19 | * Unishox2 is licensed under the Apache License, Version 2.0.
20 | * Copyright (C) 2020 Siara Logics (cc)
21 | *
22 | * Special thanks to Arundale Ramanathan, the author of Unishox2,
23 | * who genuinely answered my enquiries and helped me to debug.
24 | *
25 | * 本作品中包含的 Unishox2 不适用 AIPL-1.1 许可证。
26 | * 使用 Unishox2 须遵守其原始许可证。
27 | *
28 | */
29 |
30 | import * as Core from "./CoreHandler.js";
31 | import { preCheck_OLD, PreCheckResult, stringToUint8Array } from "./Misc.js";
32 | export class Abracadabra {
33 | //主类
34 |
35 | static TEXT = "TEXT"; //常量方便调用
36 | static UINT8 = "UINT8";
37 |
38 | static ENCRYPT = "ENCRYPT";
39 | static DECRYPT = "DECRYPT";
40 | static AUTO = "AUTO";
41 |
42 | #input = ""; //输入类型,可以是 UINT8 或者 TEXT
43 | #output = ""; //输出类型,可以是 UINT8 或者 TEXT
44 |
45 | #res = null; // 输出的结果
46 |
47 | /**
48 | * 创建一个 Abracadabra 实例
49 | * @param{string}inputType 可以是 TEXT 或者 UINT8,默认TEXT
50 | * @param{string}outputType 可以是 TEXT 或者 UINT8,默认TEXT
51 | */
52 | constructor(inputType = Abracadabra.TEXT, outputType = Abracadabra.TEXT) {
53 | //初始化函数指定一些基本参数
54 | if (inputType != Abracadabra.TEXT && inputType != Abracadabra.UINT8) {
55 | throw "Unexpected Argument";
56 | }
57 | if (outputType != Abracadabra.TEXT && outputType != Abracadabra.UINT8) {
58 | throw "Unexpected Argument";
59 | }
60 |
61 | this.#input = inputType;
62 | this.#output = outputType;
63 | }
64 | /**
65 | * 魔曰 文言文加密模式
66 | *
67 | * @param{string | Uint8Array}input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
68 | * @param{string}mode 指定模式,可以是 ENCRYPT DECRYPT 中的一种;
69 | * @param{string}key 指定密钥,默认是 ABRACADABRA;
70 | * @param{WenyanConfig}WenyanConfigObj 文言文的生成配置;
71 | * @param{function}callback 文言文的生成配置;
72 | */
73 | WenyanInput(
74 | input,
75 | mode,
76 | key = "ABRACADABRA",
77 | WenyanConfigObj = new Core.WenyanConfig(true, 50, false, false),
78 | callback
79 | ) {
80 | if (this.#input == Abracadabra.UINT8) {
81 | //如果指定输入类型是UINT8
82 | if (Object.prototype.toString.call(input) != "[object Uint8Array]") {
83 | throw "Unexpected Input Type";
84 | }
85 | if (mode == Abracadabra.ENCRYPT) {
86 | let Nextinput = new Object();
87 | Nextinput.output = input;
88 | this.#res = Core.Enc(Nextinput, key, WenyanConfigObj, callback);
89 | } else if (mode == Abracadabra.DECRYPT) {
90 | let Nextinput = new Object();
91 | Nextinput.output = input;
92 | this.#res = Core.Dec(Nextinput, key, callback);
93 | }
94 | return 0;
95 | } else if (this.#input == Abracadabra.TEXT) {
96 | //如果指定输入类型是TEXT
97 | if (Object.prototype.toString.call(input) != "[object String]") {
98 | throw "Unexpected Input Type";
99 | }
100 | let Nextinput = new Object();
101 | Nextinput.output = stringToUint8Array(input);
102 | if (mode == Abracadabra.ENCRYPT) {
103 | this.#res = Core.Enc(Nextinput, key, WenyanConfigObj, callback);
104 | } else if (mode == Abracadabra.DECRYPT) {
105 | this.#res = Core.Dec(Nextinput, key, callback);
106 | }
107 | return 0;
108 | }
109 | return 0;
110 | }
111 |
112 | /**
113 | * 魔曰 传统加密模式
114 | *
115 | * @param{string | Uint8Array}input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
116 | * @param{string}mode 指定模式,可以是 ENCRYPT DECRYPT AUTO 中的一种;
117 | * @param{string}key 指定密钥,默认是 ABRACADABRA;
118 | * @param{bool}q 指定是否在加密后省略标志位,默认 false/不省略;
119 | */
120 | OldInput(input, mode, key = "ABRACADABRA", q = false) {
121 | if (this.#input == Abracadabra.UINT8) {
122 | //如果指定输入类型是UINT8
123 | if (Object.prototype.toString.call(input) != "[object Uint8Array]") {
124 | throw "Unexpected Input Type";
125 | }
126 | //对于输入UINT8的情况,先尝试将数据转换成字符串进行预检。
127 | let Decoder = new TextDecoder("utf-8", { fatal: true });
128 | let NoNeedtoPreCheck = false;
129 | let inputString = String();
130 | try {
131 | inputString = Decoder.decode(input);
132 | } catch (err) {
133 | //指定了Fatal,如果在解码时出现错误,则无需再执行预检
134 | NoNeedtoPreCheck = true;
135 | }
136 | let preCheckRes;
137 | if (!NoNeedtoPreCheck) {
138 | //如果给定的数据是一个可解码的字符串,那么解码预检
139 | //此时参照预检结果和指定的模式进行判断
140 | preCheckRes = preCheck_OLD(inputString);
141 |
142 | if (
143 | (preCheckRes.isEncrypted && mode != Abracadabra.ENCRYPT) ||
144 | mode == Abracadabra.DECRYPT
145 | ) {
146 | //如果是加密的字符串且没有强制指定要再次加密,或者强制执行解密,自动执行解密
147 | //如果是加密的字符串,指定AUTO在此处会自动解密
148 | this.#res = Core.Dec_OLD(preCheckRes, key);
149 | } else {
150 | this.#res = Core.Enc_OLD(preCheckRes, key, q); //在字符串可解码的情况下,加密时不采用文件模式
151 | }
152 | } else {
153 | //如果给定的数据不可预检(不可能是密文,此时强制解密无效),直接对数据传递给加密函数
154 | preCheckRes = new PreCheckResult(input, true);
155 | this.#res = Core.Enc_OLD(preCheckRes, key, q);
156 | }
157 | } else if (this.#input == Abracadabra.TEXT) {
158 | //如果指定输入类型是TEXT
159 | if (Object.prototype.toString.call(input) != "[object String]") {
160 | throw "Unexpected Input Type";
161 | }
162 | let preCheckRes = preCheck_OLD(input);
163 | if (
164 | (preCheckRes.isEncrypted && mode != Abracadabra.ENCRYPT) ||
165 | mode == Abracadabra.DECRYPT
166 | ) {
167 | //如果是加密的字符串且没有强制指定要再次加密,或者强制执行解密,自动执行解密
168 | //如果是加密的字符串,指定AUTO在此处会自动解密
169 | this.#res = Core.Dec_OLD(preCheckRes, key);
170 | } else {
171 | this.#res = Core.Enc_OLD(preCheckRes, key, q); //在字符串可解码的情况下,加密时不采用文件模式
172 | }
173 | }
174 | return 0;
175 | }
176 |
177 | /**
178 | * 魔曰 获取加密/解密后的结果
179 | */
180 | Output() {
181 | if (this.#res == null) {
182 | throw "Null Output, please input some data at first.";
183 | }
184 | if (typeof this.#res == "object") {
185 | if (this.#output == Abracadabra.TEXT) {
186 | return this.#res.output; //要输出字符串,那么直接输出字符串,解密总会有字符串
187 | } else {
188 | //如果要输出UINT8
189 | if (this.#res.output_B != null) {
190 | //如果有现成的可用,直接输出现成的。
191 | return this.#res.output_B;
192 | } else {
193 | //如果没有现成的,那么就要转换一下再输出
194 | const encoder = new TextEncoder();
195 | const encodedData = encoder.encode(this.#res.output);
196 |
197 | return encodedData;
198 | }
199 | }
200 | } else if (typeof this.#res == "string") {
201 | //如果是字符串类型,那么就是加密结果
202 | if (this.#output == Abracadabra.TEXT) {
203 | return this.#res; //要输出字符串,那么直接输出字符串,解密总会有字符串
204 | } else {
205 | //如果要输出UINT8
206 | //没有现成的,那么就要转换一下再输出
207 | const encoder = new TextEncoder();
208 | const encodedData = encoder.encode(this.#res);
209 | return encodedData;
210 | }
211 | }
212 | }
213 |
214 | /**
215 | * 魔曰 传统加密模式
216 | *
217 | * **这是一个过时的接口,请尽可能切换到新接口 OldInput()**
218 | *
219 | * @param{string | Uint8Array}input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
220 | * @param{string}mode 指定模式,可以是 ENCRYPT DECRYPT AUTO 中的一种;
221 | * @param{string}key 指定密钥,默认是 ABRACADABRA;
222 | * @param{bool}q 指定是否在加密后省略标志位,默认 false/不省略;
223 | */
224 | /* v8 ignore next 4 */
225 | get Input() {
226 | // 使用 getter 属性
227 | return this.OldInput;
228 | }
229 |
230 | /**
231 | * 魔曰 文言文加密模式
232 | *
233 | * **这是一个过时的接口,请尽可能切换到新接口 WenyanInput()**
234 | *
235 | * @param{string | Uint8Array}input 输入的数据,根据此前指定的输入类型,可能是字符串或字节数组
236 | * @param{string}mode 指定模式,可以是 ENCRYPT DECRYPT 中的一种;
237 | * @param{string}key 指定密钥,默认是 ABRACADABRA;
238 | * @param{bool}q 指定是否为密文添加标点符号,默认 true/添加;
239 | * @param{int}r 密文算法的随机程度,越大随机性越强,默认 50,最大100,超过100将会出错;
240 | * @param{bool}p 指定是否强制生成骈文密文,默认 false;
241 | * @param{bool}l 指定是否强制生成逻辑密文,默认 false;
242 | */
243 | /* v8 ignore next 12 */
244 | Input_Next(
245 | input,
246 | mode,
247 | key = "ABRACADABRA",
248 | q = true,
249 | r = 50,
250 | p = false,
251 | l = false
252 | ) {
253 | let conf = new Core.WenyanConfig(q, r, p, l);
254 | return this.WenyanInput(input, mode, key, conf);
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/src/javascript/main.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | import { expect, test, bench } from "vitest";
14 |
15 | import { Abracadabra } from "./main";
16 |
17 | const Rand = Math.random() * 100000000000;
18 | const TestString = Rand.toString();
19 |
20 | const TestLinks = [
21 | "https://pan.baidu.com",
22 | "https://drive.google.com",
23 | "https://bilibili.com",
24 | "https://twitter.com",
25 | "https://github.com",
26 | "https://pixiv.net",
27 | "https://bangumi.moe",
28 | "magnet:?xt=urn:btih:C0FE00AD10B9D9A90A0750D1B5B9F6C6B8F2F5B6",
29 | "https://test.cn",
30 | "https://test.cc",
31 | "https://test.jp",
32 | "https://test.one",
33 | ];
34 |
35 | function generateRandomUint8Array(length) {
36 | // 创建一个指定长度的 Uint8Array
37 | const uint8Array = new Uint8Array(length);
38 |
39 | for (let i = 0; i < length; i++) {
40 | uint8Array[i] = Math.floor(Math.random() * 256);
41 | }
42 |
43 | return uint8Array;
44 | }
45 |
46 | const TestData = [
47 | generateRandomUint8Array(1000),
48 | generateRandomUint8Array(2048),
49 | ];
50 |
51 | test("加/解密测试", { timeout: 15000 }, () => {
52 | const Abra = new Abracadabra();
53 | let TestTemp = TestString;
54 | let TestTemp2 = TestString;
55 | let TestTemp3 = TestString;
56 |
57 | //将随机字符串用仿真加密循环加/解密6次,判断一致性和中途是否出错。
58 | for (let i = 0; i <= 6; i++) {
59 | Abra.WenyanInput(TestTemp, "ENCRYPT", "ABRACADABRA", {
60 | PunctuationMark: i % 2 == 0,
61 | RandomIndex: 50,
62 | PianwenMode: i % 2 == 0,
63 | LogicMode: i % 2 != 0,
64 | Traditional: i % 2 != 0,
65 | });
66 | TestTemp = Abra.Output();
67 | }
68 |
69 | for (let i = 0; i <= 6; i++) {
70 | Abra.WenyanInput(TestTemp, "DECRYPT", "ABRACADABRA");
71 | TestTemp = Abra.Output();
72 | }
73 |
74 | //将随机字符串用传统加密循环加/解密6次,判断一致性和中途是否出错。
75 | for (let i = 0; i <= 6; i++) {
76 | Abra.OldInput(TestTemp2, "ENCRYPT", "ABRACADABRA", true);
77 | TestTemp2 = Abra.Output();
78 | }
79 |
80 | for (let i = 0; i <= 6; i++) {
81 | Abra.OldInput(TestTemp2, "DECRYPT", "ABRACADABRA");
82 | TestTemp2 = Abra.Output();
83 | }
84 | expect(TestTemp).toBe(TestString);
85 | expect(TestTemp2).toBe(TestString);
86 |
87 | //测试传统加密标志位
88 |
89 | Abra.OldInput(TestTemp3, "AUTO", "ABRACADABRA");
90 | TestTemp3 = Abra.Output();
91 | Abra.OldInput(TestTemp3, "AUTO", "ABRACADABRA");
92 | TestTemp3 = Abra.Output();
93 |
94 | expect(TestTemp3).toBe(TestString);
95 | });
96 |
97 | test("链接压缩测试", { timeout: 15000 }, () => {
98 | //测试链接压缩。
99 | const Abra = new Abracadabra();
100 |
101 | TestLinks.forEach((item) => {
102 | //测试不同链接的加解密一致性
103 | let TestTemp = "";
104 | let TestTemp2 = "";
105 | Abra.WenyanInput(item, "ENCRYPT", "ABRACADABRA", {
106 | RandomIndex: 100,
107 | });
108 | TestTemp = Abra.Output();
109 | Abra.WenyanInput(TestTemp, "DECRYPT", "ABRACADABRA");
110 | TestTemp = Abra.Output();
111 |
112 | Abra.OldInput(item, "ENCRYPT", "ABRACADABRA", true);
113 | TestTemp2 = Abra.Output();
114 | Abra.OldInput(TestTemp2, "DECRYPT", "ABRACADABRA");
115 | TestTemp2 = Abra.Output();
116 |
117 | expect(TestTemp).toBe(item);
118 | expect(TestTemp2).toBe(item);
119 | });
120 | });
121 |
122 | test("随机数据加密测试", { timeout: 15000 }, () => {
123 | //测试随机数据的加密。
124 | const Abra = new Abracadabra("UINT8", "UINT8");
125 |
126 | TestData.forEach((data) => {
127 | let TestTemp;
128 | let TestTemp2;
129 | let TestTemp3;
130 | Abra.WenyanInput(data, "ENCRYPT", "ABRACADABRA", {
131 | RandomIndex: 100,
132 | });
133 | TestTemp = Abra.Output();
134 | Abra.WenyanInput(TestTemp, "DECRYPT", "ABRACADABRA");
135 | TestTemp = Abra.Output();
136 |
137 | Abra.OldInput(data, "ENCRYPT", "ABRACADABRA", true);
138 | TestTemp2 = Abra.Output();
139 | Abra.OldInput(TestTemp2, "DECRYPT", "ABRACADABRA");
140 | TestTemp2 = Abra.Output();
141 |
142 | //传统模式,自动判别
143 | Abra.OldInput(data, "AUTO", "ABRACADABRA");
144 | TestTemp3 = Abra.Output();
145 | Abra.OldInput(TestTemp3, "AUTO", "ABRACADABRA");
146 | TestTemp3 = Abra.Output();
147 |
148 | expect(TestTemp).toStrictEqual(data);
149 | expect(TestTemp2).toStrictEqual(data);
150 | expect(TestTemp3).toStrictEqual(data);
151 | });
152 | });
153 |
--------------------------------------------------------------------------------
/src/javascript/mapping.json:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | {
14 | "basic": {
15 | "alphabet": {
16 | "a": ["请", "上", "中", "之", "等", "人", "到", "年", "个", "将"],
17 | "b": ["得", "可", "并", "发", "过", "协", "曲", "闭", "斋", "峦"],
18 | "c": ["页", "于", "而", "被", "无", "挽", "裕", "斜", "绪", "镜"],
19 | "d": ["由", "把", "好", "从", "会", "帕", "莹", "盈", "敬", "粒"],
20 | "e": ["的", "在", "了", "是", "为", "有", "和", "我", "一", "与"],
21 | "f": ["站", "最", "号", "及", "能", "迟", "鸭", "呈", "玻", "据"],
22 | "g": ["着", "很", "此", "但", "看", "浩", "附", "侃", "汐", "绸"],
23 | "h": ["名", "呢", "又", "图", "啊", "棉", "畅", "蒸", "玫", "添"],
24 | "i": ["对", "地", "您", "给", "这", "下", "网", "也", "来", "你"],
25 | "j": ["更", "天", "去", "用", "只", "矽", "萌", "镁", "芯", "夸"],
26 | "k": ["第", "者", "所", "两", "里", "氢", "羟", "纽", "夏", "春"],
27 | "l": ["自", "做", "前", "二", "他", "氦", "汀", "兰", "竹", "捷"],
28 | "m": ["家", "点", "路", "至", "十", "锂", "羧", "暑", "夕", "振"],
29 | "n": ["区", "想", "向", "主", "四", "铍", "烃", "惠", "芳", "岩"],
30 | "o": ["就", "新", "吗", "该", "不", "多", "还", "要", "让", "大"],
31 | "p": ["小", "如", "成", "位", "其", "硼", "酞", "褔", "苑", "笋"],
32 | "q": ["吧", "每", "机", "几", "总", "碳", "铂", "涓", "绣", "悦"],
33 | "r": ["起", "它", "内", "高", "次", "氮", "铵", "奏", "鲤", "淳"],
34 | "s": ["非", "元", "类", "五", "使", "氧", "醇", "迷", "霁", "琅"],
35 | "t": ["首", "进", "即", "没", "市", "氖", "酯", "琳", "绫", "濑"],
36 | "u": ["后", "三", "本", "都", "时", "月", "或", "说", "已", "以"],
37 | "v": ["种", "快", "那", "篇", "万", "钠", "炔", "柯", "睿", "琼"],
38 | "w": ["长", "按", "报", "比", "信", "硅", "烷", "静", "欣", "束"],
39 | "x": ["再", "带", "才", "全", "呀", "磷", "烯", "柔", "雪", "冰"],
40 | "y": ["业", "却", "版", "美", "们", "硫", "桉", "寒", "冻", "玖"],
41 | "z": ["像", "走", "文", "各", "当", "氯", "缬", "妃", "琉", "璃"],
42 | "A": ["贴", "则", "老", "生", "达", "商", "行", "周", "证", "经"],
43 | "B": ["事", "场", "同", "化", "找", "建", "手", "道", "间", "式"],
44 | "C": ["特", "城", "型", "定", "接", "局", "问", "重", "叫", "通"],
45 | "D": ["件", "少", "面", "金", "近", "买", "听", "学", "见", "称"],
46 | "E": ["写", "选", "片", "体", "组", "先", "仅", "别", "表", "现"],
47 | "F": ["雨", "泊", "注", "织", "赴", "茶", "因", "设", "环", "青"],
48 | "G": ["数", "心", "子", "处", "作", "项", "谁", "分", "转", "字"],
49 | "H": ["砂", "妥", "鹦", "课", "栗", "霞", "鹉", "翌", "蕴", "憩"],
50 | "I": ["畔", "珑", "咫", "瑞", "玲", "郊", "蛟", "昱", "祉", "菁"],
51 | "J": ["铁", "宙", "耕", "琴", "铃", "瑰", "旬", "茉", "砺", "莅"],
52 | "K": ["钇", "莉", "筱", "森", "曳", "苹", "踵", "晰", "砥", "舀"],
53 | "L": ["锆", "粟", "魄", "辉", "谜", "馅", "醋", "甄", "韶", "泪"],
54 | "M": ["钌", "倘", "祥", "善", "泉", "惦", "铠", "骏", "韵", "泣"],
55 | "N": ["铑", "筑", "铿", "智", "禀", "磊", "桨", "檀", "荧", "铭"],
56 | "O": ["钯", "骐", "烛", "蔬", "凛", "溯", "困", "炯", "酿", "瑕"],
57 | "P": ["银", "榻", "驿", "缎", "澟", "绒", "莺", "萤", "桅", "枕"],
58 | "Q": ["镉", "赞", "瑾", "程", "怡", "漱", "穗", "湍", "栀", "皆"],
59 | "R": ["碘", "礼", "饴", "舒", "芷", "麟", "沥", "描", "锄", "墩"],
60 | "S": ["锡", "彰", "瞻", "雅", "贮", "喵", "翊", "闪", "翎", "婉"],
61 | "T": ["钨", "咨", "涌", "益", "嵩", "御", "饶", "纺", "栩", "稔"],
62 | "U": ["铋", "骆", "橘", "未", "泰", "频", "琥", "囍", "浣", "裳"],
63 | "V": ["钕", "飒", "浇", "哦", "途", "瓢", "珀", "涨", "仓", "棠"],
64 | "W": ["祁", "蓬", "灿", "部", "涧", "舫", "曙", "航", "礁", "渡"],
65 | "X": ["旺", "嫦", "漫", "佑", "钥", "谧", "葵", "咩", "诵", "绮"],
66 | "Y": ["阐", "译", "锻", "茜", "坞", "砌", "靛", "猫", "芮", "绚"],
67 | "Z": ["拌", "皎", "笙", "沃", "悟", "拓", "遨", "揽", "昼", "蔗"]
68 | },
69 | "numbersymbol": {
70 | "0": ["卡", "风", "水", "放", "花", "钾", "宏", "谊", "探", "棋"],
71 | "1": ["需", "头", "话", "曾", "楼", "钙", "吾", "恋", "菲", "遥"],
72 | "2": ["连", "系", "门", "力", "量", "钛", "苗", "氛", "鹤", "雀"],
73 | "3": ["书", "亿", "跟", "深", "方", "钒", "鸳", "鸯", "纸", "鸢"],
74 | "4": ["若", "低", "谈", "明", "百", "铬", "羯", "尧", "舜", "兆"],
75 | "5": ["关", "客", "读", "双", "回", "锰", "熙", "瀚", "渊", "灯"],
76 | "6": ["较", "品", "嘛", "单", "价", "钴", "阑", "珊", "雁", "鹂"],
77 | "7": ["山", "西", "动", "厂", "热", "锌", "鹃", "鸠", "昆", "仑"],
78 | "8": ["言", "笑", "度", "易", "身", "镓", "乾", "坤", "澈", "饺"],
79 | "9": ["份", "星", "千", "仍", "办", "锗", "彗", "聪", "慧", "磋"],
80 | "+": ["集", "费", "传", "室", "拉"],
81 | "/": ["难", "界", "指", "管", "具"],
82 | "?": ["相", "儿", "李", "早", "拿"],
83 | "-": ["科", "白", "段", "飞", "住"],
84 | ".": ["利", "红", "板", "光", "约"],
85 | "(": ["变", "款", "林", "夹", "院"],
86 | ")": ["服", "句", "声", "务", "游"],
87 | "[": ["股", "南", "社", "阿", "远"],
88 | "]": ["意", "换", "些", "必", "赛"],
89 | "<": ["届", "完", "乐", "彩", "讲"],
90 | ">": ["展", "帮", "且", "物", "班"],
91 | ",": ["何", "流", "密", "某", "房"],
92 | "|": ["语", "亚", "常", "除", "装"],
93 | "=": ["极", "载", "题", "刚", "气"],
94 | "@": ["米", "影", "德", "世", "坐"],
95 | "#": ["北", "招", "短", "活", "斯"],
96 | "!": ["值", "店", "树", "哪", "余"],
97 | "~": ["盘", "速", "座", "求", "创"],
98 | "`": ["梦", "足", "半", "视", "安"],
99 | "quot;: ["空", "歌", "派", "顶", "登"],
100 | "%": ["夜", "云", "感", "啦", "欲"],
101 | "^": ["边", "工", "眼", "街", "奖"],
102 | "&": ["获", "占", "理", "任", "实"],
103 | "*": ["知", "掉", "色", "讯", "克"],
104 | "_": ["直", "评", "往", "层", "园"],
105 | "{": ["留", "靠", "亦", "罗", "营"],
106 | "}": ["合", "尚", "产", "诚", "汨"],
107 | ":": ["曱", "朩", "杉", "杸", "歩"],
108 | ";": ["毋", "氕", "気", "氘", "氙"]
109 | }
110 | },
111 | "special": {
112 | "DECRYPT": {
113 | "JP": ["桜", "込", "凪", "雫", "実", "沢"],
114 | "CN": ["玚", "俟", "玊", "欤", "瞐", "珏"]
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/javascript/mapping_next.json:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | {
14 | "Actual": {
15 | "N": {
16 | "alphabet": {
17 | "a": "人",
18 | "b": "镜",
19 | "c": "鹏",
20 | "d": "曲",
21 | "e": "霞",
22 | "f": "绸",
23 | "g": "裳",
24 | "h": "路",
25 | "i": "岩",
26 | "j": "叶",
27 | "k": "鲤",
28 | "l": "月",
29 | "m": "雪",
30 | "n": "冰",
31 | "o": "局",
32 | "p": "恋",
33 | "q": "福",
34 | "r": "铃",
35 | "s": "琴",
36 | "t": "家",
37 | "u": "天",
38 | "v": "韵",
39 | "w": "书",
40 | "x": "莺",
41 | "y": "璃",
42 | "z": "雨",
43 | "A": "文",
44 | "B": "涧",
45 | "C": "水",
46 | "D": "花",
47 | "E": "风",
48 | "F": "棋",
49 | "G": "楼",
50 | "H": "鹤",
51 | "I": "鸢",
52 | "J": "灯",
53 | "K": "雁",
54 | "L": "星",
55 | "M": "声",
56 | "N": "树",
57 | "O": "茶",
58 | "P": "竹",
59 | "Q": "兰",
60 | "R": "苗",
61 | "S": "心",
62 | "T": "语",
63 | "U": "礼",
64 | "V": "梦",
65 | "W": "庭",
66 | "X": "木",
67 | "Y": "驿",
68 | "Z": "火"
69 | },
70 | "numbersymbol": {
71 | "0": "森",
72 | "1": "夏",
73 | "2": "光",
74 | "3": "林",
75 | "4": "物",
76 | "5": "云",
77 | "6": "夜",
78 | "7": "城",
79 | "8": "春",
80 | "9": "空",
81 | "+": "雀",
82 | "/": "鹂",
83 | "=": "鸳"
84 | }
85 | },
86 | "V": {
87 | "alphabet": {
88 | "a": "关",
89 | "b": "赴",
90 | "c": "呈",
91 | "d": "添",
92 | "e": "停",
93 | "f": "成",
94 | "g": "走",
95 | "h": "达",
96 | "i": "行",
97 | "j": "称",
98 | "k": "见",
99 | "l": "学",
100 | "m": "听",
101 | "n": "买",
102 | "o": "作",
103 | "p": "弹",
104 | "q": "写",
105 | "r": "定",
106 | "s": "谈",
107 | "t": "动",
108 | "u": "旅",
109 | "v": "返",
110 | "w": "度",
111 | "x": "开",
112 | "y": "筑",
113 | "z": "选",
114 | "A": "流",
115 | "B": "指",
116 | "C": "换",
117 | "D": "探",
118 | "E": "放",
119 | "F": "看",
120 | "G": "报",
121 | "H": "事",
122 | "I": "泊",
123 | "J": "现",
124 | "K": "迸",
125 | "L": "彰",
126 | "M": "需",
127 | "N": "飞",
128 | "O": "游",
129 | "P": "求",
130 | "Q": "御",
131 | "R": "航",
132 | "S": "歌",
133 | "T": "读",
134 | "U": "振",
135 | "V": "登",
136 | "W": "任",
137 | "X": "留",
138 | "Y": "奏",
139 | "Z": "连"
140 | },
141 | "numbersymbol": {
142 | "0": "知",
143 | "1": "至",
144 | "2": "致",
145 | "3": "去",
146 | "4": "画",
147 | "5": "说",
148 | "6": "进",
149 | "7": "信",
150 | "8": "取",
151 | "9": "问",
152 | "+": "笑",
153 | "/": "视",
154 | "=": "言"
155 | }
156 | },
157 | "MV": ["欲", "应", "可", "能", "将", "请", "想", "必", "当"],
158 | "A": {
159 | "alphabet": {
160 | "a": "莹",
161 | "b": "畅",
162 | "c": "新",
163 | "d": "高",
164 | "e": "静",
165 | "f": "美",
166 | "g": "绿",
167 | "h": "佳",
168 | "i": "善",
169 | "j": "良",
170 | "k": "瀚",
171 | "l": "明",
172 | "m": "早",
173 | "n": "宏",
174 | "o": "青",
175 | "p": "遥",
176 | "q": "速",
177 | "r": "慧",
178 | "s": "绚",
179 | "t": "绮",
180 | "u": "寒",
181 | "v": "冷",
182 | "w": "银",
183 | "x": "灵",
184 | "y": "绣",
185 | "z": "北",
186 | "A": "临",
187 | "B": "南",
188 | "C": "俊",
189 | "D": "捷",
190 | "E": "骏",
191 | "F": "益",
192 | "G": "雅",
193 | "H": "舒",
194 | "I": "智",
195 | "J": "谜",
196 | "K": "彩",
197 | "L": "余",
198 | "M": "短",
199 | "N": "秋",
200 | "O": "乐",
201 | "P": "怡",
202 | "Q": "瑞",
203 | "R": "惠",
204 | "S": "和",
205 | "T": "纯",
206 | "U": "悦",
207 | "V": "迷",
208 | "W": "长",
209 | "X": "少",
210 | "Y": "近",
211 | "Z": "清"
212 | },
213 | "numbersymbol": {
214 | "0": "远",
215 | "1": "极",
216 | "2": "安",
217 | "3": "聪",
218 | "4": "秀",
219 | "5": "旧",
220 | "6": "浩",
221 | "7": "盈",
222 | "8": "快",
223 | "9": "悠",
224 | "+": "后",
225 | "/": "轻",
226 | "=": "坚"
227 | }
228 | },
229 | "AD": {
230 | "alphabet": {
231 | "a": "诚",
232 | "b": "畅",
233 | "c": "新",
234 | "d": "高",
235 | "e": "静",
236 | "f": "恒",
237 | "g": "愈",
238 | "h": "谨",
239 | "i": "善",
240 | "j": "良",
241 | "k": "频",
242 | "l": "笃",
243 | "m": "早",
244 | "n": "湛",
245 | "o": "昭",
246 | "p": "遥",
247 | "q": "速",
248 | "r": "朗",
249 | "s": "祗",
250 | "t": "攸",
251 | "u": "徐",
252 | "v": "咸",
253 | "w": "皆",
254 | "x": "灵",
255 | "y": "恭",
256 | "z": "弥",
257 | "A": "临",
258 | "B": "允",
259 | "C": "公",
260 | "D": "捷",
261 | "E": "淳",
262 | "F": "益",
263 | "G": "雅",
264 | "H": "舒",
265 | "I": "嘉",
266 | "J": "勤",
267 | "K": "协",
268 | "L": "永",
269 | "M": "短",
270 | "N": "歆",
271 | "O": "乐",
272 | "P": "怡",
273 | "Q": "已",
274 | "R": "忻",
275 | "S": "和",
276 | "T": "谧",
277 | "U": "悦",
278 | "V": "稍",
279 | "W": "长",
280 | "X": "少",
281 | "Y": "近",
282 | "Z": "尚"
283 | },
284 | "numbersymbol": {
285 | "0": "远",
286 | "1": "极",
287 | "2": "安",
288 | "3": "竟",
289 | "4": "悉",
290 | "5": "渐",
291 | "6": "颇",
292 | "7": "辄",
293 | "8": "快",
294 | "9": "悠",
295 | "+": "后",
296 | "/": "轻",
297 | "=": "曾"
298 | }
299 | }
300 | },
301 | "Virtual": {
302 | "zhi": ["之"],
303 | "hu": ["乎"],
304 | "zhe": ["者"],
305 | "ye": ["也"],
306 | "for": ["为"],
307 | "ba": ["把"],
308 | "le": ["了"],
309 | "er": ["而"],
310 | "this": ["此", "斯"],
311 | "still": ["仍"],
312 | "with": ["与", "同"],
313 | "also": ["亦", "也"],
314 | "is": ["是", "乃"],
315 | "not": ["未", "莫"],
316 | "or": ["或"],
317 | "more": ["更"],
318 | "make": ["使", "将", "让"],
319 | "and": ["与", "同"],
320 | "anti": ["非", "不"],
321 | "why": ["为何", "奈何", "何哉"],
322 | "but": ["但", "却", "则", "而", "况", "且"],
323 | "like": ["似", "如", "若"],
324 | "if": ["若", "倘"],
325 | "int": ["哉", "呼", "噫"],
326 | "self": ["自"],
327 | "by": ["以", "于"]
328 | },
329 | "Sentences": {
330 | "Begin": [
331 | "1D/非/N/ye",
332 | "1B/N/曰/R",
333 | "1B/若夫/N",
334 | "1C/anti/MV/V/ye/P",
335 | "2B/A/N/曰/R",
336 | "2B/N/以/A",
337 | "2C/N/anti/在/A",
338 | "2C/N/make/N/zhi",
339 | "2C/MV/N/zhe/A",
340 | "2E/有/N/则/A",
341 | "2E/不/入/于/N/、/则/入/于/N/P",
342 | "2C/V/zhe/V/zhi",
343 | "2D/but/MV/A/zhe/A",
344 | "3C/N/V/by/N",
345 | "3B/初,/N/V/by/N",
346 | "3B/夫/N/anti/V/by/N",
347 | "3B/AD/V/zhi/谓/A",
348 | "3C/A/N/为/N/兮",
349 | "3B/V/而/V/zhi/zhi/谓/A",
350 | "3B/N/,/N/zhi/N/ye/P",
351 | "3D/A/之/V/者/必/有/N/P",
352 | "3D/有/所/V/N/,/则/不/得/其/V/P",
353 | "4D/非/N/不/A/,/V/不/A",
354 | "4C/A/N/AD/V",
355 | "4C/V/N/以/V/N",
356 | "4D/A/者/自/V/也/,/而/N/自/V/也/P",
357 | "4E/N/不在/A/,/有/N/则/A/P",
358 | "4E/上/不/V/N/,/下/不/V/N/P",
359 | "4D/A/N/常有/,/而/A/N/不常有/P",
360 | "4D/V/N/者/,/N/之/N/也/P",
361 | "4E/N/有/MV/V/,/N/有/AD/然/P",
362 | "4D/N/无/N/,/无以/V/N",
363 | "4D/欲/V/其/N/者/,/先/V/其/N/P",
364 | "4D/今/夫/N/,/一/N/之/多/,/及/其/A/A/P",
365 | "4D/V/之/不/为/N/,/V/之/不/为/N/P",
366 | "4D/吾/为/N/之/所/V/,/N/亦/为/吾/所/V/P",
367 | "5D/V/N/而/V/A/,/V/zhi/道/ye/P",
368 | "5D/A/N/之/N/,/like/N/like/N/P",
369 | "5E/N/zhi/V/V/,/实为/A/A/P",
370 | "5C/本/MV/V/A/,/anti/V/N/N",
371 | "5C/N/之/无/N/,/N/V/之/N",
372 | "5D/V/N/而/V/之/者/,/非/其/N/AD/也/P",
373 | "5B/今/V/N/以/V/A/N",
374 | "5B/N/乃/V/V/N/zhi/N",
375 | "5B/N/N/无/V/,/V/而/必/V/P",
376 | "5B/今/N/乃/A/N/A/N",
377 | "5C/A/N/V/A/N",
378 | "5B/夫/N/、/N/不/MV/AD/V/N",
379 | "5D/不/有/A/N/,/何/V/A/N/Q",
380 | "5D/以/A/N/为/N/者/,/N/MV/弗/而/V/之/P",
381 | "6B/以/N/V/,/like/V/N/V/N",
382 | "6B/A/N/zhi/N/,/V/zhi/以/V/其/N",
383 | "6B/A/N/V/于/N/而/V/N",
384 | "6B/A/N/未/V/N/、/N/之/N",
385 | "6B/V/A/N/若/V/A/N",
386 | "6C/A/N/为/N/兮/,/A/N/为/N/P",
387 | "6D/不/V/N/,/不/V/N/,/当/以/AD/V/论/P",
388 | "6D/A/则为/V/N/,/A/则为/V/N/P",
389 | "6D/若/居/A/N/之/N/,/则/当/A/N/之/V/P",
390 | "6D/N/无/N/则/V/,/N/无/N/则/V/P",
391 | "6D/A/者/V/而/V/之/,/A/者/V/而/V/之/P",
392 | "6D/N/受/命/于/N/,/固/AD/然/V/于/A/N/P",
393 | "6D/V/N/而/不/能/V/,/V/而/不/能/V/,/N/也/P",
394 | "6D/常/有/N/V/A/N/,/请/N/为/N/P",
395 | "7D/夫/A/之/N/V/N/者/,/其/所以/AD/V/者/N/也/P",
396 | "7C/N/以/A/A/,/AD/V/A/N",
397 | "7B/V/N/A/,/A/N/V/N",
398 | "7B/N/V/以/N/V/,/V/不/V/N",
399 | "7C/N/N/V/N/,/A/于/N/N",
400 | "7D/MV/AD/V/A/N/,/but/V/V/不/A",
401 | "7C/或/V/N/V/N/,/V/N/于/N",
402 | "7E/则有/N/A/N/A/,/N/N/具/V",
403 | "7D/V/A/N/zhe/,/常/V/其/所/A/,/而/V/其/所/A/P",
404 | "7D/A/N/之/N/,/常/V/于/其/所/AD/V/而/不/V/之/处/P",
405 | "7D/A/N/之/N/不在/N/,/在乎/A/N/之/N/也/P",
406 | "8D/V/A/N/,/V/A/N/,/by/MV/A/zhi/N/P",
407 | "8D/N/anti/AD/V/zhe/by/AD/V/zhe/V/,/anti/MV/AD/V/P",
408 | "8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P",
409 | "8C/V/N/A/A/,/V/N/A/A",
410 | "8C/N/V/A/N/,/N/V/A/N",
411 | "8C/N/在/A/N/,/A/N/zhi/A/,/V/于/N/P",
412 | "8C/A/N/AD/V/,/N/N/AD/V",
413 | "8C/A/N/V/N/,/N/N/V/N/P",
414 | "8B/尝/V/A/N/,/AD/V/A/N/zhi/N",
415 | "8D/予/V/夫/A/N/A/N/,/在/A/N/之/N",
416 | "8D/N/V/于/A/N/,/而/N/V/于/A/N",
417 | "8D/N/V/N/为/N/,/N/V/N/为/N/P",
418 | "8B/N/A/即/N/A/,/N/A/即/N/A/P",
419 | "8D/虽/无/N/N/zhi/V/,/亦/V/以/AD/V/A/N/P",
420 | "8D/是/故/A/N/有/A/N/,/必/AD/V/以/得/之/,/AD/V/以/失/之/P",
421 | "8D/A/N/之/A/N/,/常/为/A/N/之/A/N/P",
422 | "9D/A/N/V/zhi/而不/V/zhi/、亦/make/A/N/er/复/V/A/N/ye/P",
423 | "9D/N/MV/V/N/V/V/,/but/N/N/AD/V/P",
424 | "9B/以/N/,/当/V/A/N/,/非/N/V/N/所/MV/AD/V/P",
425 | "9C/此/N/有/A/N/A/N/,/A/N/A/N/P",
426 | "9D/是/N/ye/,/N/A/N/A/,/N/A/N/A/P"
427 | ],
428 | "Main": [
429 | "1B/非/N/ye",
430 | "1B/N/曰/R",
431 | "1C/anti/MV/V/ye",
432 | "2C/N/make/N/zhi",
433 | "2C/MV/N/zhe/A",
434 | "2E/有/N/则/A",
435 | "2E/不/入/于/N/、/则/入/于/N/P",
436 | "2C/V/zhe/V/zhi",
437 | "2C/but/MV/A/zhe/A",
438 | "3C/N/with/N/V",
439 | "3B/N/曰,何/A/zhi/V/Q",
440 | "3D/有/所/V/N/,/则/不/得/其/V/P",
441 | "4C/A/N/AD/V",
442 | "4C/V/N/以/V/N",
443 | "4D/N/无/N/,/无以/V/N/P",
444 | "4D/此/谓/V/N/在/V/其/N/P",
445 | "4D/今/夫/N/,/一/N/之/多/,/及/其/A/A/P",
446 | "4D/欲/V/其/N/者/,/先/V/其/N/P",
447 | "4E/上/不/V/N/,/下/不/V/N/P",
448 | "4D/A/者/自/V/也/,/而/N/自/V/也/P",
449 | "4D/V/N/者/,/N/之/N/也/P",
450 | "4D/以/此/V/N/,/何/N/不/V/Q",
451 | "4E/N/不在/A/,/有/N/则/A/P",
452 | "4C/N/有/MV/V/,/N/有/AD/然/P",
453 | "4D/N/非/V/而/V/之/者/,/孰/MV/无/N/P",
454 | "4D/A/N/常有/,/而/A/N/不常有/P",
455 | "4D/吾/为/N/之/所/V/,/N/亦/为/吾/所/V/P",
456 | "4C/不/以/N/V/,/不/以/N/V/P",
457 | "4D/V/之/不/为/N/,/V/之/不/为/N/P",
458 | "5B/今/V/N/以/V/A/N",
459 | "5B/N/乃/V/V/N/zhi/N",
460 | "5B/N/N/无/V/,/V/而/必/V/P",
461 | "5C/本/MV/V/A/,/anti/V/N/N",
462 | "5D/V/N/而/V/之/者/,/非/其/N/AD/也/P",
463 | "5D/A/N/之/N/,/like/N/like/N/P",
464 | "5D/以/A/N/为/N/者/,/N/MV/弗/而/V/之/P",
465 | "5D/故/夫/A/N/之/N/,/不/可/make/其/V/于/N/也/P",
466 | "5B/今/N/乃/A/N/A/N",
467 | "5E/每/有/V/N/,/便/AD/然/V/N/P",
468 | "5D/N/V/而/A/N/V/也",
469 | "5E/不/有/A/N/,/何/V/A/N/Q",
470 | "5C/N/之/无/N/,/N/V/之/N",
471 | "6D/N/A/N/A/,/则/所/V/得/其/A/P",
472 | "6B/以/N/V/,/like/V/N/V/N",
473 | "6B/V/A/N/若/V/A/N",
474 | "6C/N/V/,/V/N/V/N",
475 | "6E/虽/V/V/A/A/,/A/A/不/同/P",
476 | "6D/而/A/N/zhi/N/,/V/zhi/以/V/其/N/P",
477 | "6B/A/N/V/于/N/而/V/N",
478 | "6B/A/N/未/V/N/、/N/之/N",
479 | "6C/V/A/N/,/V/A/N",
480 | "6C/A/N/为/N/兮/,/A/N/为/N/P",
481 | "6D/V/MV/with/其/N/,/而/V/MV/V/以/N/者/,/N/也/P",
482 | "6D/A/N/必/有/A/N/V/之者/、/予/可/无/N/也/P",
483 | "6D/将/有/V/,/则/V/A/N/以/V/N/P",
484 | "6D/不/V/N/,/不/V/N/,/当/以/AD/V/论/P",
485 | "6D/A/则为/V/N/,/A/则为/V/N/P",
486 | "6D/N/无/N/则/V/,/N/无/N/则/V/P",
487 | "6D/A/者/V/而/V/之/,/A/者/V/而/V/之/P",
488 | "6D/若/居/A/N/之/N/,/则/当/A/N/之/V/P",
489 | "6D/N/受/命/于/N/,/固/AD/然/V/于/A/N/P",
490 | "6D/V/N/而/不/能/V/,/V/而/不/能/V/,/N/也/P",
491 | "6D/常/有/N/V/A/N/,/请/N/为/N/P",
492 | "7D/夫/A/之/N/V/N/者/,/其/所以/AD/V/者/N/也/P",
493 | "7B/N/V/以/N/V/,/V/不/V/N",
494 | "7C/N/N/V/N/,/A/于/N/N",
495 | "7D/MV/AD/V/A/N/,/but/V/V/不/A",
496 | "7C/或/V/N/V/N/,/V/N/于/N",
497 | "7D/V/A/N/zhe/,/常/V/其/所/A/,/而/V/其/所/A/P",
498 | "7D/A/N/之/不/V/也/AD/矣/,/欲/N/之/无/N/也/AD/矣/P",
499 | "7D/A/N/之/N/,/常/V/于/其/所/AD/V/而/不/V/之/处/P",
500 | "7D/A/N/之/N/不在/N/,/在乎/A/N/之/N/也/P",
501 | "7D/A/N/之/N/,/V/之/N/而/V/之/N/也/P",
502 | "7D/是故/A/N/不必不如/N/,/N/不必/A/于/A/N/P",
503 | "7B/有/A/N/、/A/N/、/A/N/之/N/P",
504 | "8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P",
505 | "8E/是/故/无/A/无/A/,/无/A/无/A/,/N/之/所/V/、/N/之/所/V/ye/P",
506 | "8C/V/N/A/A/,/V/N/A/A",
507 | "8B/N/在/A/N/,/A/N/zhi/A/,/V/于/N/P",
508 | "8B/like/A/N/V/N/,/不/V/N/V/之/N/P",
509 | "8C/A/N/AD/V/,/N/N/AD/V",
510 | "8D/A/N/之/A/N/,/常/为/A/N/之/A/N/P",
511 | "8C/A/N/V/N/,/N/N/V/N/P",
512 | "8D/虽/无/N/N/zhi/V/,/亦/V/以/AD/V/A/N/P",
513 | "8D/予/V/夫/A/N/A/N/,/在/A/N/之/N",
514 | "8D/故/V/A/N/者/,/当/V/A/N/之/A/N/P",
515 | "8D/N/V/于/A/N/,/而/N/V/于/A/N",
516 | "8D/N/V/N/为/N/,/N/V/N/为/N/P",
517 | "8B/N/A/即/N/A/,/N/A/即/N/A/P",
518 | "8B/A/N/MV/A/N/之/A/,/V/N/中/之/A",
519 | "8D/N/V/于/A/N/之上/,/AD/V/于/A/N/之间/P",
520 | "8D/是/故/A/N/有/A/N/,/必/AD/V/以/得/之/,/AD/V/以/失/之/P",
521 | "8B/使/其/A/N/AD/V/,/A/N/AD/V/P",
522 | "9B/N/MV/V/N/V/V/,/but/N/N/AD/V",
523 | "9D/A/N/V/zhi/而不/V/zhi/、亦/make/A/N/er/复/V/A/N/ye/P",
524 | "9D/以/N/,/当/V/A/N/,/非/N/V/N/所/MV/AD/V/P",
525 | "9C/此/N/有/A/N/A/N/,/A/N/A/N/P",
526 | "9E/是/N/ye/,/N/A/N/A/,/N/A/N/A",
527 | "9E/V/A/N/,/N/A/N/A/,/乃/AD/V"
528 | ],
529 | "End": [
530 | "1B/非/N/ye",
531 | "1C/anti/MV/V/ye",
532 | "2C/唯/N/V/zhi",
533 | "2B/V/by/N",
534 | "2D/其/also/A/hu/其/V/ye/P",
535 | "2C/N/make/N/zhi",
536 | "2C/MV/N/zhe/A",
537 | "2E/有/N/则/A",
538 | "2C/V/zhe/V/zhi",
539 | "2C/but/MV/A/zhe/A",
540 | "3C/V/在/A/N",
541 | "3D/今/zhi/V/zhe/,/亦将有/V/于/this/N/P",
542 | "3D/某也/A/,/某也/A/,/可/不/A/哉",
543 | "3D/有/所/V/N/,/则/不/得/其/V/P",
544 | "4B/V/N/zhi/N/by/N",
545 | "4D/A/者/自/V/也/,/而/N/自/V/也/P",
546 | "4C/A/N/AD/V",
547 | "4C/V/N/以/V/N",
548 | "4D/N/无/N/,/无以/V/N",
549 | "4D/V/N/者/,/N/之/N/也/P",
550 | "4D/以/此/V/N/,/何/N/不/V/Q",
551 | "4D/噫/,/A/N/ye/,/N/谁/与/V/Q",
552 | "4D/此/谓/V/N/在/V/其/N/P",
553 | "4D/今/夫/N/,/一/N/之/多/,/及/其/A/A/P",
554 | "4E/上/不/V/N/,/下/不/V/N/P",
555 | "4D/欲/V/其/N/者/,/先/V/其/N/P",
556 | "4C/不/以/N/V/,/不/以/N/V/P",
557 | "4D/V/之/不/为/N/,/V/之/不/为/N/P",
558 | "4D/然/则/A/N/自/N/V/矣/P",
559 | "5B/请/V/N/zhi/N/中/,/是/N/zhi/N/P",
560 | "5D/今/V/N/以/V/A/N",
561 | "5B/N/乃/V/V/N/zhi/N",
562 | "5B/N/N/无/V/,/V/而/必/V/P",
563 | "5C/本/MV/V/A/,/anti/V/N/N",
564 | "5D/V/N/而/V/之/者/,/非/其/N/AD/也/P",
565 | "5D/以/A/N/为/N/者/,/N/MV/弗/而/V/之/P",
566 | "5D/A/N/之/N/,/like/N/like/N/P",
567 | "5D/故/夫/A/N/之/N/,/不/可/make/其/V/于/N/也/P",
568 | "5B/今/N/乃/A/N/A/N",
569 | "5D/N/V/而/A/N/V/也",
570 | "5E/不/有/A/N/,/何/V/A/N/Q",
571 | "5C/N/之/无/N/,/N/V/之/N",
572 | "6C/A/N/为/N/兮/,/A/N/为/N/P",
573 | "6D/以/N/V/,/like/V/N/V/N",
574 | "6D/A/zhi/V/N/,/亦/like/今/zhi/V/N/,/A/夫/P",
575 | "6D/A/者/V/而/V/之/,/A/者/V/而/V/之/P",
576 | "6D/若/居/A/N/之/N/,/则/当/A/N/之/V/P",
577 | "6B/N/V/,/V/N/V/N",
578 | "6E/V/N/之/N/,/为/N/V/者/,/可以/V/矣/P",
579 | "6D/V/MV/with/其/N/,/而/V/MV/V/以/N/者/,/N/也/P",
580 | "6D/A/N/必/有/A/N/V/之者/、/予/可/无/N/也/P",
581 | "6E/虽/V/V/A/A/,/A/A/不/同/P",
582 | "6D/将/有/V/,/则/V/A/N/以/V/N/P",
583 | "6D/不/V/N/,/不/V/N/,/当/以/AD/V/论/P",
584 | "6D/A/则为/V/N/,/A/则为/V/N/P",
585 | "6D/N/受/命/于/N/,/固/AD/然/V/于/A/N/P",
586 | "6D/N/无/N/则/V/,/N/无/N/则/V/P",
587 | "6D/N/A/N/A/,/则/所/V/得/其/A/P",
588 | "6D/常/有/N/V/A/N/,/请/N/为/N/P",
589 | "6D/V/N/而/不/能/V/,/V/而/不/能/V/,/N/也/P",
590 | "7D/夫/A/之/N/V/N/者/,/其/所以/AD/V/者/N/也/P",
591 | "7D/N/V/以/N/V/,/V/不/V/N",
592 | "7C/N/N/V/N/,/A/于/N/N",
593 | "7D/MV/AD/V/A/N/,/but/V/V/不/A",
594 | "7E/或/V/N/V/N/,/V/N/于/N",
595 | "7D/A/N/之/N/不在/N/,/在乎/A/N/之/N/也/P",
596 | "7D/A/N/之/N/,/V/之/N/而/V/之/N/也/P",
597 | "7D/是故/A/N/不必不如/N/,/N/不必/A/于/A/N/P",
598 | "7B/有/A/N/、/A/N/、/A/N/之/N/P",
599 | "8E/虽/N/A/N/A/,/所/以/V/N/,其/N/A/ye/P",
600 | "8B/like/A/N/V/N/,/不/V/N/V/之/N/P",
601 | "8B/N/A/即/N/A/,/N/A/即/N/A/P",
602 | "8D/何必/V/N/V/N/,/V/N/zhi/N/N/哉/P",
603 | "8D/N/anti/MV/V/N/,/still/继/N/V/,/why/,/and/N/而/anti/V/N/ye/P",
604 | "8D/是/故/A/N/有/A/N/,/必/AD/V/以/得/之/,/AD/V/以/失/之/P",
605 | "8C/V/N/A/A/,/V/N/A/A",
606 | "8B/N/在/A/N/,/A/N/zhi/A/,/V/于/N/P",
607 | "8C/A/N/AD/V/,/N/N/AD/V",
608 | "8D/虽/无/N/N/zhi/V/,/亦/V/以/AD/V/A/N/P",
609 | "8D/N/V/N/为/N/,/N/V/N/为/N/P",
610 | "8D/故/V/A/N/者/,/当/V/A/N/之/A/N/P",
611 | "8D/N/V/于/A/N/之上/,/AD/V/于/A/N/之间/P",
612 | "8C/使/其/A/N/AD/V/,/A/N/AD/V/P",
613 | "9D/A/N/V/zhi/而不/V/zhi/、亦/make/A/N/er/复/V/A/N/ye/P",
614 | "9B/N/MV/V/N/V/V/,/but/N/N/AD/V",
615 | "9D/以/N/,/当/V/A/N/,/非/N/V/N/所/MV/AD/V/P",
616 | "9C/此/N/有/A/N/A/N/,/A/N/A/N/P",
617 | "9B/是/N/ye/,/N/A/N/A/,/N/A/N/A/P"
618 | ]
619 | }
620 | }
621 |
622 | // 句式模板语法
623 |
624 | // e.g. 3B/初,/N/V/by/P
625 |
626 | // 3 -> 载荷数量
627 | // "/" ->语素分隔符
628 | // N->名词 V->动词 A->形容词 AD->副词
629 |
630 | // B -> 一般句式
631 | // C -> 四字骈文句式
632 | // D -> 逻辑句式
633 | // E-> 既是四字句式,又有逻辑
634 |
635 | //P 句号
636 | //Q 问号
637 | //R 冒号和引号
638 | //依需要添加在句式末尾,代替原有逗号。
639 |
640 | // by... -> 虚词
641 | // 其他->不做修改/替换
642 |
--------------------------------------------------------------------------------
/src/javascript/mapping_next.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 SheepChef (a.k.a. Haruka Hokuto)
3 | *
4 | * 这是一个自由软件。
5 | * 在遵守AIPL-1.1许可证的前提下,
6 | * 你可以自由复制,修改,分发,使用它。
7 | *
8 | * 查阅 Academic Innovation Protection License(AIPL) 来了解更多 .
9 | * 本作品应随附一份完整的 AIPL-1.1 许可证全文。
10 | *
11 | */
12 |
13 | import { expect, test } from "vitest";
14 | import { WenyanSimulator } from "./ChineseMappingHelper";
15 |
16 | const Map = new WenyanSimulator(" ").Map;
17 |
18 | function Check(Map2) {
19 | const Map_Obj = JSON.parse(Map2);
20 |
21 | const LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
22 | var LETTERS_ROUND_1 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
23 | var LETTERS_ROUND_2 = "FbPoDRStyJKAUcdahfVXlqwnOGpHZejzvmrBCigQILxkYMuWTEsN";
24 | //手动随机打乱的乱序轮
25 | var LETTERS_ROUND_3 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
26 | const BIG_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
27 | const NUMBERS = "1234567890";
28 | const SYMBOLS = "+/=";
29 | const NUMBERSYMBOL = "0123456789+/=";
30 | let ErrorOccur = false;
31 |
32 | let DecodeTable = {};
33 | let PayloadLetter = "";
34 | for (let i = 0; i < 52; i++) {
35 | DecodeTable[LETTERS[i]] = [];
36 | DecodeTable[LETTERS[i]].push(
37 | Map_Obj["Actual"]["N"]["alphabet"][LETTERS[i]]
38 | );
39 | DecodeTable[LETTERS[i]].push(
40 | Map_Obj["Actual"]["A"]["alphabet"][LETTERS[i]]
41 | );
42 | DecodeTable[LETTERS[i]].push(
43 | Map_Obj["Actual"]["V"]["alphabet"][LETTERS[i]]
44 | );
45 | PayloadLetter =
46 | PayloadLetter + Map_Obj["Actual"]["N"]["alphabet"][LETTERS[i]];
47 | PayloadLetter =
48 | PayloadLetter + Map_Obj["Actual"]["A"]["alphabet"][LETTERS[i]];
49 | PayloadLetter =
50 | PayloadLetter + Map_Obj["Actual"]["V"]["alphabet"][LETTERS[i]];
51 | if (
52 | Map_Obj["Actual"]["A"]["alphabet"][LETTERS[i]] !=
53 | Map_Obj["Actual"]["AD"]["alphabet"][LETTERS[i]]
54 | ) {
55 | DecodeTable[LETTERS[i]].push(
56 | Map_Obj["Actual"]["AD"]["alphabet"][LETTERS[i]]
57 | );
58 | PayloadLetter =
59 | PayloadLetter + Map_Obj["Actual"]["AD"]["alphabet"][LETTERS[i]];
60 | }
61 | }
62 | for (let i = 0; i < 13; i++) {
63 | DecodeTable[NUMBERSYMBOL[i]] = [];
64 | DecodeTable[NUMBERSYMBOL[i]].push(
65 | Map_Obj["Actual"]["N"]["numbersymbol"][NUMBERSYMBOL[i]]
66 | );
67 | DecodeTable[NUMBERSYMBOL[i]].push(
68 | Map_Obj["Actual"]["A"]["numbersymbol"][NUMBERSYMBOL[i]]
69 | );
70 | DecodeTable[NUMBERSYMBOL[i]].push(
71 | Map_Obj["Actual"]["V"]["numbersymbol"][NUMBERSYMBOL[i]]
72 | );
73 | PayloadLetter =
74 | PayloadLetter + Map_Obj["Actual"]["N"]["numbersymbol"][NUMBERSYMBOL[i]];
75 | PayloadLetter =
76 | PayloadLetter + Map_Obj["Actual"]["A"]["numbersymbol"][NUMBERSYMBOL[i]];
77 | PayloadLetter =
78 | PayloadLetter + Map_Obj["Actual"]["V"]["numbersymbol"][NUMBERSYMBOL[i]];
79 | if (
80 | Map_Obj["Actual"]["A"]["numbersymbol"][NUMBERSYMBOL[i]] !=
81 | Map_Obj["Actual"]["AD"]["numbersymbol"][NUMBERSYMBOL[i]]
82 | ) {
83 | DecodeTable[NUMBERSYMBOL[i]].push(
84 | Map_Obj["Actual"]["AD"]["numbersymbol"][NUMBERSYMBOL[i]]
85 | );
86 | PayloadLetter =
87 | PayloadLetter +
88 | Map_Obj["Actual"]["AD"]["numbersymbol"][NUMBERSYMBOL[i]];
89 | }
90 | }
91 |
92 | for (let i = 0; i < 3; i++) {
93 | //第一重循环,选择Payload
94 |
95 | if (i == 0) {
96 | //在Begin句式库中选择。
97 | for (let c = 0; c < Map_Obj["Sentences"]["Begin"].length; c++) {
98 | //开始选择句式
99 | let Sentence = Map_Obj["Sentences"]["Begin"][c].split("/");
100 | //Sentence是列表,按照/分割的句式
101 | let CorrectPayload = parseInt(Sentence[0]);
102 | let ActualPayload = 0;
103 | if (
104 | Sentence[0][1] != "B" &&
105 | Sentence[0][1] != "C" &&
106 | Sentence[0][1] != "D" &&
107 | Sentence[0][1] != "E"
108 | ) {
109 | ErrorOccur = true;
110 | console.warn(
111 | "Incorrect Begin Sentence:" + Map_Obj["Sentences"]["Begin"][c]
112 | );
113 | }
114 | for (let d = 1; d < Sentence.length; d++) {
115 | if (
116 | Sentence[d] == "A" ||
117 | Sentence[d] == "AD" ||
118 | Sentence[d] == "V" ||
119 | Sentence[d] == "N"
120 | ) {
121 | ActualPayload++;
122 | } else {
123 | if (
124 | PayloadLetter.indexOf(Sentence[d]) != -1 &&
125 | Sentence[d] != "Q" &&
126 | Sentence[d] != "P"
127 | ) {
128 | ErrorOccur = true;
129 | console.warn(
130 | "Incorrect Begin Sentence:" +
131 | Map_Obj["Sentences"]["Begin"][c] +
132 | " || " +
133 | Sentence[d]
134 | );
135 | } else if (Sentence[d].length > 1) {
136 | let temp = Array.from(Sentence[d]);
137 | temp.forEach((item) => {
138 | if (PayloadLetter.indexOf(item) != -1) {
139 | ErrorOccur = true;
140 | console.warn(
141 | "Incorrect Begin Sentence:" +
142 | Map_Obj["Sentences"]["Begin"][c] +
143 | " || " +
144 | Sentence[d]
145 | );
146 | }
147 | });
148 | }
149 | }
150 | }
151 | if (ActualPayload != CorrectPayload) {
152 | ErrorOccur = true;
153 | console.warn(
154 | "Incorrect Begin Sentence:" + Map_Obj["Sentences"]["Begin"][c]
155 | );
156 | }
157 | }
158 | } else if (i == 1) {
159 | for (let c = 0; c < Map_Obj["Sentences"]["Main"].length; c++) {
160 | //开始选择句式
161 | let Sentence = Map_Obj["Sentences"]["Main"][c].split("/");
162 | //Sentence是列表,按照/分割的句式
163 | let CorrectPayload = parseInt(Sentence[0]);
164 | let ActualPayload = 0;
165 | if (
166 | Sentence[0][1] != "B" &&
167 | Sentence[0][1] != "C" &&
168 | Sentence[0][1] != "D" &&
169 | Sentence[0][1] != "E"
170 | ) {
171 | ErrorOccur = true;
172 | console.warn(
173 | "Incorrect Main Sentence:" + Map_Obj["Sentences"]["Main"][c]
174 | );
175 | }
176 | for (let d = 1; d < Sentence.length; d++) {
177 | if (
178 | Sentence[d] == "A" ||
179 | Sentence[d] == "AD" ||
180 | Sentence[d] == "V" ||
181 | Sentence[d] == "N"
182 | ) {
183 | ActualPayload++;
184 | } else {
185 | if (
186 | PayloadLetter.indexOf(Sentence[d]) != -1 &&
187 | Sentence[d] != "Q" &&
188 | Sentence[d] != "P" &&
189 | Sentence[d] != "R"
190 | ) {
191 | ErrorOccur = true;
192 | console.warn(
193 | "Incorrect Main Sentence:" +
194 | Map_Obj["Sentences"]["Main"][c] +
195 | " || " +
196 | Sentence[d]
197 | );
198 | } else if (Sentence[d].length > 1) {
199 | let temp = Array.from(Sentence[d]);
200 | temp.forEach((item) => {
201 | if (PayloadLetter.indexOf(item) != -1) {
202 | ErrorOccur = true;
203 | console.warn(
204 | "Incorrect Main Sentence:" +
205 | Map_Obj["Sentences"]["Main"][c] +
206 | " || " +
207 | Sentence[d]
208 | );
209 | }
210 | });
211 | }
212 | }
213 | }
214 |
215 | if (ActualPayload != CorrectPayload) {
216 | ErrorOccur = true;
217 | console.warn(
218 | "Incorrect Main Sentence:" + Map_Obj["Sentences"]["Main"][c]
219 | );
220 | }
221 | }
222 | } else if (i == 2) {
223 | for (let c = 0; c < Map_Obj["Sentences"]["End"].length; c++) {
224 | //开始选择句式
225 | let Sentence = Map_Obj["Sentences"]["End"][c].split("/");
226 | //Sentence是列表,按照/分割的句式
227 | let CorrectPayload = parseInt(Sentence[0]);
228 | let ActualPayload = 0;
229 | if (
230 | Sentence[0][1] != "B" &&
231 | Sentence[0][1] != "C" &&
232 | Sentence[0][1] != "D" &&
233 | Sentence[0][1] != "E"
234 | ) {
235 | ErrorOccur = true;
236 | console.warn(
237 | "Incorrect End Sentence:" + Map_Obj["Sentences"]["End"][c]
238 | );
239 | }
240 | for (let d = 1; d < Sentence.length; d++) {
241 | if (
242 | Sentence[d] == "A" ||
243 | Sentence[d] == "AD" ||
244 | Sentence[d] == "V" ||
245 | Sentence[d] == "N"
246 | ) {
247 | ActualPayload++;
248 | } else {
249 | if (
250 | PayloadLetter.indexOf(Sentence[d]) != -1 &&
251 | Sentence[d] != "Q" &&
252 | Sentence[d] != "P"
253 | ) {
254 | ErrorOccur = true;
255 | console.warn(
256 | "Incorrect End Sentence:" +
257 | Map_Obj["Sentences"]["End"][c] +
258 | " || " +
259 | Sentence[d]
260 | );
261 | } else if (Sentence[d].length > 1) {
262 | let temp = Array.from(Sentence[d]);
263 | temp.forEach((item) => {
264 | if (PayloadLetter.indexOf(item) != -1) {
265 | ErrorOccur = true;
266 | console.warn(
267 | "Incorrect End Sentence:" +
268 | Map_Obj["Sentences"]["End"][c] +
269 | " || " +
270 | Sentence[d]
271 | );
272 | }
273 | });
274 | }
275 | }
276 | }
277 | if (ActualPayload != CorrectPayload) {
278 | ErrorOccur = true;
279 | console.warn(
280 | "Incorrect End Sentence:" + Map_Obj["Sentences"]["End"][c]
281 | );
282 | }
283 | }
284 | }
285 | }
286 | if (ErrorOccur) {
287 | return false;
288 | } else {
289 | return "Sentence Verified";
290 | }
291 | }
292 |
293 | test("句式合法性", () => {
294 | expect(Check(Map)).toBe("Sentence Verified");
295 | });
296 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | /// <reference types="vitest/config" />
2 | import { defineConfig } from "vite";
3 | import fs from "fs";
4 | import path from "path";
5 |
6 | let outDir = "";
7 | let configStore;
8 |
9 | export default defineConfig({
10 | // 配置选项
11 | test: {
12 | coverage: {
13 | exclude: [
14 | "vite.config.js",
15 | "dist",
16 | "JavyInputAppendix.js",
17 | "src/javascript/unishox2.js",
18 | "docs",
19 | "src/javascript/main.d.ts",
20 | "src/javascript/utils.js",
21 | "src/javascript/utils_next.js",
22 | ],
23 | },
24 | },
25 | build: {
26 | outDir: "dist", // 将打包后的文件输出到 dist 目录
27 | minify: "eslint", // 使用 terser 进行压缩
28 | lib: {
29 | entry: "./src/javascript/main.js",
30 | name: "abracadabra-cn",
31 | fileName: "abracadabra-cn",
32 | },
33 | },
34 | plugins: [
35 | {
36 | name: "Abracadabra-Javy-Artifact",
37 | apply: "build", // 只在生产构建时生效
38 | configResolved(config) {
39 | // 保存最终输出目录路径
40 | outDir = path.resolve(config.root, config.build.outDir);
41 | configStore = config;
42 | },
43 | async writeBundle() {
44 | // 自定义内容(这里可以修改为你需要追加的内容)
45 | const AppendContentPath = path.join(
46 | configStore.root,
47 | "JavyInputAppendix.js"
48 | );
49 | const appendContent = fs.readFileSync(AppendContentPath, "utf8");
50 |
51 | const TargetContentPath = path.join(outDir, "abracadabra-cn.js");
52 | const TargetContent = fs.readFileSync(TargetContentPath, "utf8");
53 | //创建全新文件
54 | const newFilePath = path.join(outDir, "abracadabra-cn-javy.js");
55 | fs.writeFileSync(newFilePath, `${TargetContent}${appendContent}`);
56 |
57 | // 复制 TypeScript 声明文件到输出目录
58 | const dtsSourcePath = path.join(
59 | configStore.root,
60 | "src/javascript/main.d.ts"
61 | );
62 | const dtsTargetPath = path.join(outDir, "abracadabra-cn.d.ts");
63 | fs.copyFileSync(dtsSourcePath, dtsTargetPath);
64 | },
65 | },
66 | ],
67 | });
68 |
--------------------------------------------------------------------------------