├── .eslintignore ├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .prettierrc.js ├── .stylelintrc.js ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── commitlint.config.js ├── docs ├── .vitepress │ ├── config.mts │ └── theme │ │ ├── GLayout.vue │ │ ├── index.ts │ │ └── style.css ├── development │ └── getting-started.md ├── en │ └── index.md ├── getting-started.md ├── index.md ├── parts │ ├── screenshot.md │ └── wip.md ├── public │ ├── logo.png │ ├── logo.webp │ ├── qq_qr.png │ ├── screenshot │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 17.png │ │ ├── 18.png │ │ ├── 19.png │ │ ├── 2.png │ │ ├── 20.png │ │ ├── 21.png │ │ ├── 22.png │ │ ├── 23.png │ │ ├── 24.png │ │ ├── 25.png │ │ ├── 26.png │ │ ├── 27.png │ │ ├── 28.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ └── team │ │ ├── duchuanbo.jpg │ │ ├── kongqi.jpg │ │ ├── tiger.jpg │ │ └── 吉王义昊.webp ├── team.md └── tutorial │ ├── basic.md │ ├── community.md │ ├── concepts.md │ ├── functions.md │ └── setting.md ├── husky.sh ├── package-lock.json ├── package.json ├── quickapp.config.js ├── src ├── app.ux ├── common │ ├── css │ │ └── page.css │ ├── icon │ │ ├── author.png │ │ ├── back.png │ │ ├── book-code.png │ │ ├── catalog.png │ │ ├── check-active.png │ │ ├── check.png │ │ ├── close.png │ │ ├── code.png │ │ ├── down.png │ │ ├── explore-active.png │ │ ├── explore.png │ │ ├── group.png │ │ ├── help.png │ │ ├── history.png │ │ ├── home-active.png │ │ ├── home.png │ │ ├── info.png │ │ ├── latest.png │ │ ├── left-double.png │ │ ├── left.png │ │ ├── library.png │ │ ├── magic.png │ │ ├── menu.png │ │ ├── password.png │ │ ├── refresh.png │ │ ├── right-double.png │ │ ├── right.png │ │ ├── search.png │ │ ├── setting.png │ │ ├── source.png │ │ ├── submit.png │ │ ├── trash.png │ │ ├── ui.png │ │ ├── up.png │ │ ├── user-active.png │ │ ├── user.png │ │ └── web.png │ ├── image │ │ ├── bookcover.png │ │ ├── cover.jpg │ │ ├── qr-feed.png │ │ ├── qr-github.png │ │ └── qr-qq.png │ └── logo │ │ ├── 192-round.png │ │ └── 96-round.png ├── config-watch.json ├── i18n │ ├── defaults.json │ ├── en.json │ └── zh-CN.json ├── manifest.json ├── pages │ ├── about │ │ └── about.ux │ ├── catalog │ │ └── catalog.ux │ ├── content │ │ └── content.ux │ ├── detail │ │ └── detail.ux │ ├── index │ │ └── index.ux │ ├── search │ │ └── search.ux │ ├── searchResult │ │ └── searchResult.ux │ ├── setting │ │ └── setting.ux │ └── source │ │ └── source.ux ├── third-party │ └── JSONPath │ │ └── jsonpath.ts └── utils │ ├── book.ts │ ├── chapter.ts │ ├── cookie.ts │ ├── fetch.ts │ ├── index.ts │ ├── jsExtension.ts │ ├── source.ts │ └── tsimports.js └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | /.nyc_output 2 | /coverage 3 | /node_modules 4 | /tests/fixtures 5 | /sign 6 | /dist 7 | /build 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [22.x] 17 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'npm' 26 | - run: git submodule init && git submodule update 27 | - run: npm i 28 | - run: npm run build 29 | 30 | - name: upload artifact 31 | uses: actions/upload-artifact@v4 32 | with: 33 | path: dist/*.rpk 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | 35 | - name: Setup Pages 36 | uses: actions/configure-pages@v5 37 | 38 | - name: Build VitePress 39 | run: | 40 | npm install 41 | npm run docs:build 42 | shell: bash 43 | 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v3 46 | with: 47 | # Upload entire repository 48 | path: "./docs/.vitepress/dist" 49 | 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.nyc_output 2 | /coverage 3 | /node_modules 4 | /sign 5 | /dist 6 | /build 7 | .husky/ 8 | docs/.vitepress/dist 9 | docs/.vitepress/cache 10 | .idea 11 | src/utils/test.ts 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/third-party/Vela_input_method"] 2 | path = src/third-party/Vela_input_method 3 | url = https://github.com/NEORUAA/Vela_input_method.git 4 | [submodule "src/third-party/GBK.js"] 5 | path = src/third-party/GBK.js 6 | url = https://github.com/cnwhy/GBK.js.git 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | 5 | .DS_Store 6 | Thumbs.db 7 | 8 | *.log 9 | *.iml 10 | .idea/ 11 | .vscode/ 12 | 13 | .nyc_output 14 | /coverage 15 | 16 | /logs 17 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, // 指定代码长度,超出换行 3 | tabWidth: 2, // tab 键的宽度 4 | useTabs: false, // 使用空格替代tab 5 | semi: false, // 结尾加上分号 6 | singleQuote: false, // 使用单引号 7 | quoteProps: "consistent", // 要求对象字面量属性是否使用引号包裹,(‘as-needed’: 没有特殊要求,禁止使用,'consistent': 保持一致 , preserve: 不限制,想用就用) 8 | trailingComma: "none", // 不添加对象和数组最后一个元素的逗号 9 | bracketSpacing: false, // 对象中对空格和空行进行处理 10 | jsxBracketSameLine: false, // 在多行JSX元素的最后一行追加 > 11 | requirePragma: false, // 是否严格按照文件顶部的特殊注释格式化代码 12 | insertPragma: false, // 是否在格式化的文件顶部插入Pragma标记,以表明该文件被prettier格式化过了 13 | proseWrap: "preserve", // 按照文件原样折行 14 | htmlWhitespaceSensitivity: "ignore", // html文件的空格敏感度,控制空格是否影响布局 15 | endOfLine: "auto", // 结尾是 \n \r \n\r auto 16 | overrides: [ 17 | { 18 | files: "*.ux", 19 | options: {parser: "vue"} 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "stylelint-config-standard", 4 | "stylelint-config-recess-order" 5 | // "stylelint-selector-bem-pattern" 6 | ], 7 | ignoreFiles: ["node_modules", "test", "dist", "**/*.js"], 8 | rules: { 9 | "no-descending-specificity": null, 10 | "color-hex-case": "lower", 11 | "color-hex-length": "short", 12 | "at-rule-no-unknown": null, 13 | "block-no-empty": null, 14 | "selector-pseudo-class-no-unknown": [ 15 | true, 16 | { 17 | ignorePseudoClasses: ["blur"] 18 | } 19 | ], 20 | "property-no-unknown": [ 21 | true, 22 | { 23 | ignoreProperties: [ 24 | "placeholder-color", 25 | "gradient-start", 26 | "gradient-center", 27 | "gradient-end", 28 | "caret-color", 29 | "selected-color", 30 | "block-color" 31 | ] 32 | } 33 | ], 34 | "max-line-length": null, 35 | // "indentation": 2, 36 | // "no-empty-source": null, 37 | "selector-type-no-unknown": [ 38 | true, 39 | { 40 | ignoreTypes: ["selected-color", "block-color"] 41 | } 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Codegeex.CommitMessageStyle": "ConventionalCommits", 3 | "Codegeex.GenerationPreference": "line by line" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lordly·阅读 2 | 3 |
4 | Lordly·阅读 5 | 6 | Lordly·阅读 7 |
8 | read.lordly.vip 9 |
10 | 支持「开源阅读」规则的手环在线阅读工具 11 |
12 | 13 | [![GitHub License](https://img.shields.io/github/license/Lordly-Tech/LordlyRead)](https://github.com/Lordly-Tech/LordlyRead/blob/main/LICENSE) 14 | [![wakatime](https://wakatime.com/badge/user/7ef6dafe-00d7-45e3-b4c7-27b1f5d4d735/project/47bae092-8953-4da1-a089-e75e4ec13996.svg)](https://wakatime.com/badge/user/7ef6dafe-00d7-45e3-b4c7-27b1f5d4d735/project/47bae092-8953-4da1-a089-e75e4ec13996) 15 | [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Lordly-Tech/LordlyRead/.github%2Fworkflows%2Fdeploy.yml)](https://github.com/Lordly-Tech/LordlyRead/actions) 16 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/w/Lordly-Tech/LordlyRead)](https://github.com/Lordly-Tech/LordlyRead/commits) 17 | [![GitHub Release](https://img.shields.io/github/v/release/Lordly-Tech/LordlyRead)](https://github.com/Lordly-Tech/LordlyRead/releases/latest) 18 | [![GitHub Release Date](https://img.shields.io/github/release-date/Lordly-Tech/LordlyRead)](https://github.com/Lordly-Tech/LordlyRead/releases/latest) 19 | [![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/Lordly-Tech/LordlyRead/total)](https://github.com/Lordly-Tech/LordlyRead/releases) 20 | 21 | 22 | > 新用户?请查看我们的[使用指南](https://read.lordly.vip/getting-started)。 23 | 24 | ## 主要功能 25 | 26 | 1. 自定义书源,自己设置规则,抓取网页数据,规则简单易懂,官网内有开发指南(WIP)。 27 | 2. (WIP)列表书架,网格书架自由切换。 28 | 3. 书源规则支持搜索及发现(WIP),所有找书看书功能全部自定义,找书更方便。 29 | 4. (WIP)支持替换净化,去除广告替换内容很方便。 30 | 5. (WIP)支持本地TXT、EPUB阅读,手动浏览,智能扫描。 31 | 6. 支持高度自定义阅读界面,切换颜色、行距、段距、加粗等。 32 | 7. (WIP)支持多种翻页模式,覆盖、仿真、滑动、滚动等。 33 | 8. 软件开源,持续优化,无广告。 34 | 35 | ## 开源许可 36 | 37 | 本项目开发过程中很大程度上参考了「[开源阅读](https://github.com/gedoor/legado)」的设计和规则。在此对「开源阅读」的开发者表示感谢。 38 | 39 | 但本项目在开发过程中并未直接使用或参考「开源阅读」的具体实现(u1s1,看着头疼 www),因此本项目并不是「开源阅读」的分支或衍生项目,不会跟进「开源阅读」的更新,也不受「开源阅读」`GPL-3` 许可证的约束。 40 | 41 | 经过综合考量,本项目最终使用 `MPL-2.0` 许可证开源,这意味着您可以自由使用、修改、分发本项目的代码,但是您的衍生项目(中使用到本项目的部分以及对本项目的更改)必须使用相同的许可证开源(具体规则以 `MPL-2.0` 协议约束为准)。 42 | 43 | ## 软件截图 44 | 45 | 46 | 47 | ## 免责声明(修改自 [https://gedoor.github.io/Disclaimer](https://gedoor.github.io/Disclaimer)) 48 | 49 | 「Lordly·阅读」是一款提供网络文学搜索的工具,为广大网络文学爱好者提供一种方便、快捷舒适的试读体验。 50 | 51 | 当您搜索一本书的时,「Lordly·阅读」会将该书的书名以关键词的形式提交到各个第三方网络文学网站。各第三方网站返回的内与「Lordly·阅读」无关,「Lordly·阅读」对其概不负责,亦不承担任何法律责任。任何通过使用「Lordly·阅读」而链接到的第三方网页均系他人制作或供,您可能从第三方网页上获得其他服务,「Lordly·阅读」对其合法性概不负责,亦不承担任何法律责任。第三方搜索引擎结果据您提交的书名自动搜索获得并提供试读,不代表「Lordly·阅读」赞成或被搜索链接到的第三方网页上的内容或立场。您应该对用搜索引擎的结果自行承担风险。 52 | 53 | 「Lordly·阅读」不做任何形式的保证:不保证第三方搜索引擎的搜索结果满足您的要求,不保证搜索服务不中断,不保证搜索结的安全性、正确性、及时性、合法性。因网络状况、通讯线路、第三方网站等任何原因而导致您不能正常使用「Lordly·阅读」,读不承担任何法律责任。「Lordly·阅读」尊重并保护所有使用「Lordly·阅读」用户的个人隐私权,您注册的用户名、电子邮件地址等个人料,非经您亲自许可或根据相关法律、法规的强制性规定,「Lordly·阅读」不会主动地泄露给第三方。 54 | 55 | 「Lordly·阅读」致力于最大程度地减少网络文学阅读者在自行搜寻过程中的无意义的时间浪费,通过专业搜索展示不同网站中网文学的最新章节。阅读在为广大小说爱好者提供方便、快捷舒适的试读体验的同时,也使优秀网络文学得以迅速、更泛的传播,从而达到了在一定程度促进网络文学充分繁荣发展之目的。阅读鼓励广大小说爱好者通过阅读发现优秀网小说及其提供商,并建议阅读正版图书。 56 | 57 | 任何单位或个人认为通过阅读搜索链接到的第三方网页内容可能涉嫌侵犯其信息网络传播权,应该及时向阅读提出书权力通知,并提供身份证明、权属证明及详细侵权情况证明。阅读在收到上述法律文件后,将会依法尽快断开相关链内容。 58 | 59 | ## 联系我们 60 | 61 | ### QQ 交流群(扫描或点击下方二维码快捷加入) 62 | 63 | [![QQ 群二维码](/public/qq_qr.png)](https://qm.qq.com/q/s94RXV7eHC) 64 | 65 | ### GitHub Discussions 66 | 67 | GitHub Discussions 地址:https://github.com/Lordly-Tech/LordlyRead/discussions 68 | 69 | ## ✨ Stargazers over time 70 | 71 | 感谢大家的支持! 72 | 73 | [![Stargazers over time](https://starchart.cc/Lordly-Tech/LordlyRead.svg?variant=adaptive)](https://starchart.cc/Lordly-Tech/LordlyRead) 74 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "type-enum": [ 5 | 2, 6 | "always", 7 | [ 8 | "bug", // 此项特别针对bug号,用于向测试反馈bug列表的bug修改情况 9 | "feat", // 新功能(feature) 10 | "fix", // 修补bug 11 | "docs", // 文档(documentation) 12 | "style", // 格式(不影响代码运行的变动) 13 | "refactor", // 重构(即不是新增功能,也不是修改bug的代码变动) 14 | "test", // 增加测试 15 | "chore", // 构建过程或辅助工具的变动 16 | "revert", // feat(pencil): add ‘graphiteWidth’ option (撤销之前的commit) 17 | "merge" // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址 18 | ] 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from "vitepress" 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | title: "Lordly·阅读", 6 | description: "支持「开源阅读」规则的手环在线阅读工具", 7 | head: [["link", {rel: "icon", href: "/logo.png", type: "image/png"}]], 8 | themeConfig: { 9 | // https://vitepress.dev/reference/default-theme-config 10 | nav: [ 11 | {text: "首页", link: "/"}, 12 | {text: "使用指南", link: "/getting-started"}, 13 | {text: "团队", link: "/team"} 14 | ], 15 | 16 | sidebar: [ 17 | { 18 | text: "使用指南", 19 | items: [ 20 | {text: "简介&下载", link: "/getting-started"}, 21 | {text: "基础教程", link: "/tutorial/basic"}, 22 | {text: "概念介绍", link: "/tutorial/concepts"}, 23 | {text: "主要功能及界面介绍", link: "/tutorial/functions"}, 24 | {text: "设置及自定义", link: "/tutorial/setting"}, 25 | {text: "主要社区 & 反馈渠道", link: "/tutorial/community"} 26 | ] 27 | } 28 | ], 29 | 30 | socialLinks: [{icon: "github", link: "https://github.com/Lordly-Tech/LordlyRead"}], 31 | 32 | footer: { 33 | message: "Released under the MPL-2.0 license", 34 | copyright: "Copyright © 2024-present, Jiwang Yihao (as part of the Lordly Tech Team)" 35 | }, 36 | 37 | logo: "logo.png", 38 | 39 | editLink: { 40 | pattern: "https://github.com/Lordly-Tech/LordlyRead/edit/main/docs/:path", 41 | text: "在 GitHub 上编辑此页面" 42 | } 43 | }, 44 | 45 | locales: { 46 | root: { 47 | label: "简体中文", 48 | lang: "zh-CN" 49 | }, 50 | en: { 51 | label: "English", 52 | lang: "en", 53 | 54 | title: "Lordly·Read", 55 | description: "Supports Legado rule band reading tool", 56 | 57 | themeConfig: { 58 | nav: [ 59 | {text: "Home", link: "/"}, 60 | {text: "Getting Started", link: "/getting-started"} 61 | ], 62 | sidebar: [ 63 | { 64 | text: "Getting Started" 65 | }, 66 | { 67 | text: "Runtime API Examples" 68 | } 69 | ], 70 | 71 | logo: "logo.png" 72 | } 73 | } 74 | }, 75 | 76 | lastUpdated: true, 77 | cleanUrls: true 78 | }) 79 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/GLayout.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 53 | 54 | 79 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // https://vitepress.dev/guide/custom-theme 2 | import type {Theme} from "vitepress" 3 | import DefaultTheme from "vitepress/theme" 4 | import "./style.css" 5 | import "element-plus/dist/index.css" 6 | import GLayout from "./GLayout.vue" 7 | 8 | export default { 9 | extends: DefaultTheme, 10 | Layout: GLayout, 11 | enhanceApp({app, router, siteData}) { 12 | // ... 13 | } 14 | } satisfies Theme 15 | -------------------------------------------------------------------------------- /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 attched 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: #5c74ac; 53 | --vp-c-brand-2: #5c74ac; 54 | --vp-c-brand-3: #5c74ac; 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 | /** 90 | * Component: Home 91 | * -------------------------------------------------------------------------- */ 92 | 93 | :root { 94 | --vp-home-hero-name-color: transparent; 95 | --vp-home-hero-name-background: -webkit-linear-gradient( 96 | 120deg, 97 | #efc9f0 30%, 98 | #b1bff0 99 | ); 100 | 101 | --vp-home-hero-image-background-image: linear-gradient( 102 | -45deg, 103 | #eda3f0 50%, 104 | #738ef0 50% 105 | ); 106 | --vp-home-hero-image-filter: blur(44px); 107 | } 108 | 109 | @media (min-width: 640px) { 110 | :root { 111 | --vp-home-hero-image-filter: blur(56px); 112 | } 113 | } 114 | 115 | @media (min-width: 960px) { 116 | :root { 117 | --vp-home-hero-image-filter: blur(68px); 118 | } 119 | } 120 | 121 | /** 122 | * Component: Custom Block 123 | * -------------------------------------------------------------------------- */ 124 | 125 | :root { 126 | --vp-custom-block-tip-border: transparent; 127 | --vp-custom-block-tip-text: var(--vp-c-text-1); 128 | --vp-custom-block-tip-bg: var(--vp-c-brand-soft); 129 | --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); 130 | } 131 | 132 | /** 133 | * Component: Algolia 134 | * -------------------------------------------------------------------------- */ 135 | 136 | .DocSearch { 137 | --docsearch-primary-color: var(--vp-c-brand-1) !important; 138 | } 139 | 140 | 141 | 142 | .clip { 143 | filter: brightness(0.95); 144 | } 145 | 146 | .dark .clip { 147 | filter: none; 148 | } -------------------------------------------------------------------------------- /docs/development/getting-started.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/development/getting-started.md -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Lordly·Read" 7 | text: "Supports Legado rule band reading tool" 8 | tagline: Open source project, welcome to PR or Issue 9 | actions: 10 | - theme: brand 11 | text: Getting Started 12 | link: /getting-started 13 | - theme: alt 14 | text: View on GitHub 15 | link: https://github.com/Lordly-Tech/LordlyRead 16 | image: 17 | src: /logo.png 18 | alt: Lordly·Read Logo 19 | 20 | features: 21 | - title: 📖Intelligent Source 22 | details: A rule-based Legado reader prioritizing compatibility and customization. It fetches and filters content, simplifying management and creating a personal library. 23 | - title: 🌈Visual Experience 24 | details: The interface design combines aesthetics and practicality with clean lines and harmonious colors. Attention to detail blends technology and art, offering an immersive reading experience. 25 | - title: ⚙️Customization 26 | details: The reading tool supports extensive customization, letting you create a unique environment for an enjoyable, tailored reading experience. 27 | --- 28 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # 简介&下载 2 | 3 | ::: warning 4 | Lordly·阅读为免费应用,其余一切收费或附带广告皆为盗版!请勿上当受骗!!! 5 | ::: 6 | 7 | ## 主要功能 8 | 9 | 1. 自定义书源,自己设置规则,抓取网页数据,规则简单易懂,官网内有开发指南(WIP)。 10 | 2. (WIP)列表书架,网格书架自由切换。 11 | 3. 书源规则支持搜索及发现(WIP),所有找书看书功能全部自定义,找书更方便。 12 | 4. (WIP)支持替换净化,去除广告替换内容很方便。 13 | 5. (WIP)支持本地TXT、EPUB阅读,手动浏览,智能扫描。 14 | 6. 支持高度自定义阅读界面,切换颜色、行距、段距、加粗等。 15 | 7. (WIP)支持多种翻页模式,覆盖、仿真、滑动、滚动等。 16 | 8. 软件开源,持续优化,无广告。 17 | 18 | ## 软件截图 19 | 20 | 21 | 22 | ## 下载 23 | 24 | - [GitHub](https://github.com/Lordly-Tech/LordlyRead/releases) 25 | - More coming soon... 26 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Lordly·阅读" 7 | text: "支持「开源阅读」规则的手环在线阅读工具" 8 | tagline: 本应用为开源项目,欢迎提交 PR 或 Issue 9 | actions: 10 | - theme: brand 11 | text: 开始了解 12 | link: /getting-started 13 | - theme: alt 14 | text: 在 GitHub 上查看 15 | link: https://github.com/Lordly-Tech/LordlyRead 16 | image: 17 | src: /logo.png 18 | alt: Lordly·阅读 Logo 19 | 20 | features: 21 | - title: 📖智能书源 22 | details: 兼容与特色并重的自定义书源功能,智能抓取和筛选所需内容,快速管理各类资源,轻松打造专属您的个性化阅读库。 23 | - title: 🌈视觉体验 24 | details: 界面设计注重美学与实用性的结合,简洁线条和柔和色彩搭配,对细节的极致追求,让您感受科技与艺术的完美融合。 25 | - title: ⚙️定制体验 26 | details: 阅读工具支持高度个性化定制功能,打造独一无二的阅读环境,满足不同审美需求,让每次阅读成为一种独特的享受。 27 | --- 28 | 29 | ## 图片展示 30 | 31 | -------------------------------------------------------------------------------- /docs/parts/screenshot.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/parts/wip.md: -------------------------------------------------------------------------------- 1 | ::: danger 注意 2 | 此页面尚未完工,欢迎您加入贡献。GitHub 地址:https://github.com/Lordly-Tech/LordlyRead 3 | 4 | 您也可以点击页面底部的「在 GitHub 上编辑此页面」前往 GitHub 查看对应文件。 5 | ::: -------------------------------------------------------------------------------- /docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/logo.png -------------------------------------------------------------------------------- /docs/public/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/logo.webp -------------------------------------------------------------------------------- /docs/public/qq_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/qq_qr.png -------------------------------------------------------------------------------- /docs/public/screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/1.png -------------------------------------------------------------------------------- /docs/public/screenshot/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/10.png -------------------------------------------------------------------------------- /docs/public/screenshot/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/11.png -------------------------------------------------------------------------------- /docs/public/screenshot/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/12.png -------------------------------------------------------------------------------- /docs/public/screenshot/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/13.png -------------------------------------------------------------------------------- /docs/public/screenshot/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/14.png -------------------------------------------------------------------------------- /docs/public/screenshot/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/15.png -------------------------------------------------------------------------------- /docs/public/screenshot/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/16.png -------------------------------------------------------------------------------- /docs/public/screenshot/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/17.png -------------------------------------------------------------------------------- /docs/public/screenshot/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/18.png -------------------------------------------------------------------------------- /docs/public/screenshot/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/19.png -------------------------------------------------------------------------------- /docs/public/screenshot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/2.png -------------------------------------------------------------------------------- /docs/public/screenshot/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/20.png -------------------------------------------------------------------------------- /docs/public/screenshot/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/21.png -------------------------------------------------------------------------------- /docs/public/screenshot/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/22.png -------------------------------------------------------------------------------- /docs/public/screenshot/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/23.png -------------------------------------------------------------------------------- /docs/public/screenshot/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/24.png -------------------------------------------------------------------------------- /docs/public/screenshot/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/25.png -------------------------------------------------------------------------------- /docs/public/screenshot/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/26.png -------------------------------------------------------------------------------- /docs/public/screenshot/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/27.png -------------------------------------------------------------------------------- /docs/public/screenshot/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/28.png -------------------------------------------------------------------------------- /docs/public/screenshot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/3.png -------------------------------------------------------------------------------- /docs/public/screenshot/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/4.png -------------------------------------------------------------------------------- /docs/public/screenshot/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/5.png -------------------------------------------------------------------------------- /docs/public/screenshot/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/6.png -------------------------------------------------------------------------------- /docs/public/screenshot/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/7.png -------------------------------------------------------------------------------- /docs/public/screenshot/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/8.png -------------------------------------------------------------------------------- /docs/public/screenshot/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/9.png -------------------------------------------------------------------------------- /docs/public/team/duchuanbo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/duchuanbo.jpg -------------------------------------------------------------------------------- /docs/public/team/kongqi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/kongqi.jpg -------------------------------------------------------------------------------- /docs/public/team/tiger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/tiger.jpg -------------------------------------------------------------------------------- /docs/public/team/吉王义昊.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/吉王义昊.webp -------------------------------------------------------------------------------- /docs/team.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | hero: 4 | name: 团队成员 5 | tagline: Lordly Team & Contributors 6 | --- 7 | 8 | 38 | 39 | -------------------------------------------------------------------------------- /docs/tutorial/basic.md: -------------------------------------------------------------------------------- 1 | # 基础教程 2 | 3 | 4 | 5 | ::: warning 6 | 新人必看! 7 | ::: 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/tutorial/community.md: -------------------------------------------------------------------------------- 1 | # 主要社区 & 反馈渠道 2 | 3 | ## QQ 交流群(扫描或点击下方二维码快捷加入) 4 | 5 | [![QQ 群二维码](/public/qq_qr.png)](https://qm.qq.com/q/s94RXV7eHC) 6 | 7 | ## GitHub(推荐的反馈渠道) 8 | 9 | GitHub 地址:https://github.com/Lordly-Tech/LordlyRead 10 | 11 | ::: tip 12 | 在 GitHub 中提 Issue 可以让开发者更快了解问题(当然也顺手点下 star 吧)! 13 | 14 | 如果希望参与贡献,也欢迎提交 PR! 15 | ::: 16 | -------------------------------------------------------------------------------- /docs/tutorial/concepts.md: -------------------------------------------------------------------------------- 1 | # 概念介绍 2 | 3 | ## 书源 4 | 书源,简单理解就是书籍资源的网络来源。众所周知,网络上存在许许多多或收费或免费的读书网站。然而,这些网站往往存在或这样或那样的问题,比如广告繁多、比如排版字体欠佳,对手机优化很差等情况。书源就是通过特定的规则,在网络上抓取书籍章节、书籍内容,并干净整齐地呈现在手机屏幕上,优化阅读体验。与「开源阅读」相同,「Lordly·阅读」本身也不提供书源,用户可以自行编写或导入书源规则。 5 | 6 | ::: tip 7 | 目前,「Lordly·阅读」对「开源阅读」书源的支持还比较有限,但是我们会持续优化,让用户更加方便地使用「开源阅读」书源。 8 | 9 | 小白用户?不妨试试「Lordly·阅读」开发者维护的[吉的书源集](https://github.com/jiwangyihao/source-j-legado)(书源集中主要包含轻小说和漫画,在「Lordly·阅读」前期开发中主要使用这些书源进行兼容性测试,相关书源也进行了一定调整。已确认可以在「Lordly·阅读」使用的书源会标注导入码,另,点个 star 吧 orz)。 10 | 11 | 书源作者?请参考[开发指南](../development/getting-started)来了解「Lordly·阅读」中书源规则与「开源阅读」的主要区别,也欢迎您提交 PR 或 Issue 来帮助我们完善「Lordly·阅读」对「开源阅读」书源的支持。 12 | ::: 13 | 14 | ## 发现(WIP) 15 | 发现是书源的一部分。发现简单理解,就是把一些读书网站上的排行榜、书籍分类等信息抓取并呈现在手机屏幕上,方便用户书荒时找书。有的书源不带发现。 16 | 17 | ## 替换和净化规则(WIP) 18 | 网络上抓取的书籍正文里,可能会有一些广告词网址等类似的内容,比如“看小说请认准 xxx.com”等。替换和净化规则的作用就是替换或删除这些内容。让呈现在我们面前的书籍正文更加美观、整齐。 -------------------------------------------------------------------------------- /docs/tutorial/functions.md: -------------------------------------------------------------------------------- 1 | # 主要功能及界面介绍 2 | 3 | 4 | 5 | ## 主界面介绍 6 | 7 | 8 | ## 阅读界面介绍 9 | 10 | 11 | ## 导入书源 12 | 13 | 14 | ## 导入替换净化(WIP) 15 | 16 | -------------------------------------------------------------------------------- /docs/tutorial/setting.md: -------------------------------------------------------------------------------- 1 | # 设置及自定义 2 | 3 | 4 | 5 | 6 | ## 主界面 7 | 8 | 9 | ## 阅读界面 10 | 11 | 12 | ## 阅读设置 13 | 14 | 15 | ## 其他设置 -------------------------------------------------------------------------------- /husky.sh: -------------------------------------------------------------------------------- 1 | npx husky install 2 | npx husky add .husky/pre-commit 'npx lint-staged' 3 | npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}' -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lordly_read", 3 | "version": "1.0.0", 4 | "description": "", 5 | "engines": { 6 | "node": ">=8.10" 7 | }, 8 | "scripts": { 9 | "start": "aiot server --watch --open-nuttx --enable-custom-component true --enable-protobuf true --enable-image-png8", 10 | "build": "aiot build --enable-custom-component --enable-image-png8 --enable-jsc", 11 | "release": "aiot release --enable-custom-component --enable-image-png8 --enable-jsc", 12 | "watch": "aiot watch --open-nuttx", 13 | "lint": "eslint --format codeframe --fix --ext .ux,.js src/", 14 | "docs:dev": "vitepress dev docs", 15 | "docs:build": "vitepress build docs", 16 | "docs:preview": "vitepress preview docs" 17 | }, 18 | "lint-staged": { 19 | "*.{ux,js}": [ 20 | "prettier --write", 21 | "eslint --format codeframe --fix", 22 | "git add" 23 | ], 24 | "*.{less,css}": [ 25 | "prettier --write", 26 | "stylelint --fix --custom-syntax postcss-less", 27 | "git add" 28 | ] 29 | }, 30 | "devDependencies": { 31 | "@aiot-toolkit/jsc": "^1.0.7", 32 | "@aiot-toolkit/velasim": "^0.1.26", 33 | "@commitlint/cli": "^19.5.0", 34 | "@commitlint/config-conventional": "^19.5.0", 35 | "aiot-toolkit": "1.1.4", 36 | "babel-eslint": "^10.1.0", 37 | "buffer": "^6.0.3", 38 | "element-plus": "^2.8.6", 39 | "eslint": "^6.8.0", 40 | "eslint-config-prettier": "^6.15.0", 41 | "eslint-import-resolver-node": "^0.3.9", 42 | "eslint-plugin-import": "^2.31.0", 43 | "eslint-plugin-prettier": "^3.4.1", 44 | "eslint-plugin-ux": "^0.0.4", 45 | "husky": "^8.0.1", 46 | "less": "^4.2.0", 47 | "less-loader": "^12.2.0", 48 | "lint-staged": "^15.2.10", 49 | "postcss-html": "^1.7.0", 50 | "postcss-less": "^6.0.0", 51 | "prettier": "^3.3.3", 52 | "stylelint": "^16.9.0", 53 | "stylelint-config-recess-order": "^5.1.1", 54 | "stylelint-config-standard": "^36.0.1", 55 | "stylelint-order": "^6.0.4", 56 | "ts-loader": "^9.5.1", 57 | "typescript": "^5.6.2", 58 | "ux-types": "^0.1.1", 59 | "vitepress": "^1.3.4", 60 | "vue": "^3.5.11" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /quickapp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: { 3 | module: { 4 | rules: [ 5 | { 6 | test: /\.tsx?$/, 7 | use: [ 8 | { 9 | loader: "ts-loader" 10 | } 11 | ] 12 | } 13 | ] 14 | }, 15 | resolve: { 16 | alias: { 17 | vm: false 18 | } 19 | } 20 | }, 21 | cli: { 22 | "enable-custom-component": true, 23 | "enable-jsc": true, 24 | "enable-protobuf": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app.ux: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/common/css/page.css: -------------------------------------------------------------------------------- 1 | .page { 2 | color: #ffffff; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | } 7 | 8 | .icon { 9 | width: 32px; 10 | height: 32px; 11 | } 12 | 13 | .badge { 14 | font-size: 16px; 15 | padding: 2px 8px; 16 | border-radius: 16px; 17 | background-color: #333; 18 | font-weight: bold; 19 | } 20 | 21 | .badge-btn { 22 | padding: 8px 0; 23 | } 24 | 25 | .vertical-separator { 26 | height: 1px; 27 | width: 100%; 28 | background-color: rgba(255,255,255,0.5) 29 | } 30 | 31 | .horizontal-separator { 32 | width: 1px; 33 | height: 100%; 34 | background-color: rgba(255,255,255,0.5) 35 | } 36 | 37 | .topbar { 38 | width: 100%; 39 | align-items: center; 40 | } 41 | 42 | .topbar-btn { 43 | padding: 16px 32px; 44 | } 45 | 46 | .title { 47 | flex-grow: 1; 48 | font-size: 24px; 49 | font-weight: bold; 50 | text-align: center; 51 | } 52 | 53 | .info { 54 | font-size: 16px; 55 | font-weight: bold; 56 | } 57 | 58 | .ellipsis { 59 | lines: 1; 60 | text-overflow: ellipsis; 61 | text-align: left; 62 | } 63 | 64 | .body { 65 | width: 100%; 66 | flex-direction: column; 67 | flex-grow: 1; 68 | } 69 | 70 | .body-swiper { 71 | width: 100%; 72 | flex-grow: 1; 73 | } 74 | 75 | .swiper-container { 76 | width: 100%; 77 | height: 100%; 78 | } 79 | 80 | .body-holder { 81 | width: 100%; 82 | height: 100%; 83 | flex-direction: column; 84 | align-items: center; 85 | justify-content: center; 86 | color: grey; 87 | } 88 | 89 | .body-container { 90 | width: 100%; 91 | height: 100%; 92 | flex-grow: 1; 93 | flex-direction: column; 94 | padding: 24px; 95 | padding-top: 0; 96 | } 97 | 98 | .tabbar { 99 | width: 100%; 100 | padding: 16px 24px; 101 | } 102 | 103 | .tab { 104 | flex-grow: 1; 105 | align-items: center; 106 | justify-content: center; 107 | } 108 | 109 | .btn-group-title { 110 | font-size: 24px; 111 | font-weight: bold; 112 | margin-bottom: 8px; 113 | } 114 | 115 | .card { 116 | padding: 12px 12px; 117 | border-radius: 8px; 118 | background-color: #161616; 119 | align-items: center; 120 | margin-bottom: 8px; 121 | } 122 | 123 | .card-btn-text { 124 | margin-left: 12px; 125 | font-size: 20px; 126 | } 127 | 128 | .card-btn-secondary { 129 | color: grey; 130 | font-size: 16px; 131 | } 132 | 133 | .choose-container { 134 | width: 100%; 135 | margin-top: 8px; 136 | } 137 | 138 | .choose-item { 139 | padding: 4px; 140 | font-size: 20px; 141 | text-align: center; 142 | border-radius: 8px; 143 | border: 1px solid rgba(255,255,255,0.5); 144 | background-color: transparent; 145 | color: white; 146 | } 147 | 148 | .choose-item-active { 149 | background-color: white; 150 | color: black; 151 | } 152 | 153 | .menu { 154 | position: absolute; 155 | top: 0; 156 | left: 0; 157 | width: 336px; 158 | height: 100%; 159 | background-color: rgba(0,0,0,0.9); 160 | justify-content: center; 161 | flex-direction: column; 162 | margin-left: 336px; 163 | } 164 | 165 | .cover-animation-helper { 166 | display: flex; 167 | position: absolute; 168 | top: 0; 169 | left: 0; 170 | width: 100%; 171 | height: 100%; 172 | background-color: black; 173 | margin-left: -336px; 174 | } 175 | 176 | .book-name { 177 | font-size: 24px; 178 | font-weight: bold; 179 | } 180 | 181 | .book-progress, .book-update, .book-hint { 182 | font-size: 24px; 183 | color: #999; 184 | } 185 | 186 | .book-cover { 187 | width: 64px; 188 | height: 96px; 189 | border-radius: 8px; 190 | margin-right: 8px; 191 | } 192 | 193 | .progress { 194 | color: white; 195 | layer-color: grey; 196 | stroke-width: 8px; 197 | } 198 | 199 | @keyframes In { 200 | 100% { 201 | transform: translateX(-336px); 202 | } 203 | 204 | 0% { 205 | transform: translateX(0%); 206 | } 207 | } 208 | 209 | .animation-in { 210 | animation-name: In; 211 | animation-duration: 200ms; 212 | animation-timing-function: ease-out; 213 | animation-delay: 50ms; 214 | margin-left: 336px; 215 | } 216 | 217 | @keyframes Out { 218 | 100% { 219 | transform: translateX(-100%); 220 | /* opacity: 1; */ 221 | } 222 | 223 | 0% { 224 | transform: translateX(0%); 225 | /* opacity: 0; */ 226 | } 227 | } 228 | 229 | .animation-out { 230 | animation-name: Out; 231 | animation-duration: 200ms; 232 | animation-timing-function: ease-in; 233 | animation-delay: 50ms; 234 | margin-left: 0; 235 | } 236 | 237 | @keyframes InBack { 238 | 100% { 239 | transform: translateX(336px); 240 | /* opacity: 1; */ 241 | } 242 | 243 | 0% { 244 | transform: translateX(0%); 245 | /* opacity: 0; */ 246 | } 247 | } 248 | 249 | .animation-in-back { 250 | animation-name: InBack; 251 | animation-duration: 200ms; 252 | animation-delay: 50ms; 253 | animation-timing-function: ease-out; 254 | margin-left: -336px; 255 | } 256 | 257 | @keyframes OutBack { 258 | 100% { 259 | transform: translateX(100%); 260 | /* opacity: 1; */ 261 | } 262 | 263 | 0% { 264 | transform: translateX(0%); 265 | /* opacity: 0; */ 266 | } 267 | } 268 | 269 | .animation-out-back { 270 | animation-name: OutBack; 271 | animation-duration: 200ms; 272 | animation-delay: 50ms; 273 | animation-timing-function: ease-in; 274 | margin-left: 0; 275 | } 276 | 277 | /* style reset classes */ 278 | .col { 279 | flex-direction: column; 280 | } 281 | 282 | .start { 283 | align-items: flex-start; 284 | } 285 | 286 | .center { 287 | align-items: center; 288 | } 289 | 290 | .text-center { 291 | text-align: center; 292 | } 293 | 294 | .justify-start { 295 | justify-content: flex-start; 296 | } 297 | 298 | .justify-center { 299 | justify-content: center; 300 | } 301 | 302 | .space-between { 303 | justify-content: space-between; 304 | } 305 | 306 | .grow { 307 | flex-grow: 1; 308 | } 309 | 310 | .no-grow { 311 | flex-grow: 0; 312 | } 313 | 314 | .no-shrink, image, .badge { 315 | flex-shrink: 0; 316 | } 317 | 318 | .transparent { 319 | background-color: transparent; 320 | } 321 | 322 | .none { 323 | display: none; 324 | } 325 | 326 | /* margin and padding classes */ 327 | 328 | .ma-xl { 329 | margin: 48px; 330 | } 331 | 332 | .ma-lg { 333 | margin: 32px; 334 | } 335 | 336 | .ma-md { 337 | margin: 24px; 338 | } 339 | 340 | .ma-sm { 341 | margin: 12px; 342 | } 343 | 344 | .ma-xs { 345 | margin: 8px; 346 | } 347 | 348 | .ma-0 { 349 | margin: 0; 350 | } 351 | 352 | .mx-xl { 353 | margin-left: 48px; 354 | margin-right: 48px; 355 | } 356 | 357 | .mx-lg { 358 | margin-left: 32px; 359 | margin-right: 32px; 360 | } 361 | 362 | .mx-md { 363 | margin-left: 24px; 364 | margin-right: 24px; 365 | } 366 | 367 | .mx-sm { 368 | margin-left: 12px; 369 | margin-right: 12px; 370 | } 371 | 372 | .mx-xs { 373 | margin-left: 8px; 374 | margin-right: 8px; 375 | } 376 | 377 | .mx-0 { 378 | margin-left: 0; 379 | margin-right: 0; 380 | } 381 | 382 | .my-xl { 383 | margin-top: 48px; 384 | margin-bottom: 48px; 385 | } 386 | 387 | .my-lg { 388 | margin-top: 32px; 389 | margin-bottom: 32px; 390 | } 391 | 392 | .my-md { 393 | margin-top: 24px; 394 | margin-bottom: 24px; 395 | } 396 | 397 | .my-sm { 398 | margin-top: 12px; 399 | margin-bottom: 12px; 400 | } 401 | 402 | .my-xs { 403 | margin-top: 8px; 404 | margin-bottom: 8px; 405 | } 406 | 407 | .my-0 { 408 | margin-top: 0; 409 | margin-bottom: 0; 410 | } 411 | 412 | .mt-xl { 413 | margin-top: 48px; 414 | } 415 | 416 | .mt-lg { 417 | margin-top: 32px; 418 | } 419 | 420 | .mt-md { 421 | margin-top: 24px; 422 | } 423 | 424 | .mt-sm { 425 | margin-top: 12px; 426 | } 427 | 428 | .mt-xs { 429 | margin-top: 8px; 430 | } 431 | 432 | .mt-0 { 433 | margin-top: 0; 434 | } 435 | 436 | .mb-xl { 437 | margin-bottom: 48px; 438 | } 439 | 440 | .mb-lg { 441 | margin-bottom: 32px; 442 | } 443 | 444 | .mb-md { 445 | margin-bottom: 24px; 446 | } 447 | 448 | .mb-sm { 449 | margin-bottom: 12px; 450 | } 451 | 452 | .mb-xs { 453 | margin-bottom: 8px; 454 | } 455 | 456 | .mb-0 { 457 | margin-bottom: 0; 458 | } 459 | 460 | .ml-xl { 461 | margin-left: 48px; 462 | } 463 | 464 | .ml-lg { 465 | margin-left: 32px; 466 | } 467 | 468 | .ml-md { 469 | margin-left: 24px; 470 | } 471 | 472 | .ml-sm { 473 | margin-left: 12px; 474 | } 475 | 476 | .ml-xs { 477 | margin-left: 8px; 478 | } 479 | 480 | .ml-0 { 481 | margin-left: 0; 482 | } 483 | 484 | .mr-xl { 485 | margin-right: 48px; 486 | } 487 | 488 | .mr-lg { 489 | margin-right: 32px; 490 | } 491 | 492 | .mr-md { 493 | margin-right: 24px; 494 | } 495 | 496 | .mr-sm { 497 | margin-right: 12px; 498 | } 499 | 500 | .mr-xs { 501 | margin-right: 8px; 502 | } 503 | 504 | .mr-0 { 505 | margin-right: 0; 506 | } 507 | 508 | .pa-xl { 509 | padding: 48px; 510 | } 511 | 512 | .pa-lg { 513 | padding: 32px; 514 | } 515 | 516 | .pa-md { 517 | padding: 24px; 518 | } 519 | 520 | .pa-sm { 521 | padding: 12px; 522 | } 523 | 524 | .pa-xs { 525 | padding: 8px; 526 | } 527 | 528 | .pa-0 { 529 | padding: 0; 530 | } 531 | 532 | .px-xl { 533 | padding-left: 48px; 534 | padding-right: 48px; 535 | } 536 | 537 | .px-lg { 538 | padding-left: 32px; 539 | padding-right: 32px; 540 | } 541 | 542 | .px-md { 543 | padding-left: 24px; 544 | padding-right: 24px; 545 | } 546 | 547 | .px-sm { 548 | padding-left: 12px; 549 | padding-right: 12px; 550 | } 551 | 552 | .px-xs { 553 | padding-left: 8px; 554 | padding-right: 8px; 555 | } 556 | 557 | .px-0 { 558 | padding-left: 0; 559 | padding-right: 0; 560 | } 561 | 562 | .py-xl { 563 | padding-top: 48px; 564 | padding-bottom: 48px; 565 | } 566 | 567 | .py-lg { 568 | padding-top: 32px; 569 | padding-bottom: 32px; 570 | } 571 | 572 | .py-md { 573 | padding-top: 24px; 574 | padding-bottom: 24px; 575 | } 576 | 577 | .py-sm { 578 | padding-top: 12px; 579 | padding-bottom: 12px; 580 | } 581 | 582 | .py-xs { 583 | padding-top: 8px; 584 | padding-bottom: 8px; 585 | } 586 | 587 | .py-0 { 588 | padding-top: 0; 589 | padding-bottom: 0; 590 | } 591 | 592 | .pt-xl { 593 | padding-top: 48px; 594 | } 595 | 596 | .pt-lg { 597 | padding-top: 32px; 598 | } 599 | 600 | .pt-md { 601 | padding-top: 24px; 602 | } 603 | 604 | .pt-sm { 605 | padding-top: 12px; 606 | } 607 | 608 | .pt-xs { 609 | padding-top: 8px; 610 | } 611 | 612 | .pt-0 { 613 | padding-top: 0; 614 | } 615 | 616 | .pb-xl { 617 | padding-bottom: 48px; 618 | } 619 | 620 | .pb-lg { 621 | padding-bottom: 32px; 622 | } 623 | 624 | .pb-md { 625 | padding-bottom: 24px; 626 | } 627 | 628 | .pb-sm { 629 | padding-bottom: 12px; 630 | } 631 | 632 | .pb-xs { 633 | padding-bottom: 8px; 634 | } 635 | 636 | .pb-0 { 637 | padding-bottom: 0; 638 | } 639 | 640 | .pl-lg { 641 | padding-left: 48px; 642 | } 643 | 644 | .pl-md { 645 | padding-left: 24px; 646 | } 647 | 648 | .pl-sm { 649 | padding-left: 12px; 650 | } 651 | 652 | .pl-xs { 653 | padding-left: 8px; 654 | } 655 | 656 | .pl-0 { 657 | padding-left: 0; 658 | } 659 | 660 | .pr-xl { 661 | padding-right: 48px; 662 | } 663 | 664 | .pr-lg { 665 | padding-right: 32px; 666 | } 667 | 668 | .pr-md { 669 | padding-right: 24px; 670 | } 671 | 672 | .pr-sm { 673 | padding-right: 12px; 674 | } 675 | 676 | .pr-xs { 677 | padding-right: 8px; 678 | } 679 | 680 | .pr-0 { 681 | padding-right: 0; 682 | } 683 | 684 | /* height classes */ 685 | 686 | .h-100 { 687 | height: 100%; 688 | } 689 | 690 | .h-xl { 691 | height: 48px; 692 | } 693 | 694 | .h-lg { 695 | height: 32px; 696 | } 697 | 698 | .h-md { 699 | height: 24px; 700 | } 701 | 702 | .h-sm { 703 | height: 12px; 704 | } 705 | 706 | .h-xs { 707 | height: 8px; 708 | } 709 | 710 | .h-0 { 711 | height: 0; 712 | } 713 | 714 | /* width classes */ 715 | 716 | .w-100 { 717 | width: 100%; 718 | } 719 | 720 | .w-xl { 721 | width: 48px; 722 | } 723 | 724 | .w-lg { 725 | width: 32px; 726 | } 727 | 728 | .w-md { 729 | width: 24px; 730 | } 731 | 732 | .w-sm { 733 | width: 12px; 734 | } 735 | 736 | .w-xs { 737 | width: 8px; 738 | } 739 | 740 | .w-0 { 741 | width: 0; 742 | } 743 | -------------------------------------------------------------------------------- /src/common/icon/author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/author.png -------------------------------------------------------------------------------- /src/common/icon/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/back.png -------------------------------------------------------------------------------- /src/common/icon/book-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/book-code.png -------------------------------------------------------------------------------- /src/common/icon/catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/catalog.png -------------------------------------------------------------------------------- /src/common/icon/check-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/check-active.png -------------------------------------------------------------------------------- /src/common/icon/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/check.png -------------------------------------------------------------------------------- /src/common/icon/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/close.png -------------------------------------------------------------------------------- /src/common/icon/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/code.png -------------------------------------------------------------------------------- /src/common/icon/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/down.png -------------------------------------------------------------------------------- /src/common/icon/explore-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/explore-active.png -------------------------------------------------------------------------------- /src/common/icon/explore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/explore.png -------------------------------------------------------------------------------- /src/common/icon/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/group.png -------------------------------------------------------------------------------- /src/common/icon/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/help.png -------------------------------------------------------------------------------- /src/common/icon/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/history.png -------------------------------------------------------------------------------- /src/common/icon/home-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/home-active.png -------------------------------------------------------------------------------- /src/common/icon/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/home.png -------------------------------------------------------------------------------- /src/common/icon/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/info.png -------------------------------------------------------------------------------- /src/common/icon/latest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/latest.png -------------------------------------------------------------------------------- /src/common/icon/left-double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/left-double.png -------------------------------------------------------------------------------- /src/common/icon/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/left.png -------------------------------------------------------------------------------- /src/common/icon/library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/library.png -------------------------------------------------------------------------------- /src/common/icon/magic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/magic.png -------------------------------------------------------------------------------- /src/common/icon/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/menu.png -------------------------------------------------------------------------------- /src/common/icon/password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/password.png -------------------------------------------------------------------------------- /src/common/icon/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/refresh.png -------------------------------------------------------------------------------- /src/common/icon/right-double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/right-double.png -------------------------------------------------------------------------------- /src/common/icon/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/right.png -------------------------------------------------------------------------------- /src/common/icon/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/search.png -------------------------------------------------------------------------------- /src/common/icon/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/setting.png -------------------------------------------------------------------------------- /src/common/icon/source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/source.png -------------------------------------------------------------------------------- /src/common/icon/submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/submit.png -------------------------------------------------------------------------------- /src/common/icon/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/trash.png -------------------------------------------------------------------------------- /src/common/icon/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/ui.png -------------------------------------------------------------------------------- /src/common/icon/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/up.png -------------------------------------------------------------------------------- /src/common/icon/user-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/user-active.png -------------------------------------------------------------------------------- /src/common/icon/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/user.png -------------------------------------------------------------------------------- /src/common/icon/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/web.png -------------------------------------------------------------------------------- /src/common/image/bookcover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/bookcover.png -------------------------------------------------------------------------------- /src/common/image/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/cover.jpg -------------------------------------------------------------------------------- /src/common/image/qr-feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/qr-feed.png -------------------------------------------------------------------------------- /src/common/image/qr-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/qr-github.png -------------------------------------------------------------------------------- /src/common/image/qr-qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/qr-qq.png -------------------------------------------------------------------------------- /src/common/logo/192-round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/logo/192-round.png -------------------------------------------------------------------------------- /src/common/logo/96-round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/logo/96-round.png -------------------------------------------------------------------------------- /src/config-watch.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/i18n/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "title": "首页" 4 | } 5 | } -------------------------------------------------------------------------------- /src/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "title": "首页" 4 | } 5 | } -------------------------------------------------------------------------------- /src/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "title": "书架" 4 | } 5 | } -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": "vip.lordly.read", 3 | "name": "Lordly·阅读", 4 | "versionName": "1.1.1", 5 | "versionCode": 3, 6 | "minPlatformVersion": 1000, 7 | "minAPILevel": 2, 8 | "icon": "/common/logo/192-round.png", 9 | "deviceTypeList": ["watch"], 10 | "features": [ 11 | { 12 | "name": "system.router" 13 | }, 14 | { 15 | "name": "system.prompt" 16 | }, 17 | { 18 | "name": "system.storage" 19 | }, 20 | { 21 | "name": "system.fetch" 22 | }, 23 | { 24 | "name": "system.device" 25 | }, 26 | { 27 | "name": "system.request" 28 | }, 29 | { 30 | "name": "system.file" 31 | }, 32 | { 33 | "name": "system.brightness" 34 | } 35 | ], 36 | "config": { 37 | "logLevel": "log", 38 | "designWidth": 336 39 | }, 40 | "router": { 41 | "entry": "pages/index", 42 | "pages": { 43 | "pages/index": { 44 | "component": "index", 45 | "launchMode": "singleTask" 46 | }, 47 | "pages/about": { 48 | "component": "about", 49 | "launchMode": "singleTask" 50 | }, 51 | "pages/setting": { 52 | "component": "setting", 53 | "launchMode": "singleTask" 54 | }, 55 | "pages/source": { 56 | "component": "source", 57 | "launchMode": "singleTask" 58 | }, 59 | "pages/detail": { 60 | "component": "detail", 61 | "launchMode": "singleTask" 62 | }, 63 | "pages/content": { 64 | "component": "content", 65 | "launchMode": "singleTask" 66 | }, 67 | "pages/search": { 68 | "component": "search", 69 | "launchMode": "singleTask" 70 | }, 71 | "pages/searchResult": { 72 | "component": "searchResult", 73 | "launchMode": "singleTask" 74 | }, 75 | "pages/catalog": { 76 | "component": "catalog", 77 | "launchMode": "singleTask" 78 | } 79 | } 80 | }, 81 | "display": { 82 | "backgroundColor": "#000000" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/pages/about/about.ux: -------------------------------------------------------------------------------- 1 | 45 | 46 | 60 | 61 | -------------------------------------------------------------------------------- /src/pages/catalog/catalog.ux: -------------------------------------------------------------------------------- 1 | 37 | 38 | 119 | 120 | -------------------------------------------------------------------------------- /src/pages/content/content.ux: -------------------------------------------------------------------------------- 1 | 186 | 187 | 589 | 590 | -------------------------------------------------------------------------------- /src/pages/detail/detail.ux: -------------------------------------------------------------------------------- 1 | 84 | 85 | 246 | 247 | -------------------------------------------------------------------------------- /src/pages/index/index.ux: -------------------------------------------------------------------------------- 1 | 113 | 114 | 224 | 225 | -------------------------------------------------------------------------------- /src/pages/search/search.ux: -------------------------------------------------------------------------------- 1 | 5 | 44 | 45 | 84 | 85 | -------------------------------------------------------------------------------- /src/pages/searchResult/searchResult.ux: -------------------------------------------------------------------------------- 1 | 48 | 49 | 106 | 107 | -------------------------------------------------------------------------------- /src/pages/setting/setting.ux: -------------------------------------------------------------------------------- 1 | 77 | 78 | 120 | 121 | -------------------------------------------------------------------------------- /src/pages/source/source.ux: -------------------------------------------------------------------------------- 1 | 5 | 132 | 133 | 354 | 355 | 372 | -------------------------------------------------------------------------------- /src/third-party/JSONPath/jsonpath.ts: -------------------------------------------------------------------------------- 1 | /* JSONPath 0.8.0 - XPath for JSON 2 | * 3 | * Copyright (c) 2007 Stefan Goessner (goessner.net) 4 | * Licensed under the MIT (MIT-LICENSE.txt) licence. 5 | */ 6 | /* Modified by: jiwangyihao 7 | * Date: 2024-10-31 8 | * Description: Translated to TypeScript 9 | */ 10 | export function jsonPath( 11 | obj: any, 12 | expr: string, 13 | arg?: { 14 | resultType: "VALUE" | "PATH" 15 | } 16 | ) { 17 | const $ = obj 18 | const P = { 19 | resultType: (arg && arg.resultType) || "VALUE", 20 | result: [], 21 | normalize: function (expr: string) { 22 | // noinspection SpellCheckingInspection 23 | const subx = [] 24 | return expr 25 | .replace(/[\['](\??\(.*?\))[\]']/g, function (_$0, $1) { 26 | return "[#" + (subx.push($1) - 1) + "]" 27 | }) 28 | .replace(/'?\.'?|\['?/g, ";") 29 | .replace(/;;;|;;/g, ";..;") 30 | .replace(/;$|'?]|'$/g, "") 31 | .replace(/#([0-9]+)/g, function (_$0, $1) { 32 | return subx[$1] 33 | }) 34 | }, 35 | asPath: function (path: string) { 36 | let x = path.split(";"), 37 | p = "$" 38 | for (let i = 1, n = x.length; i < n; i++) 39 | p += /^[0-9*]+$/.test(x[i]) ? "[" + x[i] + "]" : "['" + x[i] + "']" 40 | return p 41 | }, 42 | store: function (p: string, v: any) { 43 | if (p) P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v 44 | return !!p 45 | }, 46 | trace: function (expr: string, val: any, path: string) { 47 | if (expr) { 48 | let x: string | string[] = expr.split(";"), 49 | loc = x.shift() 50 | x = x.join(";") 51 | if (val && val.hasOwnProperty(loc)) P.trace(x, val[loc], path + ";" + loc) 52 | else if (loc === "*") 53 | P.walk(loc, x, val, path, function (m, _l, x, v, p) { 54 | P.trace(m + ";" + x, v, p) 55 | }) 56 | else if (loc === "..") { 57 | P.trace(x, val, path) 58 | P.walk(loc, x, val, path, function (m, _l, x, v, p) { 59 | typeof v[m] === "object" && P.trace("..;" + x, v[m], p + ";" + m) 60 | }) 61 | } else if (/,/.test(loc)) { 62 | // [name1,name2,...] 63 | for (let s = loc.split(/'?,'?/), i = 0, n = s.length; i < n; i++) 64 | P.trace(s[i] + ";" + x, val, path) 65 | } else if (/^\(.*?\)$/.test(loc)) 66 | // [(expr)] 67 | P.trace(P.eval(loc, val, path.substring(path.lastIndexOf(";") + 1)) + ";" + x, val, path) 68 | else if (/^\?\(.*?\)$/.test(loc)) 69 | // [?(expr)] 70 | P.walk(loc, x, val, path, function (m, l, x, v, p) { 71 | if (P.eval(l.replace(/^\?\((.*?)\)$/, "$1"), v[m], m)) P.trace(m + ";" + x, v, p) 72 | }) 73 | else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) 74 | // [start:end:step] phyton slice syntax 75 | P.slice(loc, x, val, path) 76 | } else P.store(path, val) 77 | }, 78 | walk: function ( 79 | loc: string, 80 | expr: string, 81 | val: any, 82 | path: string, 83 | f: (m: string, l: string, x: string, v: any, p: string) => void 84 | ) { 85 | if (val instanceof Array) { 86 | for (let i = 0, n = val.length; i < n; i++) if (i in val) f(String(i), loc, expr, val, path) 87 | } else if (typeof val === "object") { 88 | for (const m in val) if (val.hasOwnProperty(m)) f(m, loc, expr, val, path) 89 | } 90 | }, 91 | slice: function (loc: string, expr: string, val: any, path: string) { 92 | if (val instanceof Array) { 93 | let len = val.length, 94 | start = 0, 95 | end = len, 96 | step = 1 97 | loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, function (_$0, $1, $2, $3) { 98 | start = parseInt($1 || start) 99 | end = parseInt($2 || end) 100 | step = parseInt($3 || step) 101 | return "" 102 | }) 103 | start = start < 0 ? Math.max(0, start + len) : Math.min(len, start) 104 | end = end < 0 ? Math.max(0, end + len) : Math.min(len, end) 105 | for (let i = start; i < end; i += step) P.trace(i + ";" + expr, val, path) 106 | } 107 | }, 108 | eval: function (x: string, _v: any, _vname: string) { 109 | try { 110 | return $ && _v && eval(x.replace(/@/g, "_v")) 111 | } catch (e) { 112 | throw new SyntaxError( 113 | "jsonPath: " + e.message + ": " + x.replace(/@/g, "_v").replace(/\^/g, "_a") 114 | ) 115 | } 116 | } 117 | } 118 | 119 | if (expr && obj && (P.resultType == "VALUE" || P.resultType == "PATH")) { 120 | P.trace(P.normalize(expr).replace(/^\$;/, ""), obj, "$") 121 | return P.result 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/utils/book.ts: -------------------------------------------------------------------------------- 1 | import {source} from "./index" 2 | 3 | export interface BookTocChapter { 4 | chapterInfo?: string 5 | chapterName: string 6 | chapterUrl?: string 7 | isVolume?: boolean 8 | } 9 | 10 | export interface BookData { 11 | bookSourceUrl: string 12 | name: string 13 | author: string 14 | kind: string[] 15 | wordCount: string 16 | lastChapter: string 17 | coverUrl: string 18 | intro: string 19 | tocUrl: string 20 | bookUrl: string 21 | toc: BookTocChapter[] 22 | progress: number 23 | fProgress: number 24 | variable: string 25 | } 26 | 27 | export class Book { 28 | bookSourceUrl: string 29 | name: string 30 | author: string 31 | kind: string[] 32 | wordCount: string 33 | lastChapter: string 34 | coverUrl: string 35 | intro: string 36 | tocUrl: string 37 | bookUrl: string 38 | toc: BookTocChapter[] = [] 39 | fProgress: number = 0 40 | 41 | variable: string 42 | 43 | constructor(data: Partial) { 44 | this.bookSourceUrl = data.bookSourceUrl 45 | this.name = data.name 46 | this.author = data.author 47 | this.kind = data.kind 48 | this.wordCount = data.wordCount 49 | this.lastChapter = data.lastChapter 50 | this.coverUrl = data.coverUrl 51 | this.intro = data.intro 52 | this.tocUrl = data.tocUrl 53 | this.bookUrl = data.bookUrl 54 | this.toc = data.toc ?? [] 55 | this.progress = data.fProgress ?? data.progress ?? 0 56 | this.progress = Math.max(this.fProgress, 0) 57 | if (this.progress >= this.toc.length) this.progress = this.toc.length - 1 58 | this.variable = data.variable ?? "" 59 | } 60 | 61 | set progress(progress: number) { 62 | this.fProgress = progress 63 | } 64 | 65 | get progress() { 66 | return Math.max(Math.floor(this.fProgress), 0) 67 | } 68 | 69 | update(book: Book) { 70 | this.bookSourceUrl = book.bookSourceUrl 71 | this.name = book.name 72 | this.author = book.author 73 | this.kind = book.kind 74 | this.wordCount = book.wordCount 75 | this.lastChapter = book.lastChapter 76 | this.coverUrl = book.coverUrl 77 | this.intro = book.intro 78 | this.tocUrl = book.tocUrl 79 | this.bookUrl = book.bookUrl 80 | this.toc = book.toc 81 | } 82 | 83 | toData(): BookData { 84 | return { 85 | bookSourceUrl: this.bookSourceUrl, 86 | name: this.name, 87 | author: this.author, 88 | kind: this.kind, 89 | wordCount: this.wordCount, 90 | lastChapter: this.lastChapter, 91 | coverUrl: this.coverUrl, 92 | intro: this.intro, 93 | tocUrl: this.tocUrl, 94 | bookUrl: this.bookUrl, 95 | toc: this.toc, 96 | progress: this.progress, 97 | fProgress: this.fProgress, 98 | variable: this.variable 99 | } 100 | } 101 | 102 | getVariable(v?: string) { 103 | return this.variable ?? "" 104 | } 105 | 106 | async updateToc() { 107 | return await source.getSource(this.bookSourceUrl).loadToc(this) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/utils/chapter.ts: -------------------------------------------------------------------------------- 1 | import {Book, BookData, BookTocChapter} from "./book" 2 | import {file, crypto} from "./tsimports" 3 | import {source} from "." 4 | 5 | export interface ChapterInfo extends BookTocChapter {} 6 | 7 | export class Chapter { 8 | info: ChapterInfo 9 | book: BookData 10 | 11 | constructor(info: ChapterInfo, book: BookData) { 12 | this.info = info 13 | this.book = book 14 | } 15 | 16 | getUri(type: string) { 17 | if (!this.info.chapterUrl) throw new Error("Chapter url not found") 18 | if (!this.book.bookUrl) throw new Error("Book url not found") 19 | return ( 20 | `internal://files/lordly-read/${type}/chapter/` + 21 | crypto.hashDigest({ 22 | data: this.book.bookUrl, 23 | algo: "MD5" 24 | }) + 25 | "-" + 26 | crypto.hashDigest({ 27 | data: this.info.chapterUrl, 28 | algo: "MD5" 29 | }) 30 | ) 31 | } 32 | 33 | hasCached() { 34 | return new Promise((resolve, reject) => { 35 | const uri = this.getUri("cache") 36 | file.access({ 37 | uri, 38 | success() { 39 | resolve(true) 40 | }, 41 | fail() { 42 | resolve(false) 43 | } 44 | }) 45 | }) 46 | } 47 | 48 | hasDownloaded() { 49 | return new Promise((resolve, reject) => { 50 | const uri = this.getUri("download") 51 | file.access({ 52 | uri, 53 | success() { 54 | resolve(true) 55 | }, 56 | fail() { 57 | resolve(false) 58 | } 59 | }) 60 | }) 61 | } 62 | 63 | async getContent() { 64 | try { 65 | return await this.getCachedContent() 66 | } catch { 67 | try { 68 | return await this.getDownloadedContent() 69 | } catch { 70 | return await this.getOnlineContent() 71 | } 72 | } 73 | } 74 | 75 | getCachedContent() { 76 | return new Promise((resolve, reject) => { 77 | const uri = this.getUri("cache") 78 | file.readText({ 79 | uri, 80 | success(data) { 81 | console.log("cache: " + uri) 82 | resolve(data.text) 83 | }, 84 | fail(data, code) { 85 | console.log("cache-fail: ", data, code) 86 | reject(new Error("Chapter not found in cache")) 87 | } 88 | }) 89 | }) 90 | } 91 | 92 | getDownloadedContent() { 93 | return new Promise((resolve, reject) => { 94 | const uri = this.getUri("download") 95 | file.readText({ 96 | uri, 97 | success(data) { 98 | // console.log("download: " + uri) 99 | resolve(data.text) 100 | console.log("download") 101 | }, 102 | fail(data, code) { 103 | console.log("download-fail: ", data, code) 104 | reject(new Error("Chapter not found in downloads")) 105 | } 106 | }) 107 | }) 108 | } 109 | 110 | async getOnlineContent() { 111 | if (!this.info.chapterUrl) throw new Error("Chapter url not found") 112 | const chapterUrl = this.info.chapterUrl 113 | const bookSource = source.getSource(this.book.bookSourceUrl) 114 | const book = new Book(this.book) 115 | const content = await bookSource.loadContent(book, chapterUrl) 116 | await this.cacheContent(content) 117 | if (await this.hasDownloaded()) await this.downloadContent() 118 | return content 119 | } 120 | 121 | cacheContent(content: string) { 122 | return new Promise((resolve, reject) => { 123 | const uri = this.getUri("cache") 124 | file.writeText({ 125 | uri, 126 | text: content, 127 | success() { 128 | console.log("cache: " + uri) 129 | resolve() 130 | }, 131 | fail(data, code) { 132 | console.log("cache-fail: ", data, code) 133 | reject(new Error("Failed to cache chapter")) 134 | } 135 | }) 136 | }) 137 | } 138 | 139 | clearCache() { 140 | return new Promise((resolve, reject) => { 141 | const uri = this.getUri("cache") 142 | file.delete({ 143 | uri, 144 | success() { 145 | resolve() 146 | }, 147 | fail(data, code) { 148 | reject(new Error("Failed to clear cache")) 149 | } 150 | }) 151 | }) 152 | } 153 | 154 | downloadContent() { 155 | return new Promise(async (resolve, reject) => { 156 | const uri = this.getUri("download") 157 | const content = await this.getContent() 158 | await this.clearCache().catch(() => {}) 159 | file.writeText({ 160 | uri, 161 | text: content, 162 | success() { 163 | resolve(true) 164 | }, 165 | fail(data, code) { 166 | reject(new Error("Failed to download chapter")) 167 | } 168 | }) 169 | }) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/utils/cookie.ts: -------------------------------------------------------------------------------- 1 | import {helper} from "./index" 2 | 3 | export class Cookie { 4 | cookies: Record> 5 | getter: () => Promise 6 | setter: (data: string) => Promise 7 | 8 | constructor(options: {getter: () => Promise; setter: (data: string) => Promise}) { 9 | const {getter, setter} = options 10 | this.getter = getter 11 | this.setter = setter 12 | this.refresh().then() 13 | } 14 | 15 | async refresh() { 16 | if (this.cookies) await this.save() 17 | this.cookies = JSON.parse(await this.getter()) 18 | } 19 | 20 | async save() { 21 | if (!this.cookies) console.error("在未初始化 Cookie 类前 save") 22 | await this.setter(JSON.stringify(this.cookies)) 23 | } 24 | 25 | getKey(url: string, key: string) { 26 | url = helper.getDomain(url) 27 | return this.cookies[url]?.[key] 28 | } 29 | 30 | setKey(url: string, key: string, value: string) { 31 | url = helper.getDomain(url) 32 | if (!this.cookies.hasOwnProperty(url)) this.cookies[url] = {} 33 | this.cookies[url][key] = value 34 | } 35 | 36 | getUrl(url: string) { 37 | url = helper.getDomain(url) 38 | return helper.record2Map(this.cookies[url] ?? {}) 39 | } 40 | 41 | setUrl(url: string, value: Map) { 42 | url = helper.getDomain(url) 43 | if (!this.cookies.hasOwnProperty(url)) this.cookies[url] = {} 44 | value.forEach((v, k) => { 45 | this.cookies[url][k] = v 46 | }) 47 | } 48 | 49 | getCookieFromHeader(header: string) { 50 | const value = new Map() 51 | header.split(";")?.forEach((item) => { 52 | value.set(item.split("=")[0]?.trim(), item.split("=")[1]?.trim()) 53 | }) 54 | return value 55 | } 56 | 57 | setUrlByHeader(url: string, header: string) { 58 | this.setUrl(url, this.getCookieFromHeader(header)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/fetch.ts: -------------------------------------------------------------------------------- 1 | import {fetch as systemFetch, request, file} from "./tsimports" 2 | import {cookie, helper} from "." 3 | 4 | class Response { 5 | data: string 6 | cookie: string 7 | headers: any 8 | 9 | constructor(data: string, headers?: any) { 10 | this.data = data 11 | this.headers = headers 12 | } 13 | 14 | body() { 15 | return this.data 16 | } 17 | 18 | cookies() { 19 | return helper.json2Map(this.cookie ?? "{}") 20 | } 21 | } 22 | 23 | export function fetch(rawUrl: string, options?: any): Promise { 24 | return new Promise((resolve, reject) => { 25 | const urlOptions = JSON.parse(rawUrl.split(",")[1] ?? "{}") 26 | let url = rawUrl.split(",")[0] ?? rawUrl 27 | 28 | if (!/^http/i.test(url)) { 29 | if (options.baseUrl) url = `${options.baseUrl}/${url}`.replace(/([^:]\/)\/+/g, "$1") 30 | else { 31 | console.error("错误链接格式") 32 | reject("错误链接格式") 33 | } 34 | } 35 | 36 | const cookieList = [] 37 | 38 | cookie.getUrl(url)?.forEach((v, k) => { 39 | cookieList.push(`${k}=${v}`) 40 | }) 41 | 42 | const fullOptions = { 43 | ...options, 44 | ...urlOptions, 45 | header: { 46 | "User-Agent": 47 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0", 48 | "Cookie": cookieList.join(";"), 49 | ...options?.header, 50 | ...urlOptions?.header 51 | } 52 | } 53 | 54 | fullOptions.header = { 55 | ...fullOptions.sourceHeader, 56 | ...fullOptions.header 57 | } 58 | 59 | if (fullOptions.charset) { 60 | if (/^utf-?8$/gi.test(fullOptions.charset)) { 61 | fullOptions.charset = undefined 62 | url = encodeURI(decodeURIComponent(url)) 63 | } else if (/^gbk$/gi.test(fullOptions.charset)) { 64 | fullOptions.charset = "gbk" 65 | } else { 66 | console.error("不支持的编码类型") 67 | reject("不支持的编码类型") 68 | } 69 | } else { 70 | url = encodeURI(decodeURIComponent(url)) 71 | } 72 | 73 | if (fullOptions.charset) { 74 | // const GBK = require("../third-party/GBK.js/dist/gbk.min") 75 | // url = GBK.URI.encodeURI(url) 76 | // request.download({ 77 | // url, 78 | // ...fullOptions, 79 | // header: "", 80 | // success(data) { 81 | // console.log(data) 82 | // request.onDownloadComplete({ 83 | // token: data.token, 84 | // success(res) { 85 | // file.readArrayBuffer({ 86 | // uri: res.uri, 87 | // success(buffer) { 88 | // const result = GBK.decode(buffer.buffer) 89 | // // const result = buffer 90 | // file.delete({ 91 | // uri: res.uri, 92 | // success() { 93 | // resolve(new Response(result)) 94 | // }, 95 | // fail(...err) { 96 | // reject(err) 97 | // } 98 | // }) 99 | // }, 100 | // fail(...err) { 101 | // reject(err) 102 | // } 103 | // }) 104 | // }, 105 | // fail(...err) { 106 | // reject(err) 107 | // } 108 | // }) 109 | // }, 110 | // fail(...err) { 111 | // console.log(err) 112 | // reject(err) 113 | // } 114 | // }) 115 | } else { 116 | systemFetch.fetch({ 117 | url, 118 | ...fullOptions, 119 | success(res) { 120 | const response = new Response(res.data, res.headers) 121 | if (res.headers["Set-Cookie"]) { 122 | cookie.setUrlByHeader(url, res.headers["Set-Cookie"]) 123 | response.cookie = helper.map2Json(cookie.getCookieFromHeader(res.headers["Set-Cookie"])) 124 | } 125 | resolve(response) 126 | }, 127 | fail(err) { 128 | console.log(err) 129 | if ((fullOptions.retry ?? 0) > 0) { 130 | fetch(url, {...fullOptions, retry: fullOptions.retry - 1}) 131 | .then(resolve) 132 | .catch(reject) 133 | } else { 134 | reject(err) 135 | } 136 | } 137 | }) 138 | } 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /src/utils/jsExtension.ts: -------------------------------------------------------------------------------- 1 | import {fetch} from "./fetch" 2 | import {Source} from "./source" 3 | import {date} from "./index" 4 | 5 | export class JsExtension { 6 | state: { 7 | vars: Map 8 | } 9 | source: Source 10 | src: string 11 | 12 | constructor(state: any, source: Source) { 13 | this.state = state 14 | this.source = source 15 | } 16 | 17 | updateSrc(src: string) { 18 | this.src = src 19 | } 20 | 21 | get(key: string) { 22 | return this.state.vars.get(key) 23 | } 24 | 25 | put(key: string, value: any) { 26 | this.state.vars.set(key, value) 27 | return value 28 | } 29 | 30 | getString(rule: string) { 31 | return this.source.parseGetRule(rule, this.src) 32 | } 33 | 34 | async post(urlStr: string, body: string, headers: Record) { 35 | let data: Record | string = {} 36 | if (headers["Content-Type"] === "application/json") { 37 | data = JSON.parse(body) 38 | } else if (headers["Content-Type"] === "application/x-www-form-urlencoded") { 39 | body.split("&").forEach((v) => { 40 | const [key, value] = v.split("=") 41 | data[key] = value 42 | }) 43 | } 44 | 45 | return await this.source.fetch(urlStr, { 46 | method: "POST", 47 | header: headers, 48 | data 49 | }) 50 | } 51 | 52 | async ajax(urlStr: string) { 53 | return (await this.source.fetch(urlStr)).body() 54 | } 55 | 56 | timeFormat(time: number) { 57 | return date.format(new Date(time), "yyyy-MM-dd hh:mm:ss") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/source.ts: -------------------------------------------------------------------------------- 1 | import {JsExtension} from "./jsExtension" 2 | import {Cookie} from "./cookie" 3 | import {Book, BookData} from "./book" 4 | import {fetch} from "./fetch" 5 | import {helper} from "./index" 6 | import {jsonPath} from "../third-party/JSONPath/jsonpath" 7 | 8 | export interface SourceData { 9 | bookSourceComment: string 10 | bookSourceGroup: string 11 | bookSourceName: string 12 | bookSourceType: number 13 | bookSourceUrl: string 14 | concurrentRate: string 15 | customOrder: number 16 | enabled: boolean 17 | enabledCookieJar: boolean 18 | enabledExplore: boolean 19 | exploreUrl: string 20 | jsLib: string 21 | lastUpdateTime: number 22 | loginCheckJs: string 23 | loginUi: string 24 | loginUrl: string 25 | respondTime: number 26 | ruleBookInfo: RuleBookInfo 27 | ruleContent: RuleContent 28 | ruleExplore: RuleExplore 29 | ruleSearch: RuleSearch 30 | ruleToc: RuleToc 31 | searchUrl: string 32 | weight: number 33 | } 34 | 35 | export interface RuleBookInfo { 36 | author: string 37 | coverUrl: string 38 | init: string 39 | intro: string 40 | kind: string 41 | lastChapter: string 42 | name: string 43 | tocUrl: string 44 | wordCount: string 45 | } 46 | 47 | export interface RuleContent { 48 | content: string 49 | } 50 | 51 | export interface RuleExplore { 52 | author: string 53 | bookList: string 54 | bookUrl: string 55 | coverUrl: string 56 | intro: string 57 | kind: string 58 | name: string 59 | wordCount: string 60 | lastChapter: string 61 | } 62 | 63 | export interface RuleSearch extends RuleExplore { 64 | checkKeyWord: string 65 | } 66 | 67 | export interface RuleToc { 68 | chapterList: string 69 | chapterName: string 70 | chapterUrl: string 71 | isVolume: string 72 | updateTime: string 73 | } 74 | 75 | export interface SourceUi { 76 | bookSourceUrl: string 77 | bookSourceName: string 78 | enabled: boolean 79 | enabledExplore: boolean 80 | hasExplore: boolean 81 | hasLogin: boolean 82 | loginUi?: LoginUiComponent[] 83 | } 84 | 85 | export interface LoginUiComponent { 86 | type: string 87 | name: string 88 | value: string 89 | } 90 | 91 | export class Source { 92 | raw: SourceData 93 | cookie: Cookie 94 | java = new JsExtension( 95 | { 96 | vars: new Map() 97 | }, 98 | this 99 | ) 100 | loginInfoMap: string 101 | loginHeader: string 102 | variable: string 103 | jsonPathCache: Map = new Map() 104 | 105 | constructor(raw: SourceData, cookie: Cookie) { 106 | this.raw = raw 107 | this.cookie = cookie 108 | } 109 | 110 | get bookSourceUrl() { 111 | return this.raw.bookSourceUrl 112 | } 113 | 114 | get bookSourceName() { 115 | return this.raw.bookSourceName 116 | } 117 | 118 | get enabled() { 119 | return this.raw.enabled 120 | } 121 | 122 | set enabled(value: boolean) { 123 | this.raw.enabled = value 124 | } 125 | 126 | get enabledExplore() { 127 | return this.raw.enabledExplore 128 | } 129 | 130 | set enabledExplore(value: boolean) { 131 | this.raw.enabledExplore = value 132 | } 133 | 134 | get hasExplore() { 135 | return this.raw.ruleExplore !== undefined 136 | } 137 | 138 | get hasLogin() { 139 | return this.raw.loginUrl !== undefined 140 | } 141 | 142 | get loginUi() { 143 | try { 144 | const loginInfoMap = helper.json2Map(this.loginInfoMap ?? "{}") 145 | return JSON.parse(this.raw.loginUi)?.map((v: Partial) => { 146 | return { 147 | value: loginInfoMap.get(v.name) ?? "", 148 | ...v 149 | } 150 | }) as LoginUiComponent[] 151 | } catch (e) { 152 | return undefined 153 | } 154 | } 155 | 156 | get additionalData() { 157 | return { 158 | bookSourceUrl: this.bookSourceUrl, 159 | loginInfoMap: this.loginInfoMap, 160 | loginHeader: this.loginHeader, 161 | variable: this.variable 162 | } 163 | } 164 | 165 | set additionalData(value: { 166 | bookSourceUrl: string 167 | loginInfoMap: string 168 | loginHeader: string 169 | variable: string 170 | }) { 171 | this.loginInfoMap = value.loginInfoMap 172 | this.loginHeader = value.loginHeader 173 | this.variable = value.variable 174 | } 175 | 176 | get sourceHeader() { 177 | return JSON.parse(this.loginHeader ?? "{}") 178 | } 179 | 180 | // noinspection JSUnusedGlobalSymbols // used in eval 181 | getLoginInfoMap() { 182 | return helper.json2Map(this.loginInfoMap) 183 | } 184 | 185 | // noinspection JSUnusedGlobalSymbols // used in eval 186 | putLoginHeader(header: string) { 187 | this.loginHeader = header 188 | } 189 | 190 | getVariable() { 191 | return this.variable ?? "" 192 | } 193 | 194 | async fetch(url: string, options?: any) { 195 | return await fetch(url, { 196 | baseUrl: this.bookSourceUrl, 197 | sourceHeader: this.sourceHeader, 198 | ...options 199 | }) 200 | } 201 | 202 | async executeJs(js: string, additional: Record, debug = false) { 203 | // noinspection JSUnusedLocalSymbols 204 | let {key, page, result, resolve, book, baseUrl} = { 205 | book: new Book({bookSourceUrl: this.bookSourceUrl}), 206 | baseUrl: this.bookSourceUrl, 207 | ...additional 208 | } as Record 209 | const java = this.java 210 | const cookie = this.cookie 211 | const source = this 212 | 213 | let resultResolve: (value: any) => void 214 | let resultReject: (value: any) => void 215 | 216 | const resultPromise = new Promise((resolve, reject) => { 217 | resultResolve = (res) => { 218 | if (debug) console.log(res) 219 | resolve(res) 220 | } 221 | resultReject = (e) => { 222 | if (debug) console.log(e) 223 | reject(e) 224 | } 225 | }) 226 | 227 | // js = js.replace(/java\.(.*?)\(/gi, "await java.$1(") 228 | 229 | js = js 230 | .replace(/(?<=\s|\(|=|\n|\[)([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await 231 | .replace(/^([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await 232 | .replace(/\((\w+)\(([^()\n]*?)\)/g, "(await $1($2))") // 将函数调用全部转换为 await 233 | .replace(/function( await)? (\w+)\(/g, "async function $2(") // 将函数声明全部转换为 async function 234 | .replace(/new( await)? (\w+)\(/g, "new $2(") // 将类实例化中的 await 去掉 235 | .replace(/await (if|else if|catch|for|while)/g, "$1") // 去掉关键字前的 await 236 | .replace(/await (\w+)\((.*?)\)/g, "(await $1($2))") // 给 await 函数调用加括号 237 | 238 | js = 239 | "async function main() {\n" + 240 | (this.raw.jsLib ?? "") 241 | .replace(/(?<=\s|\(|=|\n)([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await 242 | .replace(/^([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await 243 | .replace(/function( await)? (\w+)\(/g, "async function $2(") // 将函数声明全部转换为 async function 244 | .replace(/new( await)? (\w+)\(/g, "new $2(") // 将类实例化中的 await 去掉 245 | .replace(/await (if|else if|catch|for|while)/g, "$1") // 去掉关键字前的 await 246 | .replace(/await (\w+)\((.*?)\)/g, "(await $1($2))") // 给 await 函数调用加括号 247 | .replace(/(const|let|var)\s*\{[\w\s,]+}\s*=\s*this\s*\n/g, "") + // 去掉 this 变量声明 248 | js.replace(/\n(.+)\s*$/i, "\nreturn $1").replace(/^(.+)$/i, "return $1") + 249 | "\n}\nmain().then(r=>resultResolve(r)).catch(e=>resultReject(e))" 250 | 251 | if (debug) console.log(js) 252 | 253 | try { 254 | global.runGC() 255 | eval(js) 256 | return await resultPromise 257 | } catch (e) { 258 | console.log(e) 259 | return result 260 | } 261 | } 262 | 263 | async parseGetRule( 264 | rule: string, 265 | result: string, 266 | maybeJs = false, 267 | isList = false, 268 | additional: Record = {}, 269 | debug = false 270 | ) { 271 | let res: string | string[] = rule 272 | 273 | const additionalKeys = Object.keys(additional) 274 | 275 | if (additionalKeys.includes(rule)) { 276 | return [additional[rule]] 277 | } else if (/^\$\./.test(rule)) { 278 | // JsonPath 279 | if (debug) console.log(rule, result) 280 | global.runGC() 281 | const $ = JSON.parse(result) 282 | if (isList) { 283 | res = jsonPath($, rule.replace(/\[-1]/gi, "[-1:]")) 284 | } else { 285 | let path = this.jsonPathCache.get(rule) 286 | if (path === undefined) { 287 | path = jsonPath($, rule.replace(/\[-1]/gi, "[-1:]"), { 288 | resultType: "PATH" 289 | }) 290 | if (path.length === 1) { 291 | this.jsonPathCache.set(rule, path) 292 | } 293 | } 294 | for (const v of path) { 295 | res = eval(v) 296 | } 297 | } 298 | } else if (maybeJs) { 299 | try { 300 | res = await this.executeJs(rule, { 301 | result, 302 | ...additional 303 | }) 304 | } catch (e) { 305 | console.log(e) 306 | } 307 | } 308 | 309 | if (debug) console.log(rule, res) 310 | 311 | if (res instanceof Array) { 312 | return res 313 | } else { 314 | return [res] 315 | } 316 | } 317 | 318 | async parseBracketRule( 319 | rule: string, 320 | result: string, 321 | maybeJs = false, 322 | isList = false, 323 | additional?: Record, 324 | debug = false 325 | ) { 326 | const regexpRule = rule.match(/##.+(##.*)?/gi)?.[0] 327 | rule = rule.replace(/##.+(##.*)?/gi, "") 328 | 329 | let resultList = [] 330 | for (const orPart of rule.split("||")) { 331 | if (debug) console.log(orPart) 332 | for (const andPart of orPart.split("&&")) { 333 | resultList.push( 334 | ...(await this.parseGetRule(andPart, result, maybeJs, isList, additional, debug)) 335 | ) 336 | } 337 | if (resultList.length > 0) break 338 | } 339 | 340 | resultList = resultList.flat(3).map((v) => (typeof v === "string" ? v : JSON.stringify(v))) 341 | 342 | if (regexpRule) { 343 | resultList = resultList.map((res) => 344 | res.replace( 345 | new RegExp(regexpRule.split("##")[1] ?? "", "gi"), 346 | regexpRule.split("##")[2] ?? "" 347 | ) 348 | ) 349 | } 350 | 351 | return resultList 352 | } 353 | 354 | async parseRule( 355 | rule: string, 356 | result: string, 357 | isList = false, 358 | additional?: Record, 359 | debug = false 360 | ) { 361 | if (!rule) { 362 | return isList ? [] : "" 363 | } 364 | 365 | if (rule.match(/{{[\s\S]*?}}/gi)) { 366 | for (const v of rule.match(/{{[\s\S]*?}}/gi)) { 367 | const bracketRule = v.replace(/^{{|}}$/gi, "") 368 | rule = rule.replace( 369 | v, 370 | (await this.parseBracketRule(bracketRule, result, true, isList, additional, debug)).join( 371 | ", " 372 | ) 373 | ) 374 | } 375 | } 376 | 377 | let parts = rule 378 | .split(/(@js:[\s\S]*?$)|([\s\S]*?<\/js>)/gi) 379 | .filter((v) => !!v && !v?.match(/^\s*$/)) 380 | .filter((v) => !v.match(/^undefined$/gi)) 381 | 382 | let res = undefined 383 | 384 | if (debug) console.log(parts) 385 | 386 | for (const v of parts) { 387 | if (/^|^@js:|<\/js>$/gi.test(v)) { 388 | let js = v.replace(/^|^@js:|<\/js>$/gi, "") 389 | if (js.match(/{{[\s\S]*?}}/gi)) { 390 | for (const v of js.match(/{{[\s\S]*?}}/gi)) { 391 | const rule = v.replace(/^{{|}}$/gi, "") 392 | js = js.replace( 393 | v, 394 | (await this.parseBracketRule(rule, result, false, isList, additional, debug)).join( 395 | ", " 396 | ) 397 | ) 398 | } 399 | } 400 | if (debug) console.log(js) 401 | res = await this.executeJs(js, {result: res ?? result, ...additional}, debug) 402 | } else if (/^\s*##.+(##.*)?\s*$/.test(v)) { 403 | if (!isList && typeof res === "string") { 404 | res = res.replace(new RegExp(v.split("##")[1] ?? "", "gi"), v.split("##")[2] ?? "") 405 | } 406 | } else if (!v.match(/{{[\s\S]*?}}/gi)) { 407 | res = await this.parseBracketRule(v, result, false, isList, additional, debug) 408 | if (!isList) { 409 | res = res.join(", ") 410 | } 411 | } else { 412 | res ??= v 413 | } 414 | } 415 | 416 | if (!isList && typeof res === "string") { 417 | if (res.match(/{{[\s\S]*?}}/gi)) { 418 | for (const v of res.match(/{{[\s\S]*?}}/gi)) { 419 | const rule = v.replace(/^{{|}}$/gi, "") 420 | if (debug) console.log(v, rule) 421 | res = res.replace( 422 | v, 423 | await this.parseBracketRule(rule, result, true, isList, additional, debug) 424 | ) 425 | } 426 | } 427 | } 428 | 429 | return res 430 | } 431 | 432 | async login(loginInfoMap: Map) { 433 | this.loginInfoMap = helper.map2Json(loginInfoMap) 434 | 435 | if (!this.hasLogin) { 436 | return { 437 | success: false, 438 | message: "未配置登录信息" 439 | } 440 | } 441 | 442 | if (!this.raw.loginUrl.match(/^|^@js:|<\/js>$/gi)) { 443 | console.error("暂不支持链接登录") 444 | } 445 | 446 | const js = this.raw.loginUrl 447 | .replace(/^|^@js:|<\/js>$/gi, "") 448 | .replace(/function login\(\) {/gi, "async function login() {") 449 | 450 | let resultResolve: (value: {success: boolean; message: string}) => void 451 | 452 | const result = new Promise<{ 453 | success: boolean 454 | message: string 455 | }>((resolve) => { 456 | resultResolve = resolve 457 | }) 458 | 459 | await this.executeJs( 460 | js + 461 | "\nlogin().then(r=>resolve({success:true, msg:''})).catch(e=>resolve({success:false, msg:e}))", 462 | { 463 | resolve: resultResolve 464 | } 465 | ) 466 | 467 | return await result 468 | } 469 | 470 | async search(key: string, page: number) { 471 | let parts = this.raw.searchUrl 472 | // .replace("pageSize=20", "pageSize=10") 473 | .split(/(@js:[\s\S]*?$)|([\s\S]*?<\/js>)/gi) 474 | .filter((v) => !!v && !v?.match(/^\s*$/)) 475 | .filter((v) => !v.match(/^undefined$/gi)) 476 | let url = parts.shift() 477 | 478 | for (const v of parts) { 479 | const js = v.replace(/^|^@js:|<\/js>$/gi, "") 480 | url = await this.executeJs(js, {result: url}) 481 | } 482 | 483 | if (url.match(/{{[\s\S]*?}}/gi)) { 484 | for (const v of url.match(/{{[\s\S]*?}}/gi)) { 485 | const rule = v.replace(/^{{|}}$/gi, "") 486 | url = url.replace(v, (await this.parseBracketRule(rule, url, true, false, {key, page}))[0]) 487 | } 488 | } 489 | 490 | url.match(/{(key|page)}/gi)?.forEach((v) => { 491 | url = url.replace(v, (/key/gi.test(v) ? key : page) as string) 492 | }) 493 | 494 | const response = ( 495 | await this.fetch(url, { 496 | responseType: "text" 497 | }) 498 | ).body() 499 | 500 | this.java.updateSrc(response) 501 | 502 | const resultList = [] 503 | 504 | for (const v of await this.parseRule(this.raw.ruleSearch.bookList, response, true)) { 505 | this.java.updateSrc(v) 506 | try { 507 | resultList.push( 508 | new Book({ 509 | bookSourceUrl: this.bookSourceUrl, 510 | name: await this.parseRule(this.raw.ruleSearch.name, v, false, {baseUrl: url}), 511 | author: await this.parseRule(this.raw.ruleSearch.author, v, false, {baseUrl: url}), 512 | kind: await this.parseRule(this.raw.ruleSearch.kind, v, true, {baseUrl: url}), 513 | coverUrl: await this.parseRule(this.raw.ruleSearch.coverUrl, v, false, {baseUrl: url}), 514 | intro: await this.parseRule(this.raw.ruleSearch.intro, v, false, {baseUrl: url}), 515 | wordCount: await this.parseRule(this.raw.ruleSearch.wordCount, v, false, { 516 | baseUrl: url 517 | }), 518 | bookUrl: await this.parseRule(this.raw.ruleSearch.bookUrl, v, false, {baseUrl: url}), 519 | lastChapter: await this.parseRule(this.raw.ruleSearch.lastChapter, v, false, { 520 | baseUrl: url 521 | }) 522 | }) 523 | ) 524 | } catch (e) { 525 | console.log(e) 526 | } 527 | } 528 | 529 | return resultList 530 | } 531 | 532 | async detail(bookData: BookData) { 533 | const book = new Book(bookData) 534 | const response = await this.parseRule( 535 | this.raw.ruleBookInfo.init, 536 | ( 537 | await this.fetch(bookData.bookUrl, { 538 | responseType: "text" 539 | }) 540 | ).body(), 541 | false, 542 | {book} 543 | ) 544 | 545 | this.java.updateSrc(response) 546 | 547 | book.name = helper.withDefault( 548 | await this.parseRule(this.raw.ruleBookInfo.name, response, false, { 549 | book, 550 | baseUrl: book.bookUrl 551 | }), 552 | book.name 553 | ) 554 | book.author = helper.withDefault( 555 | await this.parseRule(this.raw.ruleBookInfo.author, response, false, { 556 | book, 557 | baseUrl: book.bookUrl 558 | }), 559 | book.author 560 | ) 561 | book.kind = helper.withDefault( 562 | ( 563 | await this.parseRule(this.raw.ruleBookInfo.kind, response, false, { 564 | book, 565 | baseUrl: book.bookUrl 566 | }) 567 | ) 568 | .split(",") 569 | .map((v: string) => v.trim()) 570 | .filter((v: string) => !!v), 571 | book.kind 572 | ) 573 | book.wordCount = helper.withDefault( 574 | await this.parseRule(this.raw.ruleBookInfo.wordCount, response, false, { 575 | book, 576 | baseUrl: book.bookUrl 577 | }), 578 | book.wordCount 579 | ) 580 | book.lastChapter = helper.withDefault( 581 | await this.parseRule(this.raw.ruleBookInfo.lastChapter, response, false, { 582 | book, 583 | baseUrl: book.bookUrl 584 | }), 585 | book.lastChapter 586 | ) 587 | book.coverUrl = helper.withDefault( 588 | await this.parseRule(this.raw.ruleBookInfo.coverUrl, response, false, { 589 | book, 590 | baseUrl: book.bookUrl 591 | }), 592 | book.coverUrl 593 | ) 594 | book.intro = helper.withDefault( 595 | await this.parseRule(this.raw.ruleBookInfo.intro, response, false, { 596 | book, 597 | baseUrl: book.bookUrl 598 | }), 599 | book.intro 600 | ) 601 | book.tocUrl = helper.withDefault( 602 | await this.parseRule(this.raw.ruleBookInfo.tocUrl, response, false, { 603 | book, 604 | baseUrl: book.bookUrl 605 | }), 606 | book.tocUrl 607 | ) 608 | 609 | return book 610 | } 611 | 612 | async loadToc(book: Book) { 613 | const response = ( 614 | await this.fetch(book.tocUrl ?? book.bookUrl, { 615 | responseType: "text" 616 | }) 617 | ).body() 618 | 619 | this.java.updateSrc(response) 620 | 621 | book.toc = [] 622 | 623 | for (const v of await this.parseRule(this.raw.ruleToc.chapterList, response, true)) { 624 | this.java.updateSrc(v) 625 | try { 626 | book.toc.push({ 627 | chapterInfo: await this.parseRule(this.raw.ruleToc.updateTime, v, false, { 628 | baseUrl: book.bookUrl 629 | }), 630 | chapterName: await this.parseRule(this.raw.ruleToc.chapterName, v, false, { 631 | baseUrl: book.bookUrl 632 | }), 633 | chapterUrl: await this.parseRule(this.raw.ruleToc.chapterUrl, v, false, { 634 | baseUrl: book.bookUrl 635 | }), 636 | isVolume: await this.parseRule(this.raw.ruleToc.isVolume, v, false, { 637 | baseUrl: book.bookUrl 638 | }) 639 | }) 640 | } catch (e) { 641 | console.log(e) 642 | } 643 | } 644 | 645 | return book 646 | } 647 | 648 | async loadContent(book: Book, chapterUrl: string) { 649 | const response = ( 650 | await this.fetch(chapterUrl, { 651 | responseType: "text" 652 | }) 653 | ).body() 654 | 655 | const content = await this.parseRule(this.raw.ruleContent.content, response, false, { 656 | baseUrl: chapterUrl 657 | }) 658 | 659 | return content 660 | .replace(/<(?!img\b)[^>]*>/gi, "") // 去掉除以外的标签 661 | .replace(/&/gi, "&") 662 | .replace(/ /gi, " ") 663 | .split(/\n+/) 664 | .map((v: string) => v.trim()) 665 | .filter((v: string) => !!v) 666 | .join("\n") 667 | } 668 | } 669 | -------------------------------------------------------------------------------- /src/utils/tsimports.js: -------------------------------------------------------------------------------- 1 | import router from "@system.router" 2 | import storage from "@system.storage" 3 | import prompt from "@system.prompt" 4 | import fetch from "@system.fetch" 5 | import device from "@system.device" 6 | import request from "@system.request" 7 | import file from "@system.file" 8 | import crypto from "@system.crypto" 9 | import brightness from "@system.brightness" 10 | 11 | export {fetch, storage, device, router, prompt, request, file, crypto, brightness} 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "esModuleInterop": true, 5 | "module": "commonjs", 6 | "target": "es5", 7 | "outDir": "./dist" 8 | }, 9 | "include": [ 10 | "src/**/*" 11 | ], 12 | "exclude": [ 13 | "src/**/*.ux" 14 | ] 15 | } --------------------------------------------------------------------------------