├── .ecrc.json ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ └── feature.yml └── workflows │ ├── contribute_list.yml │ ├── editorconfig-check.yml │ ├── pake-cli.yaml │ ├── pake_build.yaml │ ├── pake_build_with_cache.yaml │ └── rust-code-quality-check.yml ├── .gitignore ├── .prettierignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_CN.md ├── app.csv ├── bin ├── README.md ├── README_EN.md ├── builders │ ├── BuilderFactory.ts │ ├── LinuxBuilder.ts │ ├── MacBuilder.ts │ ├── WinBulider.ts │ ├── base.ts │ ├── common.ts │ └── tauriConf.js ├── cli.ts ├── defaults.ts ├── helpers │ ├── rust.ts │ ├── tauriConfig.ts │ └── updater.ts ├── options │ ├── icon.ts │ ├── index.ts │ └── logger.ts ├── types.ts └── utils │ ├── dir.ts │ ├── ip_addr.ts │ ├── platform.ts │ ├── shell.ts │ ├── tlds.ts │ ├── url.ts │ └── validate.ts ├── cli.js ├── dist ├── .gitkeep ├── about_pake.html └── cli.js ├── icns2png.py ├── package.json ├── rollup.config.js ├── script ├── build.bat ├── build.ps1 ├── build.sh ├── build_with_pake_cli.ps1 ├── sd-apple-x64 ├── sd-linux-aarch64 └── sd-linux-x86_64 ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── assets │ ├── com-tw93-weread.desktop │ └── main.wxs ├── build.rs ├── cn_config.bak ├── icons │ ├── ails.icns │ ├── chatgpt.icns │ ├── coderunner.icns │ ├── flomo.icns │ ├── icon.icns │ ├── lizhi.icns │ ├── loop.icns │ ├── poe.icns │ ├── programmusic.icns │ ├── qwerty.icns │ ├── reference.icns │ ├── tool.icns │ ├── twitter.icns │ ├── wechat.icns │ ├── weread.icns │ ├── witeboard.icns │ ├── xapi.icns │ ├── xiaohongshu.icns │ ├── youtube.icns │ ├── yuque.icns │ └── zlibrary.icns ├── pake.json ├── png │ ├── ails_256.ico │ ├── ails_32.ico │ ├── ails_512.png │ ├── chatgpt_256.ico │ ├── chatgpt_32.ico │ ├── chatgpt_512.png │ ├── code_256.ico │ ├── code_32.ico │ ├── code_512.png │ ├── coderunner_256.ico │ ├── coderunner_32.ico │ ├── coderunner_512.png │ ├── flomo_256.ico │ ├── flomo_32.ico │ ├── flomo_512.png │ ├── icon_256.ico │ ├── icon_32.ico │ ├── icon_512.png │ ├── lizhi_256.ico │ ├── lizhi_32.ico │ ├── lizhi_512.png │ ├── loop_256.ico │ ├── loop_32.ico │ ├── loop_512.png │ ├── poe_256.ico │ ├── poe_32.ico │ ├── poe_512.png │ ├── programmusic_256.ico │ ├── programmusic_32.ico │ ├── programmusic_512.png │ ├── qwerty_256.ico │ ├── qwerty_32.ico │ ├── qwerty_512.png │ ├── reference_256.ico │ ├── reference_32.ico │ ├── reference_512.png │ ├── tool_256.ico │ ├── tool_32.ico │ ├── tool_512.png │ ├── twitter_256.ico │ ├── twitter_32.ico │ ├── twitter_512.png │ ├── wechat_256.ico │ ├── wechat_32.ico │ ├── wechat_512.png │ ├── weread_256.ico │ ├── weread_32.ico │ ├── weread_512.png │ ├── witeboard_256.ico │ ├── witeboard_32.ico │ ├── witeboard_512.png │ ├── xapi_256.ico │ ├── xapi_32.ico │ ├── xapi_512.png │ ├── xiaohongshu_256.ico │ ├── xiaohongshu_32.ico │ ├── xiaohongshu_512.png │ ├── youtube_256.ico │ ├── youtube_32.ico │ ├── youtube_512.png │ ├── yuque_256.ico │ ├── yuque_32.ico │ ├── yuque_512.png │ ├── zlibrary_256.ico │ ├── zlibrary_32.ico │ └── zlibrary_512.png ├── src │ ├── app │ │ ├── config.rs │ │ ├── invoke.rs │ │ ├── menu.rs │ │ ├── mod.rs │ │ └── window.rs │ ├── inject │ │ ├── component.js │ │ ├── event.js │ │ └── style.js │ ├── main.rs │ └── util.rs ├── tauri.conf.json ├── tauri.linux.conf.json ├── tauri.macos.conf.json └── tauri.windows.conf.json └── tsconfig.json /.ecrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "Exclude": ["Cargo\\.lock$", "dist", "*\\.md"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Use 4 spaces for Python, Rust and Bash files 13 | [*.{py,rs,sh}] 14 | indent_size = 4 15 | 16 | # Makefiles always use tabs for indentation 17 | [Makefile] 18 | indent_style = tab 19 | 20 | [*.bat] 21 | indent_size = 2 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | 26 | [*.ts] 27 | quote_type= "single" 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | bin/**/* linguist-vendored 2 | bin/* linguist-vendored 3 | dist/* linguist-vendored 4 | /cli.js linguist-vendored 5 | .github/**/* linguist-vendored 6 | .github/* linguist-vendored 7 | script/* linguist-vendored 8 | /icns2png.py linguist-vendored 9 | /rollup.config.js linguist-vendored 10 | src-tauri/src/inject/* linguist-vendored 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["tw93"] 2 | custom: ["https://miaoyan.app/cats.html?name=Pake"] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Problems with the software 3 | title: "[Bug] " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you very much for your feedback! 10 | 11 | 有关讨论、建议或者咨询的内容请去往[讨论区](https://github.com/tw93/Pake/discussions)。 12 | 13 | For suggestions or help, please consider using [Github Discussion](https://github.com/tw93/Pake/discussions) instead. 14 | - type: checkboxes 15 | attributes: 16 | label: Search before asking 17 | description: > 18 | 🙊 辛苦提 bug 前,去找一下 [历史](https://github.com/tw93/Pake/issues?q=) 是否有提。辛苦提供系统版本、录屏或者截图、复现路径,期待解决的点——这几个说明能帮助我更好的解决问题,此外假如是讨论,建议辛苦去 [Discussions](https://github.com/tw93/Pake/discussions) 看看是否有类似的讨论。 19 | 20 | 🙊 Check out [Issues](https://github.com/tw93/Pake/issues?q=) before reporting. Please provide your system version, screencasts, screenshots, way to reproduce, and the expected result – helpful for me to understand and fix up this issue! Besides, for suggestions or something else, head to [Pake's Discussions Platform](https://github.com/tw93/Pake/discussions). 21 | options: 22 | - label: > 23 | 我在 [issues](https://github.com/tw93/Pake/issues?q=) 列表中搜索,没有找到类似的内容。 24 | 25 | I searched in the [issues](https://github.com/tw93/Pake/issues?q=) and found nothing similar. 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Pake version 30 | description: > 31 | Please provide the version of Pake you are using. If you are using the master/dev branch, please provide the commit id. 32 | validations: 33 | required: true 34 | - type: textarea 35 | attributes: 36 | label: System version 37 | description: > 38 | Please provide the version of System you are using. 39 | validations: 40 | required: true 41 | - type: textarea 42 | attributes: 43 | label: Node.js version 44 | description: > 45 | Please provide the Node.js version. 46 | validations: 47 | required: true 48 | - type: textarea 49 | attributes: 50 | label: Minimal reproduce step 51 | description: Please try to give reproducing steps to facilitate quick location of the problem. 52 | validations: 53 | required: true 54 | - type: textarea 55 | attributes: 56 | label: What did you expect to see? 57 | validations: 58 | required: true 59 | - type: textarea 60 | attributes: 61 | label: What did you see instead? 62 | validations: 63 | required: true 64 | - type: textarea 65 | attributes: 66 | label: Anything else? 67 | - type: checkboxes 68 | attributes: 69 | label: Are you willing to submit a PR? 70 | description: > 71 | We look forward to the community of developers or users helping solve Pake problems together. If you are willing to submit a PR to fix this problem, please check the box. 72 | options: 73 | - label: I'm willing to submit a PR! 74 | - type: markdown 75 | attributes: 76 | value: "Thanks for completing our form!" 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question or get support 4 | url: https://github.com/tw93/Pake/discussions/categories/q-a 5 | about: Ask a question or request support for Pake 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature 2 | description: Add new feature, improve code, and more 3 | labels: [ "enhancement" ] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you very much for your feature proposal! 9 | - type: checkboxes 10 | attributes: 11 | label: Search before asking 12 | description: > 13 | Please search [issues](https://github.com/tw93/Pake/issues?q=) to check if your issue has already been reported. 14 | options: 15 | - label: > 16 | 我在 [issues](https://github.com/tw93/Pake/issues?q=) 列表中搜索,没有找到类似的内容。 17 | 18 | I searched in the [issues](https://github.com/tw93/Pake/issues?q=) and found nothing similar. 19 | required: true 20 | - type: textarea 21 | attributes: 22 | label: Motivation 23 | description: Describe the motivations for this feature, like how it fixes the problem you meet. 24 | validations: 25 | required: true 26 | - type: textarea 27 | attributes: 28 | label: Solution 29 | description: Describe the proposed solution and add related materials like links if any. 30 | - type: textarea 31 | attributes: 32 | label: Alternatives 33 | description: Describe other alternative solutions or features you considered, but rejected. 34 | - type: textarea 35 | attributes: 36 | label: Anything else? 37 | - type: checkboxes 38 | attributes: 39 | label: Are you willing to submit a PR? 40 | description: > 41 | We look forward to the community of developers or users helping develop Pake features together. If you are willing to submit a PR to implement the feature, please check the box. 42 | options: 43 | - label: I'm willing to submit a PR! 44 | - type: markdown 45 | attributes: 46 | value: "Thanks for completing our form!" 47 | -------------------------------------------------------------------------------- /.github/workflows/contribute_list.yml: -------------------------------------------------------------------------------- 1 | name: Build Contribute List 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | contrib-readme-job: 9 | runs-on: ubuntu-latest 10 | name: A job to automate contrib in readme 11 | steps: 12 | - name: Contribute List 13 | uses: akhilmhdh/contributors-readme-action@v2.3.6 14 | with: 15 | image_size: 90 16 | columns_per_row: 7 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/editorconfig-check.yml: -------------------------------------------------------------------------------- 1 | name: Check EditorConfig 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | actions: write 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | editorconfig-check: 18 | name: Run EditorConfig Check 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: editorconfig-checker/action-editorconfig-checker@main 23 | - run: editorconfig-checker -config .ecrc.json 24 | -------------------------------------------------------------------------------- /.github/workflows/pake-cli.yaml: -------------------------------------------------------------------------------- 1 | name: Build App with Pake Cli 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | platform: 6 | description: "platform" 7 | required: true 8 | default: "macos-latest" 9 | type: choice 10 | options: 11 | - "windows-latest" 12 | - "macos-latest" 13 | - "ubuntu-20.04" 14 | url: 15 | description: "[URL]" 16 | required: true 17 | name: 18 | description: "[Name]" 19 | required: true 20 | icon: 21 | description: "[Icon, Optional]" 22 | required: false 23 | height: 24 | description: "[Height, Optional]" 25 | required: false 26 | default: "780" 27 | width: 28 | description: "[Width, Optional]" 29 | required: false 30 | default: "1200" 31 | transparent: 32 | description: "[Transparent, Optional]" 33 | required: false 34 | type: boolean 35 | default: false 36 | # fullscreen: 37 | # description: "[FullScreen, Optional]" 38 | # required: false 39 | # type: boolean 40 | # default: false 41 | resize: 42 | description: "[Resize, Optional]" 43 | required: false 44 | type: boolean 45 | default: true 46 | multi_arch: 47 | description: "[MultiArch, Optional, MacOS only]" 48 | required: false 49 | type: boolean 50 | default: false 51 | targets: 52 | description: "[Targets, Optional, Linux only]" 53 | required: false 54 | default: "deb" 55 | type: choice 56 | options: 57 | - "deb" 58 | - "appimage" 59 | - "all" 60 | 61 | 62 | jobs: 63 | build: 64 | name: ${{ inputs.platform }} 65 | runs-on: ${{ inputs.platform }} 66 | strategy: 67 | fail-fast: false 68 | 69 | steps: 70 | - name: Checkout repository 71 | uses: actions/checkout@v3 72 | 73 | - name: install node 74 | uses: actions/setup-node@v3 75 | with: 76 | node-version: 18 77 | 78 | - name: Install Rust for ubuntu-20.04 79 | if: inputs.platform == 'ubuntu-20.04' 80 | uses: dtolnay/rust-toolchain@stable 81 | with: 82 | toolchain: stable 83 | target: x86_64-unknown-linux-musl 84 | 85 | - name: Install Rust for windows-latest 86 | if: inputs.platform == 'windows-latest' 87 | uses: dtolnay/rust-toolchain@stable 88 | with: 89 | toolchain: stable-x86_64-msvc 90 | target: x86_64-pc-windows-msvc 91 | 92 | - name: Install Rust for macos-latest 93 | if: inputs.platform == 'macos-latest' 94 | uses: dtolnay/rust-toolchain@stable 95 | with: 96 | toolchain: stable 97 | target: x86_64-apple-darwin 98 | 99 | - name: install dependencies (ubuntu only) 100 | if: inputs.platform == 'ubuntu-20.04' 101 | uses: awalsh128/cache-apt-pkgs-action@latest 102 | with: 103 | packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra 104 | version: 1.1 105 | 106 | - name: install pake-cli local 107 | shell: bash 108 | run: | 109 | echo "install pake on local" 110 | npm install pake-cli 111 | 112 | - name: rust cache restore 113 | uses: actions/cache/restore@v3 114 | id: cache_store 115 | with: 116 | path: | 117 | ~/.cargo/bin/ 118 | ~/.cargo/registry/index/ 119 | ~/.cargo/registry/cache/ 120 | ~/.cargo/git/db/ 121 | node_modules/pake-cli/src-tauri/target/ 122 | key: ${{ inputs.platform }}-cargo-${{ hashFiles('node_modules/pake-cli/src-tauri/Cargo.lock') }} 123 | 124 | - name: build with pake-cli 125 | shell: pwsh 126 | run: | 127 | pwsh ./script/build_with_pake_cli.ps1 128 | env: 129 | URL: ${{ inputs.url }} 130 | NAME: ${{ inputs.name }} 131 | ICON: ${{ inputs.icon }} 132 | HEIGHT: ${{ inputs.height }} 133 | WIDTH: ${{ inputs.width }} 134 | TRANSPARENT: ${{ inputs.transparent }} 135 | FULLSCREEN: ${{ inputs.fullscreen }} 136 | RESIZE: ${{ inputs.resize }} 137 | MULTI_ARCH: ${{ inputs.multi_arch }} 138 | TARGETS: ${{ inputs.targets }} 139 | 140 | - name: Upload archive 141 | uses: actions/upload-artifact@v3 142 | with: 143 | name: output-${{ inputs.platform }}.zip 144 | path: node_modules/pake-cli/output/* 145 | retention-days: 3 146 | 147 | - name: rust cache store 148 | uses: actions/cache/save@v3 149 | if: steps.cache_store.outputs.cache-hit != 'true' 150 | with: 151 | path: | 152 | ~/.cargo/bin/ 153 | ~/.cargo/registry/index/ 154 | ~/.cargo/registry/cache/ 155 | ~/.cargo/git/db/ 156 | node_modules/pake-cli/src-tauri/target/ 157 | key: ${{ inputs.platform }}-cargo-${{ hashFiles('node_modules/pake-cli/src-tauri/Cargo.lock') }} 158 | -------------------------------------------------------------------------------- /.github/workflows/pake_build.yaml: -------------------------------------------------------------------------------- 1 | name: Build App 2 | on: 3 | push: 4 | # Sequence of patterns matched against refs/tags 5 | tags: 6 | - "V*" 7 | 8 | jobs: 9 | build: 10 | name: build 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | build: [linux, windows, macos] 15 | include: 16 | - build: linux 17 | os: ubuntu-20.04 18 | rust: stable 19 | target: x86_64-unknown-linux-musl 20 | - build: windows 21 | os: windows-latest 22 | rust: stable-x86_64-msvc 23 | target: x86_64-pc-windows-msvc 24 | - build: macos 25 | os: macos-latest 26 | rust: stable 27 | target: x86_64-apple-darwin 28 | fail-fast: false 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v3 33 | 34 | - name: install node 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: 18 38 | 39 | - name: Install Rust 40 | uses: dtolnay/rust-toolchain@stable 41 | with: 42 | toolchain: ${{ matrix.rust }} 43 | target: ${{ matrix.target }} 44 | 45 | - name: install dependencies (ubuntu only) 46 | if: matrix.os == 'ubuntu-20.04' 47 | uses: awalsh128/cache-apt-pkgs-action@latest 48 | with: 49 | packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra 50 | version: 1.1 51 | 52 | - name: rust cache restore 53 | uses: actions/cache/restore@v3 54 | with: 55 | path: | 56 | ~/.cargo/bin/ 57 | ~/.cargo/registry/index/ 58 | ~/.cargo/registry/cache/ 59 | ~/.cargo/git/db/ 60 | src-tauri/target/ 61 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 62 | 63 | - name: build for Ubuntu 64 | if: matrix.os == 'ubuntu-20.04' 65 | run: npm run build:all-unix 66 | 67 | - name: build for MacOS 68 | if: matrix.os == 'macos-latest' 69 | run: | 70 | rustup target add aarch64-apple-darwin 71 | npm run build:all-unix 72 | 73 | - name: build for windows 74 | if: matrix.os == 'windows-latest' 75 | shell: pwsh 76 | run: | 77 | npm run build:all-windows 78 | 79 | - name: Upload files 80 | # arg info: https://github.com/ncipollo/release-action#release-action 81 | uses: ncipollo/release-action@v1 82 | with: 83 | allowUpdates: true 84 | artifacts: "output/*/*.*" 85 | token: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /.github/workflows/pake_build_with_cache.yaml: -------------------------------------------------------------------------------- 1 | name: Build App with Cache 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: "tag version" 7 | required: true 8 | default: "V0.0.1" 9 | is_pre_release: 10 | description: "pre-release or release, if true, is pre-release" 11 | required: true 12 | type: boolean 13 | default: true 14 | 15 | 16 | jobs: 17 | build: 18 | name: build 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | build: [linux, windows, macos] 23 | include: 24 | - build: linux 25 | os: ubuntu-20.04 26 | rust: stable 27 | target: x86_64-unknown-linux-musl 28 | # archive-name: target-linux.tar.gz 29 | - build: windows 30 | os: windows-latest 31 | rust: stable-x86_64-msvc 32 | target: x86_64-pc-windows-msvc 33 | # archive-name: target-windows.tar.gz 34 | - build: macos 35 | os: macos-latest 36 | rust: stable 37 | target: x86_64-apple-darwin 38 | # archive-name: target-macos.tar.gz 39 | fail-fast: false 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v3 44 | 45 | - name: install node 46 | uses: actions/setup-node@v3 47 | with: 48 | node-version: 18 49 | 50 | - name: Install Rust 51 | uses: dtolnay/rust-toolchain@stable 52 | with: 53 | toolchain: ${{ matrix.rust }} 54 | target: ${{ matrix.target }} 55 | 56 | - name: install dependencies (ubuntu only) 57 | if: matrix.os == 'ubuntu-20.04' 58 | uses: awalsh128/cache-apt-pkgs-action@latest 59 | with: 60 | packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra 61 | version: 1.1 62 | 63 | - name: rust cache restore 64 | uses: actions/cache/restore@v3 65 | id: cache_store 66 | with: 67 | path: | 68 | ~/.cargo/bin/ 69 | ~/.cargo/registry/index/ 70 | ~/.cargo/registry/cache/ 71 | ~/.cargo/git/db/ 72 | src-tauri/target/ 73 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 74 | 75 | - name: build for Ubuntu 76 | if: matrix.os == 'ubuntu-20.04' 77 | run: npm run build:all-unix 78 | 79 | - name: build for MacOS 80 | if: matrix.os == 'macos-latest' 81 | run: | 82 | rustup target add aarch64-apple-darwin 83 | npm run build:all-unix 84 | 85 | - name: build for windows 86 | if: matrix.os == 'windows-latest' 87 | shell: pwsh 88 | run: | 89 | npm run build:all-windows 90 | 91 | - name: Upload files 92 | # arg info: https://github.com/ncipollo/release-action#release-action 93 | uses: ncipollo/release-action@v1 94 | with: 95 | allowUpdates: true 96 | prerelease: ${{ inputs.is_pre_release }} 97 | artifacts: "output/*/*.*" 98 | tag: ${{ inputs.version }} 99 | token: ${{ secrets.GITHUB_TOKEN }} 100 | 101 | - name: rust cache store 102 | uses: actions/cache/save@v3 103 | if: steps.cache_store.outputs.cache-hit != 'true' 104 | with: 105 | path: | 106 | ~/.cargo/bin/ 107 | ~/.cargo/registry/index/ 108 | ~/.cargo/registry/cache/ 109 | ~/.cargo/git/db/ 110 | src-tauri/target/ 111 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 112 | -------------------------------------------------------------------------------- /.github/workflows/rust-code-quality-check.yml: -------------------------------------------------------------------------------- 1 | name: Check Rust Code 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | actions: write 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | defaults: 17 | run: 18 | shell: bash 19 | working-directory: src-tauri 20 | 21 | jobs: 22 | cargo-test: 23 | name: Test codebase on ${{ matrix.os }} (cargo test) 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | matrix: 27 | os: 28 | - windows-latest 29 | - ubuntu-latest 30 | - macos-latest 31 | fail-fast: false 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: actions-rust-lang/setup-rust-toolchain@v1 35 | - uses: rui314/setup-mold@v1 36 | - uses: taiki-e/install-action@v1 37 | with: 38 | tool: cargo-hack,nextest 39 | - name: Install dependencies for Ubuntu 40 | if: matrix.os == 'ubuntu-latest' 41 | uses: awalsh128/cache-apt-pkgs-action@latest 42 | with: 43 | packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra 44 | version: 1.0 45 | - name: Run unit & integration tests with nextest 46 | run: cargo hack --feature-powerset nextest run 47 | # - name: Run documentation tests with cargo test 48 | # run: cargo hack --feature-powerset test --doc 49 | 50 | cargo-clippy: 51 | name: Check codebase quality (cargo clippy) 52 | runs-on: ${{ matrix.os }} 53 | strategy: 54 | matrix: 55 | os: 56 | - windows-latest 57 | - ubuntu-latest 58 | - macos-latest 59 | fail-fast: false 60 | steps: 61 | - uses: actions/checkout@v3 62 | - uses: actions-rust-lang/setup-rust-toolchain@v1 63 | with: 64 | components: clippy 65 | - uses: taiki-e/install-action@cargo-hack 66 | - name: Install dependencies for Ubuntu 67 | if: matrix.os == 'ubuntu-latest' 68 | uses: awalsh128/cache-apt-pkgs-action@latest 69 | with: 70 | packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra 71 | version: 1.0 72 | - name: Run all-features code quality checks 73 | run: cargo hack --feature-powerset --no-dev-deps clippy 74 | - name: Run normal code quality check 75 | run: cargo clippy 76 | 77 | cargo-fmt: 78 | name: Enforce codebase style (cargo fmt) 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: actions/checkout@v3 82 | - uses: actions-rust-lang/setup-rust-toolchain@v1 83 | with: 84 | components: rustfmt 85 | - run: cargo fmt --all -- --color=always --check 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist-ssr 12 | *.local 13 | 14 | # Editor directories and files 15 | .vscode 16 | .idea 17 | .DS_Store 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | .npmrc 24 | output 25 | *.msi 26 | *.deb 27 | *.AppImage 28 | *.dmg 29 | 30 | package-lock.json 31 | yarn.lock 32 | pnpm-lock.yaml 33 | dist 34 | !dist/about_pake.html 35 | !dist/cli.js 36 | !dist/.gitkeep 37 | src-tauri/.cargo/config 38 | src-tauri/.cargo/ 39 | .next 40 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | dist/**/* 4 | !dist/twitter.css 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of erotic language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | tw93@qq.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to Pake 2 | 3 | **Welcome to create [pull requests](https://github.com/tw93/Pake/compare/) for bugfix, new component, doc, example, suggestion and anything.** 4 | 5 | ## Branch Management 6 | 7 | ```mermaid 8 | graph LR 9 | b_dev(dev) --> b_master(master); 10 | contributions([Develop / Pull requests]) -.-> b_dev; 11 | ``` 12 | 13 | - `dev` branch 14 | - `dev` is the developing branch. 15 | - It's **RECOMMENDED** to commit feature PR to `dev`. 16 | - `master` branch 17 | - `master` is the release branch, we will make tag and publish version on this branch. 18 | - If it is a document modification, it can be submitted to this branch. 19 | 20 | ## Commit Log 21 | 22 | please use 23 | 24 | ## More 25 | 26 | It is a good habit to create a feature request issue to discuss whether the feature is necessary before you implement it. However, it's unnecessary to create an issue to claim that you found a typo or improved the readability of documentation, just create a pull request. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tw93 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 |

English | 简体中文

2 |

3 | 4 |

5 |

Pake

6 |

很简单的用 Rust 打包网页生成很小的桌面App

7 |
8 | 9 | twitter 10 | 11 | telegram 12 | 13 | GitHub downloads 14 | 15 | GitHub commit 16 | 17 | GitHub closed issues 18 |
19 |
支持 Mac / Windows / Linux,关于 常用包下载命令行一键打包定制开发 可见下面文档,也欢迎去 讨论区 交流。
20 | 21 | ## 特征 22 | 23 | - 🎐 相比传统的 Electron 套壳打包,要小将近 20 倍,5M 上下。 24 | - 🚀 Pake 的底层使用的 Rust Tauri 框架,性能体验较 JS 框架要轻快不少,内存小很多。 25 | - 📦 不是单纯打包,实现了快捷键的透传、沉浸式的窗口、拖动、样式改写、去广告、产品的极简风格定制。 26 | - 👻 只是一个很简单的小玩具,用 Rust 替代之前套壳网页打包的老思路,其实 PWA 也很好。 27 | 28 | ## 常用包下载 29 | 30 | 31 | 32 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 53 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 69 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 101 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 117 | 122 | 123 | 124 | 125 | 126 | 127 |
WeRead 33 | Mac 34 | Windows 35 | Linux 36 | Twitter 38 | Mac 39 | Windows 40 | Linux 41 |
ChatGPT 49 | Mac 50 | Windows 51 | Linux 52 | Poe 54 | Mac 55 | Windows 56 | Linux 57 |
LiZhi 65 | Mac 66 | Windows 67 | Linux 68 | ProgramMusic 70 | Mac 71 | Windows 72 | Linux 73 |
YouTube 81 | Mac 82 | Windows 83 | Linux 84 | XiaoHongShu 86 | Mac 87 | Windows 88 | Linux 89 |
Flomo 97 | Mac 98 | Windows 99 | Linux 100 | Qwerty 102 | Mac 103 | Windows 104 | Linux 105 |
Reference 113 | Mac 114 | Windows 115 | Linux 116 | CodeRunner 118 | Mac 119 | Windows 120 | Linux 121 |
128 | 129 |
130 | 131 | 🏂 更多应用可去 Release下载,此外点击可展开快捷键说明 132 | 133 |
134 | 135 | | Mac | Windows/Linux | 功能 | 136 | |-----------------------------|--------------------------------|-----------| 137 | | + [ | Ctrl + | 返回上一个页面 | 138 | | + ] | Ctrl + | 去下一个页面 | 139 | | + | Ctrl + | 自动滚动到页面顶部 | 140 | | + | Ctrl + | 自动滚动到页面底部 | 141 | | + r | Ctrl + r | 刷新页面 | 142 | | + w | Ctrl + w | 隐藏窗口,非退出 | 143 | | + - | Ctrl + - | 缩小页面 | 144 | | + + | Ctrl + + | 放大页面 | 145 | | + = | Ctrl + = | 放大页面 | 146 | | + 0 | Ctrl + 0 | 重置页面缩放 | 147 | 148 | 此外还支持双击头部进行全屏切换,拖拽头部进行移动窗口,Mac 用户支持手势方式返回和去下一页,还有其他需求,欢迎提过来。 149 | 150 |
151 | 152 | ## 开始之前 153 | 154 | 1. **小白用户**:使用 「常用包下载」 方式来把玩 Pake 的能力,可去 [讨论群](https://github.com/tw93/Pake/discussions) 寻求帮助,也可试试 [Action](https://github.com/tw93/Pake/wiki/%E5%9C%A8%E7%BA%BF%E7%BC%96%E8%AF%91%EF%BC%88%E6%99%AE%E9%80%9A%E7%94%A8%E6%88%B7%E4%BD%BF%E7%94%A8%EF%BC%89) 方式。 155 | 2. **开发用户**:使用 「命令行一键打包」,对 Mac 比较友好,Windows / Linux 需折腾下 [环境配置](https://tauri.app/v1/guides/getting-started/prerequisites)。 156 | 3. **折腾用户**:假如你前端和 Rust 都会,那可试试下面的 「[定制开发](#定制开发)」,可深度二次开发定制你的功能。 157 | 158 | ## 命令行一键打包 159 | 160 | 161 | 162 | 163 |

164 | 165 | **Pake 提供了命令行工具,可以更快捷方便地一键自定义打你需要的包,详细可见 [文档](./bin/README.md)。** 166 | 167 | ```bash 168 | # 使用 npm 进行安装 169 | npm install -g pake-cli 170 | 171 | # 命令使用 172 | pake url [OPTIONS]... 173 | 174 | # 随便玩玩,首次由于安装环境会有些慢,后面就快了 175 | pake https://weekly.tw93.fun --name Weekly --transparent 176 | ``` 177 | 178 | 假如你不太会使用命令行,或许使用 **GitHub Actions 在线编译多系统版本** 是一个不错的选择,可查看 [文档](https://github.com/tw93/Pake/wiki/%E5%9C%A8%E7%BA%BF%E7%BC%96%E8%AF%91%EF%BC%88%E6%99%AE%E9%80%9A%E7%94%A8%E6%88%B7%E4%BD%BF%E7%94%A8%EF%BC%89)。 179 | 180 | ## 定制开发 181 | 182 | 开始前请确保电脑已经安装了 Rust `>=1.63` 和 Node `>=16 如 16.18.1` 的环境,此外需参考 [Tauri 文档](https://tauri.app/v1/guides/getting-started/prerequisites) 快速配置好环境才可以开始使用,假如你太不懂,使用上面的命令行打包会更加合适。 183 | 184 | ```sh 185 | # 安装依赖 186 | npm i 187 | 188 | # 本地开发[右键可打开调试模式] 189 | npm run dev 190 | 191 | # 打包应用 192 | npm run build 193 | 194 | ``` 195 | 196 | ## 高级使用 197 | 198 | 1. 代码结构可参考 [文档](https://github.com/tw93/Pake/wiki/Pake-%E7%9A%84%E4%BB%A3%E7%A0%81%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E),便于你在开发前了解更多。 199 | 2. 修改 src-tauri 目录下 `pake.json` 中的 `url` 和 `productName` 字段,以及 `tauri.xxx.conf.json` 中的 `icon` 和 `identifier` 字段,其中 `icon` 可以从 icons 目录选择一个,也可以去 [macOSicons](https://macosicons.com/#/) 下载符合效果的。 200 | 3. 关于窗口属性设置,可以在 `pake.json` 修改 windows 属性对应的 `width/height`,fullscreen 是否全屏,resizable 是否可以调整大小,假如想适配 Mac 沉浸式头部,可以将 transparent 设置成 `true`,找到 Header 元素加一个 padding-top 样式即可,不想适配改成 `false` 也行。 201 | 4. 此外样式改写、屏蔽广告、逻辑代码注入、容器消息通信、自定义快捷键可见 [高级用法](https://github.com/tw93/Pake/wiki/Pake-%E7%9A%84%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95)。 202 | 203 | ## 开发者 204 | 205 | Pake 的发展离不开这些 Hacker 们,一起贡献了大量能力,也欢迎关注他们 ❤️ 206 | 207 | 208 | 209 | 210 | 217 | 224 | 231 | 238 | 245 | 252 | 259 | 260 | 267 | 274 | 281 | 288 | 295 | 302 | 309 | 310 | 317 | 324 | 331 |
211 | 212 | tw93 213 |
214 | Tw93 215 |
216 |
218 | 219 | Tlntin 220 |
221 | Tlntin 222 |
223 |
225 | 226 | pan93412 227 |
228 | Pan93412 229 |
230 |
232 | 233 | wanghanzhen 234 |
235 | Volare 236 |
237 |
239 | 240 | liby 241 |
242 | Bryan Lee 243 |
244 |
246 | 247 | essesoul 248 |
249 | Essesoul 250 |
251 |
253 | 254 | m1911star 255 |
256 | Horus 257 |
258 |
261 | 262 | AielloChan 263 |
264 | Aiello 265 |
266 |
268 | 269 | QingZ11 270 |
271 | Steam 272 |
273 |
275 | 276 | 2nthony 277 |
278 | 2nthony 279 |
280 |
282 | 283 | nekomeowww 284 |
285 | Ayaka Neko 286 |
287 |
289 | 290 | turkyden 291 |
292 | Dengju Deng 293 |
294 |
296 | 297 | Fechin 298 |
299 | Fechin 300 |
301 |
303 | 304 | princemaple 305 |
306 | Po Chen 307 |
308 |
311 | 312 | houhoz 313 |
314 | Hyzhao 315 |
316 |
318 | 319 | liusishan 320 |
321 | Liusishan 322 |
323 |
325 | 326 | piaoyidage 327 |
328 | Ranger 329 |
330 |
332 | 333 | 334 | ## 支持 335 | 336 | 1. 我有两只猫,一只叫汤圆,一只叫可乐,假如觉得 Pake 让你生活更美好,可以给汤圆可乐 喂罐头 🥩。 337 | 2. 如果你喜欢 Pake,可以在 Github Star,更欢迎 [推荐](https://twitter.com/intent/tweet?url=https://github.com/tw93/Pake&text=%23Pake%20%E4%B8%80%E4%B8%AA%E5%BE%88%E7%AE%80%E5%8D%95%E7%9A%84%E7%94%A8%20Rust%20%E6%89%93%E5%8C%85%E7%BD%91%E9%A1%B5%E7%94%9F%E6%88%90%20Mac%20App%20%E7%9A%84%E5%B7%A5%E5%85%B7%EF%BC%8C%E7%9B%B8%E6%AF%94%E4%BC%A0%E7%BB%9F%E7%9A%84%20Electron%20%E5%A5%97%E5%A3%B3%E6%89%93%E5%8C%85%EF%BC%8C%E5%A4%A7%E5%B0%8F%E8%A6%81%E5%B0%8F%E5%B0%86%E8%BF%91%2040%20%E5%80%8D%EF%BC%8C%E4%B8%80%E8%88%AC%202M%20%E5%B7%A6%E5%8F%B3%EF%BC%8C%E5%BA%95%E5%B1%82%E4%BD%BF%E7%94%A8Tauri%20%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BD%93%E9%AA%8C%E8%BE%83%20JS%20%E6%A1%86%E6%9E%B6%E8%A6%81%E8%BD%BB%E5%BF%AB%E4%B8%8D%E5%B0%91%EF%BC%8C%E5%86%85%E5%AD%98%E5%B0%8F%E5%BE%88%E5%A4%9A%EF%BC%8C%E6%94%AF%E6%8C%81%E5%BE%AE%E4%BF%A1%E8%AF%BB%E4%B9%A6%E3%80%81Twitter%E3%80%81Youtube%E3%80%81RunCode%E3%80%81Flomo%E3%80%81%E8%AF%AD%E9%9B%80%E7%AD%89%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%BE%88%E6%96%B9%E4%BE%BF%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91~) 给你志同道合的朋友使用。 338 | 3. 可以关注我的 [Twitter](https://twitter.com/HiTw93) 获取到最新的 Pake 更新消息,也欢迎加入 [Telegram](https://t.me/+GclQS9ZnxyI2ODQ1) 聊天群。 339 | 4. 希望大伙玩的过程中有一种学习新技术的喜悦感,假如你发现有很适合做成桌面 App 的网页也很欢迎告诉我。 340 | -------------------------------------------------------------------------------- /app.csv: -------------------------------------------------------------------------------- 1 | name(Linux),name(Mac/Windows),name_zh,url 2 | ails,AILS,AILS,https://1.gpt99.xyz/ 3 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | ## 安装 2 | 3 | 请确保 Node 版本>=16 如 16.8,不要使用 sudo 进行安装,假如 npm 报没有权限可以参考 [How to fix npm throwing error without sudo](https://stackoverflow.com/questions/16151018/how-to-fix-npm-throwing-error-without-sudo)。 4 | 5 | ```bash 6 | npm install -g pake-cli 7 | ``` 8 | 9 | ## Windows/Linux 注意点 10 | 11 | - **十分重要** 查看 Tauri 提供的[依赖指南](https://tauri.app/v1/guides/getting-started/prerequisites) 12 | - 对于 windows(至少安装了`Win10 SDK(10.0.19041.0)` 与`Visual Studio build tool 2022(>=17.2)`),还需要额外安装: 13 | 14 | 1. Microsoft Visual C++ 2015-2022 Redistributable (x64) 15 | 2. Microsoft Visual C++ 2015-2022 Redistributable (x86) 16 | 3. Microsoft Visual C++ 2012 Redistributable (x86)(可选) 17 | 4. Microsoft Visual C++ 2013 Redistributable (x86)(可选) 18 | 5. Microsoft Visual C++ 2008 Redistributable (x86)(可选) 19 | 20 | - 此外 Ubuntu 在开始之前可以运行如下命令,安装前期所需依赖。 21 | 22 | ```bash 23 | sudo apt install libdbus-1-dev \ 24 | libsoup2.4-dev \ 25 | libjavascriptcoregtk-4.0-dev \ 26 | libwebkit2gtk-4.0-dev \ 27 | build-essential \ 28 | curl \ 29 | wget \ 30 | libssl-dev \ 31 | libgtk-3-dev \ 32 | libayatana-appindicator3-dev \ 33 | librsvg2-dev \ 34 | gnome-video-effects \ 35 | gnome-video-effects-extra 36 | ``` 37 | 38 | ## 用法 39 | 40 | ```bash 41 | pake url [options] 42 | ``` 43 | 44 | 打包完成后的应用程序默认为当前工作目录,首次打包由于需配置好环境,需要一些时间,请耐心等待即可。 45 | 46 | > **Note**: 47 | > 打包需要用 `Rust` 环境,如果没有 `Rust`,会提示确认安装。如遇安装失败或超时,可[自行安装](https://www.rust-lang.org/tools/install)。 48 | 49 | ### url 50 | 51 | url 为你需要打包的网页链接 🔗 或者本地 html 文件,必须提供。 52 | 53 | ### [options] 54 | 55 | 提供了一些特定的选项,打包时可以传递对应参数达到定制化的效果。 56 | 57 | #### [name] 58 | 59 | 应用名称,如输入时未指定,会提示你输入,尽量使用英语。 60 | 61 | ```shell 62 | --name 63 | # 或者 64 | -n 65 | ``` 66 | 67 | #### [icon] 68 | 69 | 应用 icon,支持本地/远程文件,默认为 Pake 自带图标,定制的可以去 [icon-icons](https://icon-icons.com) 或 [macOSicons](https://macosicons.com/#/) 搜索下载。 70 | 71 | - MacOS 下必须为 `.icns` 72 | - Windows 下必须为 `.ico` 73 | - Linux 下必须为 `.png` 74 | 75 | ```shell 76 | --icon 77 | # 或者 78 | -i 79 | ``` 80 | 81 | #### [height] 82 | 83 | 打包后的应用窗口高度,默认 `780px`。 84 | 85 | ```shell 86 | --height 87 | # 或者 88 | -h 89 | ``` 90 | 91 | #### [width] 92 | 93 | 打包后的应用窗口宽度,默认 `1200px`。 94 | 95 | ```shell 96 | --width 97 | # 或者 98 | -w 99 | ``` 100 | 101 | #### [transparent] 102 | 103 | 是否开启沉浸式头部,默认为 `false` 不开启,输入下面的命令则开启沉浸式,推荐 MacOS 用户开启。 104 | 105 | ```shell 106 | --transparent 107 | ``` 108 | 109 | #### [resize] 110 | 111 | 是否可以拖动大小,默认为 `true` 可拖动,输入下面的命令则不能对窗口大小进行拉伸。 112 | 113 | ```shell 114 | --no-resizable 115 | ``` 116 | 117 | #### [fullscreen] 118 | 119 | 打开应用后是否开启全屏,默认为 `false`,输入下面的命令则会自动全屏。 120 | 121 | ```shell 122 | --fullscreen 123 | ``` 124 | 125 | #### [multi-arch] 126 | 127 | 打包结果同时支持英特尔和 m1 芯片,仅适用于 MacOS,默认为 `false`。 128 | 129 | ##### 准备工作 130 | 131 | - 注意:开启该选项后,需要用 rust 官网的 rustup 安装 rust,不支持 brew 安装。 132 | - 对于 intel 芯片用户,需要安装 arm64 跨平台包,使安装包支持 m1 芯片,使用下面命令安装。 133 | 134 | ```shell 135 | rustup target add aarch64-apple-darwin 136 | ``` 137 | 138 | - 对于 M1 芯片用户,需要安装 x86 跨平台包,使安装包支持 interl 芯片,使用下面的命令安装。 139 | 140 | ```shell 141 | rustup target add x86_64-apple-darwin 142 | ``` 143 | 144 | ##### 使用方法 145 | 146 | ```shell 147 | --multi-arch 148 | # 或者 149 | -m 150 | ``` 151 | 152 | #### [targets] 153 | 154 | 选择输出的包格式,支持 deb/appimage/all,如果选择 all,则同时打包 deb 和 appimage,该选项仅支持 Linux,默认为`all`。 155 | 156 | ```shell 157 | --targets xxx 158 | ``` 159 | 160 | #### [user-agent] 161 | 162 | 自定义浏览器请求头, 默认为空。 163 | 164 | ```shell 165 | --user-agent 166 | ``` 167 | 168 | #### [show-menu] 169 | 170 | 显示菜单栏, 默认不显示,输入下面的命令则会显示,推荐MacOS用户开启。 171 | 172 | ```shell 173 | --show-menu 174 | ``` 175 | 176 | #### [show-system-tray] 177 | 178 | 显示通知栏托盘, 默认不显示,输入下面的命令则会显示。 179 | 180 | ```shell 181 | --show-system-tray 182 | ``` 183 | 184 | #### [system-tray-icon] 185 | 186 | 通知栏托盘图标,仅当显示通知栏托盘时有效, 图标必须为.ico或者.png格式的,512*512像素的图片。 187 | 188 | ```shell 189 | --system-tray-icon 190 | ``` 191 | 192 | 193 | #### [copy-iter-file] 194 | 195 | 递归拷贝,当url为本地文件路径时候,若开启该选项,则将url路径文件所在文件夹以及所有子文件都拷贝到pake静态文件夹,默认不开启 196 | 197 | ```shell 198 | --copy-iter-file 199 | ``` 200 | -------------------------------------------------------------------------------- /bin/README_EN.md: -------------------------------------------------------------------------------- 1 | ## Install 2 | 3 | Ensure the version of your installed Node.js is greater than `16.0` such as `16.8`. Do not use `sudo` to install. If you encountered permission issues/problems while installing using npm, see [How to fix npm throwing error without sudo](https://stackoverflow.com/questions/16151018/how-to-fix-npm-throwing-error-without-sudo). 4 | 5 | ```bash 6 | npm install -g pake-cli 7 | ``` 8 | 9 | ## Notes for Windows & Linux users 10 | 11 | - **VERY IMPORTANT**: Check out [the Tauri prerequisites](https://tauri.app/v1/guides/getting-started/prerequisites) before proceeding. 12 | - For Windows users who had been installed `Win10 SDK (10.0.19041.0)` and `Visual Studio build tool 2022(>=17.2)`, you may need to install these additionally: 13 | 14 | 1. Microsoft Visual C++ 2015-2022 Redistributable (x64) 15 | 2. Microsoft Visual C++ 2015-2022 Redistributable (x86) 16 | 3. Microsoft Visual C++ 2012 Redistributable (x86) (optional) 17 | 4. Microsoft Visual C++ 2013 Redistributable (x86) (optional) 18 | 5. Microsoft Visual C++ 2008 Redistributable (x86) (optional) 19 | 20 | - For Ubuntu users, run the following commands to install the required libraries before compiling: 21 | 22 | ```bash 23 | sudo apt install libdbus-1-dev \ 24 | libsoup2.4-dev \ 25 | libjavascriptcoregtk-4.0-dev \ 26 | libwebkit2gtk-4.0-dev \ 27 | build-essential \ 28 | curl \ 29 | wget \ 30 | libssl-dev \ 31 | libgtk-3-dev \ 32 | libayatana-appindicator3-dev \ 33 | librsvg2-dev \ 34 | gnome-video-effects \ 35 | gnome-video-effects-extra 36 | ``` 37 | 38 | ## Usage 39 | 40 | ```bash 41 | pake url [options] 42 | ``` 43 | 44 | The packaged application will be placed in the current working directory by default. Since the environment needs to be configured for the first packaging, it will take some time. Please wait patiently. 45 | 46 | > **Note**: 47 | > The Rust environment is required for packaging. If you have not installed Rust, you will be prompted to confirm the installation. If the installation fails or times out, you can [install](https://www.rust-lang.org/tools/install) by yourself. 48 | 49 | ### url 50 | 51 | The url🔗 is the link to the website you want to package. Required. 52 | 53 | ### [options] 54 | 55 | We provide some options for customization. When packaging, the corresponding arguments can be passed to configure your app. 56 | 57 | #### [name] 58 | 59 | The name of your application. We will prompt you to enter this if you do not provide it in this phase. Input must be in English. 60 | 61 | ```shell 62 | --name 63 | # or 64 | -n 65 | ``` 66 | 67 | #### [icon] 68 | 69 | The application icon. Supports local and remote files. By default, it is the brand icon of Pake. For customizing the icon of your product, go to [icon icons](https://icon-icons.com) or [macOSicons](https://macosicons.com/#/) to download it. 70 | 71 | - macOS must be `.icns` 72 | - Windows must be `.ico` 73 | - Linux must be `.png` 74 | 75 | ```shell 76 | --icon 77 | # or 78 | -i 79 | ``` 80 | 81 | #### [height] 82 | 83 | The height of the packaged application window. The default is `780px`. 84 | 85 | ```shell 86 | --height 87 | # or 88 | -h 89 | ``` 90 | 91 | #### [width] 92 | 93 | The width of the packaged application window. The default is `1200px`. 94 | 95 | ```shell 96 | --width 97 | # or 98 | -w 99 | ``` 100 | 101 | #### [transparent] 102 | 103 | Whether to enable the immersive header. The default is `false`. Use the command below to enable this feature. 104 | 105 | ```shell 106 | --transparent 107 | ``` 108 | 109 | #### [fullscreen] 110 | 111 | Indicates if the window should be full screen on application launch. The default is `false`. 112 | Use the command below to enable this feature. 113 | 114 | ```shell 115 | --fullscreen 116 | ``` 117 | 118 | #### [resize] 119 | 120 | Indicates if the window can be resized. The default value is `true`. 121 | Use the command below to disable this feature. 122 | 123 | ```shell 124 | --no-resizable 125 | #or 126 | -r 127 | ``` 128 | 129 | #### [multi-arch] 130 | 131 | Package results support both Intel and m1 chips, only for MacOS. The default is `false`. 132 | 133 | ```shell 134 | --targets xxx 135 | ``` 136 | 137 | ##### Preparation 138 | 139 | - Note: After enabling this option, you need to use rustup on the rust official website to install rust, brew installation is not supported. 140 | - For intel chip users, you need to install the arm64 cross-platform package to make the installation package support the m1 chip, and use the following command to install. 141 | 142 | ```shell 143 | rustup target add aarch64-apple-darwin 144 | ``` 145 | 146 | - For M1 chip users, you need to install the x86 cross-platform package to make the installation package support the interl chip, and use the following command to install. 147 | 148 | ```shell 149 | rustup target add x86_64-apple-darwin 150 | ``` 151 | 152 | ##### Instructions 153 | 154 | ```shell 155 | --multi-arch 156 | # or 157 | -m 158 | ``` 159 | 160 | #### [targets] 161 | 162 | Select the output package format, support deb/appimage/all, if all is selected, deb and appimage will be packaged at the same time, this option only supports Linux, the default is `all`. 163 | 164 | 165 | #### [user-agent] 166 | 167 | Custom browser user agent, default is empty. 168 | ```shell 169 | --user-agent 170 | ``` 171 | 172 | #### [show-menu] 173 | 174 | Display the menu bar, not display it by default, enter the following command and it will be displayed. MacOS users are recommended to enable. 175 | 176 | ```shell 177 | --show-menu 178 | ``` 179 | 180 | #### [show-system-tray] 181 | 182 | Display the notification tray, not display it by default, entering the following command will display. 183 | 184 | ```shell 185 | --show-system-tray 186 | ``` 187 | 188 | #### [system-tray-icon] 189 | 190 | The notification tray icon is only valid when the notification tray is displayed. The icon must be a 512*512 pixel image in .ico or .png format. 191 | 192 | ```shell 193 | --system-tray-icon 194 | ``` 195 | 196 | #### [copy-iter-file] 197 | 198 | Recursive copy, when the url is a local file path, if this option is enabled, the folder where the url path file is located and all sub-files are copied to the pake static folder, which is not enabled by default 199 | 200 | ```shell 201 | --copy-iter-file 202 | ``` 203 | -------------------------------------------------------------------------------- /bin/builders/BuilderFactory.ts: -------------------------------------------------------------------------------- 1 | import { IS_MAC, IS_WIN, IS_LINUX } from '@/utils/platform.js'; 2 | import { IBuilder } from './base.js'; 3 | import MacBuilder from './MacBuilder.js'; 4 | import WinBuilder from './WinBulider.js'; 5 | import LinuxBuilder from './LinuxBuilder.js'; 6 | 7 | export default class BuilderFactory { 8 | static create(): IBuilder { 9 | if (IS_MAC) { 10 | return new MacBuilder(); 11 | } 12 | if (IS_WIN) { 13 | return new WinBuilder(); 14 | } 15 | if (IS_LINUX) { 16 | return new LinuxBuilder(); 17 | } 18 | throw new Error('The current system does not support!!'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bin/builders/LinuxBuilder.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import prompts from 'prompts'; 4 | import { checkRustInstalled, installRust } from '@/helpers/rust.js'; 5 | import { PakeAppOptions } from '@/types.js'; 6 | import { IBuilder } from './base.js'; 7 | import { shellExec } from '@/utils/shell.js'; 8 | import {isChinaDomain} from '@/utils/ip_addr.js'; 9 | // @ts-expect-error 加上resolveJsonModule rollup会打包报错 10 | // import tauriConf from '../../src-tauri/tauri.windows.conf.json'; 11 | import tauriConf from './tauriConf.js'; 12 | 13 | import { fileURLToPath } from 'url'; 14 | import logger from '@/options/logger.js'; 15 | import { mergeTauriConfig } from './common.js'; 16 | import { npmDirectory } from '@/utils/dir.js'; 17 | 18 | export default class LinuxBuilder implements IBuilder { 19 | async prepare() { 20 | logger.info( 21 | 'To build the Linux app, you need to install Rust and Linux package' 22 | ); 23 | logger.info( 24 | 'See more in https://tauri.app/v1/guides/getting-started/prerequisites#installing\n' 25 | ); 26 | if (checkRustInstalled()) { 27 | return; 28 | } 29 | 30 | const res = await prompts({ 31 | type: 'confirm', 32 | message: 'We detected that you have not installed Rust. Install it now?', 33 | name: 'value', 34 | }); 35 | 36 | if (res.value) { 37 | // TODO 国内有可能会超时 38 | await installRust(); 39 | } else { 40 | logger.error('Error: Pake needs Rust to package your webapp!!!'); 41 | process.exit(2); 42 | } 43 | } 44 | 45 | async build(url: string, options: PakeAppOptions) { 46 | logger.debug('PakeAppOptions', options); 47 | const { name } = options; 48 | await mergeTauriConfig(url, options, tauriConf); 49 | const isChina = await isChinaDomain("www.npmjs.com"); 50 | if (isChina) { 51 | logger.info("it's in China, use npm/rust cn mirror") 52 | const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo"); 53 | const e1 = fs.access(rust_project_dir); 54 | if (e1) { 55 | await fs.mkdir(rust_project_dir, { recursive: true }); 56 | } 57 | const project_cn_conf = path.join(npmDirectory, "src-tauri", "cn_config.bak"); 58 | const project_conf = path.join(rust_project_dir, "config"); 59 | fs.copyFile(project_cn_conf, project_conf); 60 | 61 | const _ = await shellExec( 62 | `cd "${npmDirectory}" && npm install --registry=https://registry.npmmirror.com && npm run build` 63 | ); 64 | } else { 65 | const _ = await shellExec(`cd "${npmDirectory}" && npm install && npm run build`); 66 | } 67 | let arch: string; 68 | if (process.arch === "x64") { 69 | arch = "amd64"; 70 | } else { 71 | arch = process.arch; 72 | } 73 | if (options.targets === "deb" || options.targets === "all") { 74 | const debName = `${name}_${tauriConf.package.version}_${arch}.deb`; 75 | const appPath = this.getBuildAppPath(npmDirectory, "deb", debName); 76 | const distPath = path.resolve(`${name}.deb`); 77 | await fs.copyFile(appPath, distPath); 78 | await fs.unlink(appPath); 79 | logger.success('Build Deb success!'); 80 | logger.success('You can find the deb app installer in', distPath); 81 | } 82 | if (options.targets === "appimage" || options.targets === "all") { 83 | const appImageName = `${name}_${tauriConf.package.version}_${arch}.AppImage`; 84 | const appImagePath = this.getBuildAppPath(npmDirectory, "appimage", appImageName); 85 | const distAppPath = path.resolve(`${name}.AppImage`); 86 | await fs.copyFile(appImagePath, distAppPath); 87 | await fs.unlink(appImagePath); 88 | logger.success('Build AppImage success!'); 89 | logger.success('You can find the AppImage app installer in', distAppPath); 90 | } 91 | } 92 | 93 | getBuildAppPath(npmDirectory: string, packageType: string, packageName: string) { 94 | return path.join( 95 | npmDirectory, 96 | 'src-tauri/target/release/bundle/', 97 | packageType, 98 | packageName 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /bin/builders/MacBuilder.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import prompts from 'prompts'; 4 | import { checkRustInstalled, installRust } from '@/helpers/rust.js'; 5 | import { PakeAppOptions } from '@/types.js'; 6 | import { IBuilder } from './base.js'; 7 | import { shellExec } from '@/utils/shell.js'; 8 | // @ts-expect-error 加上resolveJsonModule rollup会打包报错 9 | // import tauriConf from '../../src-tauri/tauri.macos.conf.json'; 10 | import tauriConf from './tauriConf.js'; 11 | import log from 'loglevel'; 12 | import { mergeTauriConfig } from './common.js'; 13 | import { npmDirectory } from '@/utils/dir.js'; 14 | import {isChinaDomain} from '@/utils/ip_addr.js'; 15 | import logger from '@/options/logger.js'; 16 | 17 | export default class MacBuilder implements IBuilder { 18 | async prepare() { 19 | if (checkRustInstalled()) { 20 | return; 21 | } 22 | 23 | const res = await prompts({ 24 | type: 'confirm', 25 | message: 'We detected that you have not installed Rust. Install it now?', 26 | name: 'value', 27 | }); 28 | 29 | if (res.value) { 30 | // TODO 国内有可能会超时 31 | await installRust(); 32 | } else { 33 | log.error('Error: Pake need Rust to package your webapp!!!'); 34 | process.exit(2); 35 | } 36 | } 37 | 38 | async build(url: string, options: PakeAppOptions) { 39 | log.debug('PakeAppOptions', options); 40 | const { name } = options; 41 | 42 | await mergeTauriConfig(url, options, tauriConf); 43 | let dmgName: string; 44 | if (options.multiArch) { 45 | const isChina = await isChinaDomain("www.npmjs.com"); 46 | if (isChina) { 47 | logger.info("it's in China, use npm/rust cn mirror") 48 | const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo"); 49 | const e1 = fs.access(rust_project_dir); 50 | if (e1) { 51 | await fs.mkdir(rust_project_dir, { recursive: true }); 52 | } 53 | const project_cn_conf = path.join(npmDirectory, "src-tauri", "cn_config.bak"); 54 | const project_conf = path.join(rust_project_dir, "config"); 55 | fs.copyFile(project_cn_conf, project_conf); 56 | 57 | const _ = await shellExec( 58 | `cd "${npmDirectory}" && npm install --registry=https://registry.npmmirror.com && npm run build:mac` 59 | ); 60 | } else { 61 | const _ = await shellExec(`cd "${npmDirectory}" && npm install && npm run build:mac`); 62 | } 63 | dmgName = `${name}_${tauriConf.package.version}_universal.dmg`; 64 | } else { 65 | const isChina = isChinaDomain("www.npmjs.com") 66 | if (isChina) { 67 | const _ = await shellExec( 68 | `cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build` 69 | ); 70 | } else { 71 | const _ = await shellExec(`cd ${npmDirectory} && npm install && npm run build`); 72 | } 73 | let arch = "x64"; 74 | if (process.arch === "arm64") { 75 | arch = "aarch64"; 76 | } else { 77 | arch = process.arch; 78 | } 79 | dmgName = `${name}_${tauriConf.package.version}_${arch}.dmg`; 80 | } 81 | const appPath = this.getBuildAppPath(npmDirectory, dmgName, options.multiArch); 82 | const distPath = path.resolve(`${name}.dmg`); 83 | await fs.copyFile(appPath, distPath); 84 | await fs.unlink(appPath); 85 | 86 | logger.success('Build success!'); 87 | logger.success('You can find the app installer in', distPath); 88 | } 89 | 90 | getBuildAppPath(npmDirectory: string, dmgName: string, multiArch: boolean) { 91 | let dmgPath: string; 92 | if (multiArch) { 93 | dmgPath = 'src-tauri/target/universal-apple-darwin/release/bundle/dmg'; 94 | } else { 95 | dmgPath = 'src-tauri/target/release/bundle/dmg'; 96 | } 97 | return path.join(npmDirectory, dmgPath, dmgName); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /bin/builders/WinBulider.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import prompts from 'prompts'; 4 | import { checkRustInstalled, installRust } from '@/helpers/rust.js'; 5 | import { PakeAppOptions } from '@/types.js'; 6 | import { IBuilder } from './base.js'; 7 | import { shellExec } from '@/utils/shell.js'; 8 | // @ts-expect-error 加上resolveJsonModule rollup会打包报错 9 | // import tauriConf from '../../src-tauri/tauri.windows.conf.json'; 10 | import tauriConf from './tauriConf.js'; 11 | 12 | import { fileURLToPath } from 'url'; 13 | import logger from '@/options/logger.js'; 14 | import { mergeTauriConfig } from './common.js'; 15 | import { npmDirectory } from '@/utils/dir.js'; 16 | import {isChinaDomain} from '@/utils/ip_addr.js'; 17 | 18 | export default class WinBuilder implements IBuilder { 19 | async prepare() { 20 | logger.info( 21 | 'To build the Windows app, you need to install Rust and VS Build Tools.' 22 | ); 23 | logger.info( 24 | 'See more in https://tauri.app/v1/guides/getting-started/prerequisites#installing\n' 25 | ); 26 | if (checkRustInstalled()) { 27 | return; 28 | } 29 | 30 | const res = await prompts({ 31 | type: 'confirm', 32 | message: 'We detected that you have not installed Rust. Install it now?', 33 | name: 'value', 34 | }); 35 | 36 | if (res.value) { 37 | // TODO 国内有可能会超时 38 | await installRust(); 39 | } else { 40 | logger.error('Error: Pake needs Rust to package your webapp!!!'); 41 | process.exit(2); 42 | } 43 | } 44 | 45 | async build(url: string, options: PakeAppOptions) { 46 | logger.debug('PakeAppOptions', options); 47 | const { name } = options; 48 | await mergeTauriConfig(url, options, tauriConf); 49 | const isChina = await isChinaDomain("www.npmjs.com") 50 | if (isChina) { 51 | logger.info("it's in China, use npm/rust cn mirror") 52 | const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo"); 53 | const e1 = fs.access(rust_project_dir); 54 | if (e1) { 55 | await fs.mkdir(rust_project_dir, { recursive: true }); 56 | } 57 | const project_cn_conf = path.join(npmDirectory, "src-tauri", "cn_config.bak"); 58 | const project_conf = path.join(rust_project_dir, "config"); 59 | fs.copyFile(project_cn_conf, project_conf); 60 | 61 | const _ = await shellExec( 62 | `cd "${npmDirectory}" && npm install --registry=https://registry.npmmirror.com && npm run build` 63 | ); 64 | } else { 65 | const _ = await shellExec(`cd "${npmDirectory}" && npm install && npm run build`); 66 | } 67 | const language = tauriConf.tauri.bundle.windows.wix.language[0]; 68 | const arch = process.arch; 69 | const msiName = `${name}_${tauriConf.package.version}_${arch}_${language}.msi`; 70 | const appPath = this.getBuildedAppPath(npmDirectory, msiName); 71 | const distPath = path.resolve(`${name}.msi`); 72 | await fs.copyFile(appPath, distPath); 73 | await fs.unlink(appPath); 74 | logger.success('Build success!'); 75 | logger.success('You can find the app installer in', distPath); 76 | } 77 | 78 | getBuildedAppPath(npmDirectory: string, dmgName: string) { 79 | return path.join( 80 | npmDirectory, 81 | 'src-tauri/target/release/bundle/msi', 82 | dmgName 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /bin/builders/base.ts: -------------------------------------------------------------------------------- 1 | import { PakeAppOptions } from '@/types.js'; 2 | 3 | /** 4 | * Builder接口 5 | * 不同平台打包过程需要实现 prepare 和 build 方法 6 | */ 7 | export interface IBuilder { 8 | /** 前置检查 */ 9 | prepare(): Promise; 10 | /** 11 | * 开始打包 12 | * @param url 打包url 13 | * @param options 配置参数 14 | */ 15 | build(url: string, options: PakeAppOptions): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /bin/builders/common.ts: -------------------------------------------------------------------------------- 1 | import { PakeAppOptions } from '@/types.js'; 2 | import prompts, { override } from 'prompts'; 3 | import path from 'path'; 4 | import fs from 'fs/promises'; 5 | import fs2 from 'fs-extra'; 6 | import { npmDirectory } from '@/utils/dir.js'; 7 | import logger from '@/options/logger.js'; 8 | 9 | 10 | export async function promptText(message: string, initial?: string) { 11 | const response = await prompts({ 12 | type: 'text', 13 | name: 'content', 14 | message, 15 | initial, 16 | }); 17 | return response.content; 18 | } 19 | 20 | 21 | export async function mergeTauriConfig( 22 | url: string, 23 | options: PakeAppOptions, 24 | tauriConf: any 25 | ) { 26 | const { 27 | width, 28 | height, 29 | fullscreen, 30 | transparent, 31 | resizable, 32 | userAgent, 33 | showMenu, 34 | showSystemTray, 35 | systemTrayIcon, 36 | iterCopyFile, 37 | identifier, 38 | name, 39 | } = options; 40 | 41 | const tauriConfWindowOptions = { 42 | width, 43 | height, 44 | fullscreen, 45 | transparent, 46 | resizable, 47 | }; 48 | // Package name is valid ? 49 | // for Linux, package name must be a-z, 0-9 or "-", not allow to A-Z and other 50 | if (process.platform === "linux") { 51 | const reg = new RegExp(/[0-9]*[a-z]+[0-9]*\-?[0-9]*[a-z]*[0-9]*\-?[0-9]*[a-z]*[0-9]*/); 52 | if (!reg.test(name) || reg.exec(name)[0].length != name.length) { 53 | logger.error("package name is illegal, it must be lowercase letters, numbers, dashes, and it must contain the lowercase letters.") 54 | logger.error("E.g com-123-xxx, 123pan, pan123,weread, we-read"); 55 | process.exit(); 56 | } 57 | } 58 | if (process.platform === "win32" || process.platform === "darwin" ) { 59 | const reg = new RegExp(/([0-9]*[a-zA-Z]+[0-9]*)+/); 60 | if (!reg.test(name) || reg.exec(name)[0].length != name.length) { 61 | logger.error("package name is illegal, it must be letters, numbers, and it must contain the letters") 62 | logger.error("E.g 123pan,123Pan Pan123,weread, WeRead, WERead"); 63 | process.exit(); 64 | } 65 | } 66 | 67 | // logger.warn(JSON.stringify(tauriConf.pake.windows, null, 4)); 68 | Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions }); 69 | tauriConf.package.productName = name; 70 | tauriConf.tauri.bundle.identifier = identifier; 71 | // 判断一下url类型,是文件还是网站 72 | // 如果是文件,并且开启了递归拷贝功能,则需要将该文件以及所在文件夹下的所有文件拷贝到src目录下,否则只拷贝单个文件。 73 | 74 | const url_exists = await fs.stat(url) 75 | .then(() => true) 76 | .catch(() => false); 77 | if (url_exists) { 78 | logger.warn("you input may a local file"); 79 | tauriConf.pake.windows[0].url_type = "local"; 80 | const file_name = path.basename(url); 81 | const dir_name = path.dirname(url); 82 | if (!iterCopyFile) { 83 | const url_path = path.join(npmDirectory,"dist/", file_name); 84 | await fs.copyFile(url, url_path); 85 | } else { 86 | const old_dir = path.join(npmDirectory,"dist/"); 87 | const new_dir = path.join(npmDirectory,"dist_bak/"); 88 | fs2.moveSync(old_dir, new_dir, {"overwrite": true}); 89 | fs2.copySync(dir_name, old_dir, {"overwrite": true}); 90 | // logger.warn("dir name", dir_name); 91 | // 将dist_bak里面的cli.js和about_pake.html拷贝回去 92 | const cli_path = path.join(new_dir, "cli.js") 93 | const cli_path_target = path.join(old_dir, "cli.js") 94 | const about_pake_path = path.join(new_dir, "about_pake.html"); 95 | const about_patk_path_target = path.join(old_dir, "about_pake.html") 96 | fs.copyFile(cli_path, cli_path_target); 97 | fs.copyFile(about_pake_path, about_patk_path_target); 98 | } 99 | tauriConf.pake.windows[0].url = file_name; 100 | tauriConf.pake.windows[0].url_type = "local"; 101 | } else { 102 | tauriConf.pake.windows[0].url_type = "web"; 103 | } 104 | 105 | // 处理user-agent 106 | if (userAgent.length > 0) { 107 | if (process.platform === "win32") { 108 | tauriConf.pake.user_agent.windows = userAgent; 109 | } 110 | 111 | if (process.platform === "linux") { 112 | tauriConf.pake.user_agent.linux = userAgent; 113 | } 114 | 115 | if (process.platform === "darwin") { 116 | tauriConf.pake.user_agent.macos = userAgent; 117 | } 118 | } 119 | 120 | // 处理菜单栏 121 | if (showMenu) { 122 | if (process.platform === "win32") { 123 | tauriConf.pake.menu.windows = true; 124 | } 125 | 126 | if (process.platform === "linux") { 127 | tauriConf.pake.menu.linux = true; 128 | } 129 | 130 | if (process.platform === "darwin") { 131 | tauriConf.pake.menu.macos = true; 132 | } 133 | } else { 134 | if (process.platform === "win32") { 135 | tauriConf.pake.menu.windows = false; 136 | } 137 | 138 | if (process.platform === "linux") { 139 | tauriConf.pake.menu.linux = false; 140 | } 141 | 142 | if (process.platform === "darwin") { 143 | tauriConf.pake.menu.macos = false; 144 | } 145 | } 146 | 147 | // 处理托盘 148 | if (showSystemTray) { 149 | if (process.platform === "win32") { 150 | tauriConf.pake.system_tray.windows = true; 151 | } 152 | 153 | if (process.platform === "linux") { 154 | tauriConf.pake.system_tray.linux = true; 155 | } 156 | 157 | if (process.platform === "darwin") { 158 | tauriConf.pake.system_tray.macos = true; 159 | } 160 | } else { 161 | if (process.platform === "win32") { 162 | tauriConf.pake.system_tray.windows = false; 163 | } 164 | 165 | if (process.platform === "linux") { 166 | tauriConf.pake.system_tray.linux = false; 167 | } 168 | 169 | if (process.platform === "darwin") { 170 | tauriConf.pake.system_tray.macos = false; 171 | } 172 | } 173 | 174 | // 处理targets 暂时只对linux开放 175 | if (process.platform === "linux") { 176 | delete tauriConf.tauri.bundle.deb.files; 177 | if (["all", "deb", "appimage"].includes(options.targets)) { 178 | if (options.targets === "all") { 179 | tauriConf.tauri.bundle.targets = ["deb", "appimage"]; 180 | } else { 181 | tauriConf.tauri.bundle.targets = [options.targets]; 182 | } 183 | } else { 184 | logger.warn("targets must be 'all', 'deb', 'appimage', we will use default 'all'"); 185 | } 186 | } 187 | 188 | // 处理应用图标 189 | const exists = await fs.stat(options.icon) 190 | .then(() => true) 191 | .catch(() => false); 192 | if (exists) { 193 | let updateIconPath = true; 194 | let customIconExt = path.extname(options.icon).toLowerCase(); 195 | if (process.platform === "win32") { 196 | if (customIconExt === ".ico") { 197 | const ico_path = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}_32.ico`); 198 | tauriConf.tauri.bundle.resources = [`png/${name.toLowerCase()}_32.ico`]; 199 | await fs.copyFile(options.icon, ico_path); 200 | } else { 201 | updateIconPath = false; 202 | logger.warn(`icon file in Windows must be 256 * 256 pix with .ico type, but you give ${customIconExt}`); 203 | tauriConf.tauri.bundle.icon = ["png/icon_256.ico"]; 204 | } 205 | } 206 | if (process.platform === "linux") { 207 | if (customIconExt != ".png") { 208 | updateIconPath = false; 209 | logger.warn(`icon file in Linux must be 512 * 512 pix with .png type, but you give ${customIconExt}`); 210 | tauriConf.tauri.bundle.icon = ["png/icon_512.png"]; 211 | } 212 | } 213 | 214 | if (process.platform === "darwin" && customIconExt !== ".icns") { 215 | updateIconPath = false; 216 | logger.warn(`icon file in MacOS must be .icns type, but you give ${customIconExt}`); 217 | tauriConf.tauri.bundle.icon = ["icons/icon.icns"]; 218 | } 219 | if (updateIconPath) { 220 | tauriConf.tauri.bundle.icon = [options.icon]; 221 | } else { 222 | logger.warn(`icon file will not change with default.`); 223 | } 224 | } else { 225 | logger.warn("the custom icon path may not exists. we will use default icon to replace it"); 226 | if (process.platform === "win32") { 227 | tauriConf.tauri.bundle.icon = ["png/icon_256.ico"]; 228 | } 229 | if (process.platform === "linux") { 230 | tauriConf.tauri.bundle.icon = ["png/icon_512.png"]; 231 | } 232 | if (process.platform === "darwin") { 233 | tauriConf.tauri.bundle.icon = ["icons/icon.icns"]; 234 | } 235 | } 236 | 237 | // 处理托盘自定义图标 238 | let useDefaultIcon = true; // 是否使用默认托盘图标 239 | if (systemTrayIcon.length > 0) { 240 | const icon_exists = await fs.stat(systemTrayIcon) 241 | .then(() => true) 242 | .catch(() => false); 243 | if (icon_exists) { 244 | // 需要判断图标格式,默认只支持ico和png两种 245 | let iconExt = path.extname(systemTrayIcon).toLowerCase(); 246 | if (iconExt == ".png" || iconExt == ".icon") { 247 | useDefaultIcon = false; 248 | const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}${iconExt}`); 249 | tauriConf.tauri.systemTray.iconPath = `png/${name.toLowerCase()}${iconExt}`; 250 | await fs.copyFile(systemTrayIcon, trayIcoPath); 251 | } else { 252 | logger.warn(`file type for system tray icon mut be .ico or .png , but you give ${iconExt}`); 253 | logger.warn(`system tray icon file will not change with default.`); 254 | } 255 | } else { 256 | logger.warn(`${systemTrayIcon} not exists!`) 257 | logger.warn(`system tray icon file will not change with default.`); 258 | } 259 | } 260 | 261 | // 处理托盘默认图标 262 | if (useDefaultIcon) { 263 | if (process.platform === "linux" || process.platform === "win32") { 264 | tauriConf.tauri.systemTray.iconPath = tauriConf.tauri.bundle.icon[0]; 265 | } else { 266 | tauriConf.tauri.systemTray.iconPath = "png/icon_512.png"; 267 | } 268 | } 269 | 270 | // 保存配置文件 271 | let configPath = ""; 272 | switch (process.platform) { 273 | case "win32": { 274 | configPath = path.join(npmDirectory, 'src-tauri/tauri.windows.conf.json'); 275 | break; 276 | } 277 | case "darwin": { 278 | configPath = path.join(npmDirectory, 'src-tauri/tauri.macos.conf.json'); 279 | break; 280 | } 281 | case "linux": { 282 | configPath = path.join(npmDirectory, 'src-tauri/tauri.linux.conf.json'); 283 | break; 284 | } 285 | } 286 | 287 | let bundleConf = {tauri: {bundle: tauriConf.tauri.bundle}}; 288 | await fs.writeFile( 289 | configPath, 290 | Buffer.from(JSON.stringify(bundleConf, null, 4), 'utf-8') 291 | ); 292 | 293 | const pakeConfigPath = path.join(npmDirectory, 'src-tauri/pake.json') 294 | await fs.writeFile( 295 | pakeConfigPath, 296 | Buffer.from(JSON.stringify(tauriConf.pake, null, 4), 'utf-8') 297 | ); 298 | // logger.info("tauri config", JSON.stringify(tauriConf.build)); 299 | let tauriConf2 = JSON.parse(JSON.stringify(tauriConf)); 300 | delete tauriConf2.pake; 301 | delete tauriConf2.tauri.bundle; 302 | 303 | const configJsonPath = path.join(npmDirectory, 'src-tauri/tauri.conf.json') 304 | await fs.writeFile( 305 | configJsonPath, 306 | Buffer.from(JSON.stringify(tauriConf2, null, 4), 'utf-8') 307 | ); 308 | } 309 | -------------------------------------------------------------------------------- /bin/builders/tauriConf.js: -------------------------------------------------------------------------------- 1 | import CommonConf from '../../src-tauri/tauri.conf.json'; 2 | import pakeConf from '../../src-tauri/pake.json'; 3 | import WinConf from '../../src-tauri/tauri.windows.conf.json'; 4 | import MacConf from '../../src-tauri/tauri.macos.conf.json'; 5 | import LinuxConf from '../../src-tauri/tauri.linux.conf.json'; 6 | 7 | let tauriConf = { 8 | package: CommonConf.package, 9 | tauri: CommonConf.tauri, 10 | build: CommonConf.build, 11 | pake: pakeConf 12 | } 13 | switch (process.platform) { 14 | case "win32": { 15 | tauriConf.tauri.bundle = WinConf.tauri.bundle; 16 | break; 17 | } 18 | case "darwin": { 19 | tauriConf.tauri.bundle = MacConf.tauri.bundle; 20 | break; 21 | } 22 | case "linux": { 23 | tauriConf.tauri.bundle = LinuxConf.tauri.bundle; 24 | break; 25 | } 26 | } 27 | 28 | export default tauriConf; 29 | 30 | 31 | -------------------------------------------------------------------------------- /bin/cli.ts: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import log from 'loglevel'; 3 | import chalk from 'chalk'; 4 | import { DEFAULT_PAKE_OPTIONS } from './defaults.js'; 5 | import { PakeCliOptions } from './types.js'; 6 | import { validateNumberInput, validateUrlInput } from './utils/validate.js'; 7 | import handleInputOptions from './options/index.js'; 8 | import BuilderFactory from './builders/BuilderFactory.js'; 9 | import { checkUpdateTips } from './helpers/updater.js'; 10 | // @ts-expect-error 11 | import packageJson from '../package.json'; 12 | import logger from './options/logger.js'; 13 | 14 | program.version(packageJson.version).description('A cli application can package a web page to desktop application.'); 15 | 16 | program 17 | .showHelpAfterError() 18 | .argument('[url]', 'the web url you want to package', validateUrlInput) 19 | .option('--name ', 'application name') 20 | .option('--icon ', 'application icon', DEFAULT_PAKE_OPTIONS.icon) 21 | .option('--height ', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height) 22 | .option('--width ', 'window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width) 23 | .option('--no-resizable', 'whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable) 24 | .option('--fullscreen', 'makes the packaged app start in full screen', DEFAULT_PAKE_OPTIONS.fullscreen) 25 | .option('--transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent) 26 | .option('--user-agent ', 'custom user agent', DEFAULT_PAKE_OPTIONS.userAgent) 27 | .option('--show-menu', 'show menu in app', DEFAULT_PAKE_OPTIONS.showMenu) 28 | .option('--show-system-tray', 'show system tray in app', DEFAULT_PAKE_OPTIONS.showSystemTray) 29 | .option('--system-tray-icon ', 'custom system tray icon', DEFAULT_PAKE_OPTIONS.systemTrayIcon) 30 | .option('--iter-copy-file', 31 | 'copy all static file to pake app when url is a local file', 32 | DEFAULT_PAKE_OPTIONS.iterCopyFile) 33 | .option( 34 | '-m, --multi-arch', 35 | "available for Mac only, and supports both Intel and M1", 36 | DEFAULT_PAKE_OPTIONS.multiArch 37 | ) 38 | .option( 39 | '--targets ', 40 | 'only for linux, default is "deb", option "appaimge" or "all"(deb & appimage)', 41 | DEFAULT_PAKE_OPTIONS.targets 42 | ) 43 | .option('--debug', 'debug', DEFAULT_PAKE_OPTIONS.transparent) 44 | .action(async (url: string, options: PakeCliOptions) => { 45 | checkUpdateTips(); 46 | 47 | if (!url) { 48 | // 直接 pake 不需要出现url提示 49 | program.help(); 50 | } 51 | 52 | log.setDefaultLevel('info'); 53 | if (options.debug) { 54 | log.setLevel('debug'); 55 | } 56 | 57 | const builder = BuilderFactory.create(); 58 | await builder.prepare(); 59 | // logger.warn("you input url is ", url); 60 | const appOptions = await handleInputOptions(options, url); 61 | // logger.info(JSON.stringify(appOptions, null, 4)); 62 | builder.build(url, appOptions); 63 | }); 64 | 65 | program.parse(); 66 | -------------------------------------------------------------------------------- /bin/defaults.ts: -------------------------------------------------------------------------------- 1 | import { PakeCliOptions } from './types.js'; 2 | 3 | export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = { 4 | icon: '', 5 | height: 780, 6 | width: 1200, 7 | fullscreen: false, 8 | resizable: true, 9 | transparent: false, 10 | userAgent: '', 11 | showMenu: false, 12 | showSystemTray: false, 13 | multiArch: false, 14 | targets: 'deb', 15 | iterCopyFile: false, 16 | systemTrayIcon: '', 17 | debug: false, 18 | }; 19 | 20 | export const DEFAULT_APP_NAME = 'Pake'; 21 | -------------------------------------------------------------------------------- /bin/helpers/rust.ts: -------------------------------------------------------------------------------- 1 | import { IS_WIN } from '@/utils/platform.js'; 2 | import ora from 'ora'; 3 | import shelljs from 'shelljs'; 4 | import logger from '@/options/logger.js'; 5 | import { shellExec } from '../utils/shell.js'; 6 | import {isChinaDomain} from '@/utils/ip_addr.js' 7 | 8 | 9 | export async function installRust() { 10 | const is_china = await isChinaDomain("sh.rustup.rs"); 11 | let RustInstallScriptFocMac = ""; 12 | if (is_china) { 13 | logger.info("it's in China, use rust cn mirror to install rust"); 14 | RustInstallScriptFocMac = 15 | 'export RUSTUP_DIST_SERVER="https://rsproxy.cn" && export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup" && curl --proto "=https" --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh'; 16 | } else { 17 | RustInstallScriptFocMac = 18 | "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; 19 | } 20 | const RustInstallScriptForWin = 'winget install --id Rustlang.Rustup'; 21 | const spinner = ora('Downloading Rust').start(); 22 | try { 23 | await shellExec(IS_WIN ? RustInstallScriptForWin : RustInstallScriptFocMac); 24 | spinner.succeed(); 25 | } catch (error) { 26 | console.error('install rust return code', error.message); 27 | spinner.fail(); 28 | 29 | process.exit(1); 30 | } 31 | } 32 | 33 | export function checkRustInstalled() { 34 | return shelljs.exec('rustc --version', { silent: true }).code === 0; 35 | } 36 | -------------------------------------------------------------------------------- /bin/helpers/tauriConfig.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | export function getIdentifier(name: string, url: string) { 4 | const hash = crypto.createHash('md5'); 5 | hash.update(url); 6 | const postFixHash = hash.digest('hex').substring(0, 6); 7 | return `pake-${postFixHash}`; 8 | } 9 | -------------------------------------------------------------------------------- /bin/helpers/updater.ts: -------------------------------------------------------------------------------- 1 | import updateNotifier from 'update-notifier'; 2 | // @ts-expect-error 3 | import packageJson from '../../package.json'; 4 | 5 | export async function checkUpdateTips() { 6 | updateNotifier({ pkg: packageJson }).notify(); 7 | } 8 | -------------------------------------------------------------------------------- /bin/options/icon.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { fileTypeFromBuffer } from 'file-type'; 3 | import { PakeAppOptions } from '../types.js'; 4 | import { dir } from 'tmp-promise'; 5 | import path from 'path'; 6 | import fs from 'fs/promises'; 7 | import logger from './logger.js'; 8 | import { npmDirectory } from '@/utils/dir.js'; 9 | import { IS_LINUX, IS_WIN } from '@/utils/platform.js'; 10 | 11 | export async function handleIcon(options: PakeAppOptions, url: string) { 12 | if (options.icon) { 13 | if (options.icon.startsWith('http')) { 14 | return downloadIcon(options.icon); 15 | } else { 16 | return path.resolve(options.icon); 17 | } 18 | } 19 | if (!options.icon) { 20 | return getDefaultIcon(); 21 | } 22 | } 23 | 24 | export async function getDefaultIcon() { 25 | logger.info('You have not provided an app icon, use the default icon.(use --icon option to assign an icon)') 26 | let iconPath = 'src-tauri/icons/icon.icns'; 27 | if (IS_WIN) { 28 | iconPath = 'src-tauri/png/icon_256.ico'; 29 | } else if (IS_LINUX) { 30 | iconPath = 'src-tauri/png/icon_512.png'; 31 | } 32 | 33 | return path.join(npmDirectory, iconPath); 34 | } 35 | 36 | // export async function getIconFromPageUrl(url: string) { 37 | // const icon = await pageIcon(url); 38 | // console.log(icon); 39 | // if (icon.ext === '.ico') { 40 | // const a = await ICO.parse(icon.data); 41 | // icon.data = Buffer.from(a[0].buffer); 42 | // } 43 | 44 | // const iconDir = (await dir()).path; 45 | // const iconPath = path.join(iconDir, `/icon.icns`); 46 | 47 | // const out = png2icons.createICNS(icon.data, png2icons.BILINEAR, 0); 48 | 49 | // await fs.writeFile(iconPath, out); 50 | // return iconPath; 51 | // } 52 | 53 | // export async function getIconFromMacosIcons(name: string) { 54 | // const data = { 55 | // query: name, 56 | // filters: 'approved:true', 57 | // hitsPerPage: 10, 58 | // page: 1, 59 | // }; 60 | // const res = await axios.post('https://p1txh7zfb3-2.algolianet.com/1/indexes/macOSicons/query?x-algolia-agent=Algolia%20for%20JavaScript%20(4.13.1)%3B%20Browser', data, { 61 | // headers: { 62 | // 'x-algolia-api-key': '0ba04276e457028f3e11e38696eab32c', 63 | // 'x-algolia-application-id': 'P1TXH7ZFB3', 64 | // }, 65 | // }); 66 | // if (!res.data.hits.length) { 67 | // return ''; 68 | // } else { 69 | // return downloadIcon(res.data.hits[0].icnsUrl); 70 | // } 71 | // } 72 | 73 | export async function downloadIcon(iconUrl: string) { 74 | let iconResponse; 75 | try { 76 | iconResponse = await axios.get(iconUrl, { 77 | responseType: 'arraybuffer', 78 | }); 79 | } catch (error) { 80 | if (error.response && error.response.status === 404) { 81 | return null; 82 | } 83 | throw error; 84 | } 85 | 86 | const iconData = await iconResponse.data; 87 | if (!iconData) { 88 | return null; 89 | } 90 | const fileDetails = await fileTypeFromBuffer(iconData); 91 | if (!fileDetails) { 92 | return null; 93 | } 94 | const { path } = await dir(); 95 | const iconPath = `${path}/icon.${fileDetails.ext}`; 96 | await fs.writeFile(iconPath, iconData); 97 | return iconPath; 98 | } 99 | -------------------------------------------------------------------------------- /bin/options/index.ts: -------------------------------------------------------------------------------- 1 | import { promptText } from '@/builders/common.js'; 2 | import { getDomain } from '@/utils/url.js'; 3 | import { getIdentifier } from '../helpers/tauriConfig.js'; 4 | import { PakeAppOptions, PakeCliOptions } from '../types.js'; 5 | import { handleIcon } from './icon.js'; 6 | import fs from 'fs/promises'; 7 | 8 | export default async function handleOptions(options: PakeCliOptions, url: string): Promise { 9 | const appOptions: PakeAppOptions = { 10 | ...options, 11 | identifier: '', 12 | }; 13 | const url_exists = await fs.stat(url) 14 | .then(() => true) 15 | .catch(() => false); 16 | if (!appOptions.name) { 17 | if (!url_exists) { 18 | appOptions.name = await promptText('please input your application name', getDomain(url)); 19 | } else { 20 | appOptions.name = await promptText('please input your application name', ""); 21 | } 22 | } 23 | 24 | appOptions.identifier = getIdentifier(appOptions.name, url); 25 | 26 | appOptions.icon = await handleIcon(appOptions, url); 27 | 28 | return appOptions; 29 | } 30 | -------------------------------------------------------------------------------- /bin/options/logger.ts: -------------------------------------------------------------------------------- 1 | import log from 'loglevel'; 2 | import chalk from 'chalk'; 3 | 4 | const logger = { 5 | info(...msg: any[]) { 6 | log.info(...msg.map((m) => chalk.blue.bold(m))); 7 | }, 8 | debug(...msg: any[]) { 9 | log.debug(...msg); 10 | }, 11 | error(...msg: any[]) { 12 | log.error(...msg.map((m) => chalk.red.bold(m))); 13 | }, 14 | warn(...msg: any[]) { 15 | log.info(...msg.map((m) => chalk.yellow.bold(m))); 16 | }, 17 | success(...msg: any[]) { 18 | log.info(...msg.map((m) => chalk.green.bold(m))); 19 | } 20 | }; 21 | 22 | export default logger; 23 | -------------------------------------------------------------------------------- /bin/types.ts: -------------------------------------------------------------------------------- 1 | export interface PakeCliOptions { 2 | /** 应用名称 */ 3 | name?: string; 4 | 5 | /** 应用icon */ 6 | icon: string; 7 | 8 | /** 应用窗口宽度,默认 1200px */ 9 | width: number; 10 | 11 | /** 应用窗口高度,默认 780px */ 12 | height: number; 13 | 14 | /** 是否可以拖动,默认true */ 15 | resizable: boolean; 16 | 17 | /** 是否可以全屏,默认 false */ 18 | fullscreen: boolean; 19 | 20 | /** 是否开启沉浸式头部,默认为 false 不开启 ƒ*/ 21 | transparent: boolean; 22 | 23 | /** 自定义UA,默认为不开启 ƒ*/ 24 | userAgent: string; 25 | 26 | /** 开启菜单栏,MacOS默认开启,Windows,Linux默认不开启 ƒ*/ 27 | showMenu: boolean; 28 | 29 | /** 开启系统托盘,MacOS默认不开启,Windows,Linux默认开启 ƒ*/ 30 | showSystemTray: boolean; 31 | 32 | /** 托盘图标, Windows、Linux默认和应用图标共用一样的,MacOS需要提别提供, 格式为png或者ico */ 33 | systemTrayIcon: string; 34 | 35 | // /** 递归拷贝,当url为本地文件路径时候,若开启该选项,则将url路径文件所在文件夹以及所有子文件都拷贝到pake静态文件夹,默认不开启 */ 36 | iterCopyFile: false; 37 | 38 | /** mutli arch, Supports both Intel and m1 chips, only for Mac */ 39 | multiArch: boolean; 40 | 41 | // 包输出产物,对linux用户有效,默认为deb,可选appimage, 或者all(即同时输出deb和all); 42 | targets: string; 43 | 44 | /** 调试模式,会输出更多日志 */ 45 | debug: boolean; 46 | } 47 | 48 | export interface PakeAppOptions extends PakeCliOptions { 49 | identifier: string; 50 | } 51 | -------------------------------------------------------------------------------- /bin/utils/dir.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | 4 | 5 | export const npmDirectory = path.join( 6 | path.dirname(fileURLToPath(import.meta.url)), 7 | '..' 8 | ); 9 | -------------------------------------------------------------------------------- /bin/utils/ip_addr.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | import { promisify } from 'util'; 3 | import logger from '@/options/logger.js'; 4 | import dns from 'dns'; 5 | import http from 'http'; 6 | 7 | 8 | const ping = async (host: string) => { 9 | const lookup = promisify(dns.lookup); 10 | const ip = await lookup(host); 11 | const start = new Date(); 12 | 13 | return new Promise((resolve, reject) => { 14 | const req = http.get(`http://${ip.address}`, (res) => { 15 | const delay = new Date().getTime() - start.getTime(); 16 | res.resume(); 17 | resolve(delay); 18 | }); 19 | 20 | req.on('error', (err) => { 21 | reject(err); 22 | }); 23 | }); 24 | }; 25 | 26 | 27 | const resolve = promisify(dns.resolve); 28 | 29 | async function isChinaDomain(domain: string): Promise { 30 | try { 31 | // 解析域名为IP地址 32 | const [ip] = await resolve(domain); 33 | return await isChinaIP(ip, domain); 34 | } catch (error) { 35 | // 域名无法解析,返回false 36 | logger.info(`${domain} can't be parse, is not in China!`); 37 | return false; 38 | } 39 | } 40 | 41 | async function isChinaIP(ip: string, domain: string): Promise { 42 | try { 43 | const delay = await ping(ip); 44 | logger.info(`${domain} latency is ${delay} ms`); 45 | // 判断延迟是否超过500ms 46 | return delay > 500; 47 | } catch (error) { 48 | // 命令执行出错,返回false 49 | logger.info(`ping ${domain} failed!, is not in China!`); 50 | return false; 51 | } 52 | } 53 | 54 | export { isChinaDomain, isChinaIP }; 55 | -------------------------------------------------------------------------------- /bin/utils/platform.ts: -------------------------------------------------------------------------------- 1 | export const IS_MAC = process.platform === 'darwin'; 2 | 3 | export const IS_WIN = process.platform === 'win32'; 4 | 5 | export const IS_LINUX = process.platform === 'linux'; 6 | -------------------------------------------------------------------------------- /bin/utils/shell.ts: -------------------------------------------------------------------------------- 1 | import shelljs from "shelljs"; 2 | import { npmDirectory } from "./dir.js"; 3 | 4 | export function shellExec(command: string) { 5 | return new Promise((resolve, reject) => { 6 | shelljs.exec(command, { async: true, silent: false, cwd: npmDirectory}, (code) => { 7 | if (code === 0) { 8 | resolve(0); 9 | } else { 10 | reject(new Error(`${code}`)); 11 | } 12 | }); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /bin/utils/url.ts: -------------------------------------------------------------------------------- 1 | import url from 'url'; 2 | import isurl from 'is-url'; 3 | import tlds from './tlds.js'; 4 | 5 | export function getDomain(inputUrl: string) { 6 | const parsed = url.parse(inputUrl).host; 7 | var parts = parsed.split('.'); 8 | if (parts[0] === 'www' && parts[1] !== 'com') { 9 | parts.shift(); 10 | } 11 | var ln = parts.length, 12 | i = ln, 13 | minLength = parts[parts.length - 1].length, 14 | part; 15 | 16 | // iterate backwards 17 | while ((part = parts[--i])) { 18 | // stop when we find a non-TLD part 19 | if ( 20 | i === 0 || // 'asia.com' (last remaining must be the SLD) 21 | i < ln - 2 || // TLDs only span 2 levels 22 | part.length < minLength || // 'www.cn.com' (valid TLD as second-level domain) 23 | tlds.indexOf(part) < 0 // officialy not a TLD 24 | ) { 25 | return part; 26 | } 27 | } 28 | } 29 | 30 | function appendProtocol(inputUrl: string): string { 31 | const parsed = url.parse(inputUrl); 32 | if (!parsed.protocol) { 33 | const urlWithProtocol = `https://${inputUrl}`; 34 | return urlWithProtocol; 35 | } 36 | return inputUrl; 37 | } 38 | 39 | export function normalizeUrl(urlToNormalize: string): string { 40 | const urlWithProtocol = appendProtocol(urlToNormalize); 41 | 42 | if (isurl(urlWithProtocol)) { 43 | return urlWithProtocol; 44 | } else { 45 | throw new Error(`Your url "${urlWithProtocol}" is invalid`); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bin/utils/validate.ts: -------------------------------------------------------------------------------- 1 | import * as Commander from 'commander'; 2 | import { normalizeUrl } from './url.js'; 3 | import fs from 'fs'; 4 | 5 | export function validateNumberInput(value: string) { 6 | const parsedValue = Number(value); 7 | if (isNaN(parsedValue)) { 8 | throw new Commander.InvalidArgumentError('Not a number.'); 9 | } 10 | return parsedValue; 11 | } 12 | 13 | export function validateUrlInput(url: string) { 14 | if(!fs.existsSync(url)) { 15 | try { 16 | return normalizeUrl(url) 17 | } catch (error) { 18 | throw new Commander.InvalidArgumentError(error.message); 19 | } 20 | } else { 21 | return url; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import './dist/cli.js'; 3 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/dist/.gitkeep -------------------------------------------------------------------------------- /dist/about_pake.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
Welcome from Pake!
10 |

version: 1.0.9

11 | Project link
12 | Discussions
13 | Issues
14 |

LICENSE: MIT

15 | 16 | -------------------------------------------------------------------------------- /icns2png.py: -------------------------------------------------------------------------------- 1 | """ 2 | 批量将icns文件转成png文件 3 | Batch convert ICNS files to PNG files 4 | """ 5 | import os 6 | 7 | try: 8 | from PIL import Image 9 | except ImportError: 10 | os.system("pip install Pillow") 11 | from PIL import Image 12 | 13 | 14 | if __name__ == "__main__": 15 | now_dir = os.path.dirname(os.path.abspath(__file__)) 16 | icons_dir = os.path.join(now_dir, "src-tauri", "icons") 17 | png_dir = os.path.join(now_dir, "src-tauri", "png") 18 | if not os.path.exists(png_dir): 19 | os.mkdir(png_dir) 20 | file_list = os.listdir(icons_dir) 21 | file_list = [file for file in file_list if file.endswith(".icns")] 22 | for file in file_list: 23 | icns_path = os.path.join(icons_dir, file) 24 | image = Image.open(icns_path) 25 | image_512 = image.copy().resize((512, 512)) 26 | image_256 = image.copy().resize((256, 256)) 27 | image_32 = image.copy().resize((32, 32)) 28 | image_name = os.path.splitext(file)[0] 29 | image_512_path = os.path.join(png_dir, image_name + "_512.png") 30 | image_256_path = os.path.join(png_dir, image_name + "_256.ico") 31 | image_32_path = os.path.join(png_dir, image_name + "_32.ico") 32 | image_512.save(image_512_path, "PNG") 33 | image_256.save(image_256_path, "ICO") 34 | image_32.save(image_32_path, "ICO") 35 | print("png file write success.") 36 | print(f"There are {len(os.listdir(png_dir))} png picture in ", png_dir) 37 | 38 | 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pake-cli", 3 | "version": "2.0.2", 4 | "description": "🤱🏻 Turn any webpage into a desktop app with Rust. 🤱🏻 很简单的用 Rust 打包网页生成很小的桌面 App。", 5 | "engines": { 6 | "node": ">=16.0.0" 7 | }, 8 | "bin": { 9 | "pake": "./cli.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/tw93/pake.git" 14 | }, 15 | "author": { 16 | "name": "Tw93", 17 | "email": "tw93@qq.com" 18 | }, 19 | "keywords": [ 20 | "pake", 21 | "pake-cli", 22 | "rust", 23 | "tauri", 24 | "no-electron", 25 | "productivity" 26 | ], 27 | "files": [ 28 | "dist", 29 | "src-tauri", 30 | "cli.js" 31 | ], 32 | "scripts": { 33 | "start": "npm run dev", 34 | "dev": "npm run tauri dev", 35 | "build": "npm run tauri build --release", 36 | "build:mac": "npm run tauri build -- --target universal-apple-darwin", 37 | "build:all-unix": "chmod +x ./script/build.sh && ./script/build.sh", 38 | "build:all-windows": "pwsh ./script/build.ps1", 39 | "analyze": "cd src-tauri && cargo bloat --release --crates", 40 | "tauri": "tauri", 41 | "cli": "rollup -c rollup.config.js --watch", 42 | "cli:build": "cross-env NODE_ENV=production rollup -c rollup.config.js", 43 | "prepublishOnly": "npm run cli:build" 44 | }, 45 | "type": "module", 46 | "exports": "./dist/pake.js", 47 | "license": "MIT", 48 | "dependencies": { 49 | "@tauri-apps/api": "^1.2.0", 50 | "@tauri-apps/cli": "^1.2.3", 51 | "axios": "^1.1.3", 52 | "chalk": "^5.1.2", 53 | "commander": "^9.4.1", 54 | "file-type": "^18.0.0", 55 | "fs-extra": "^11.1.0", 56 | "is-url": "^1.2.4", 57 | "loglevel": "^1.8.1", 58 | "ora": "^6.1.2", 59 | "prompts": "^2.4.2", 60 | "shelljs": "^0.8.5", 61 | "tmp-promise": "^3.0.3", 62 | "update-notifier": "^6.0.2" 63 | }, 64 | "devDependencies": { 65 | "@rollup/plugin-alias": "^4.0.2", 66 | "@rollup/plugin-commonjs": "^23.0.2", 67 | "@rollup/plugin-json": "^5.0.1", 68 | "@rollup/plugin-terser": "^0.1.0", 69 | "@rollup/plugin-typescript": "^9.0.2", 70 | "@types/fs-extra": "^9.0.13", 71 | "@types/is-url": "^1.2.30", 72 | "@types/page-icon": "^0.3.4", 73 | "@types/prompts": "^2.4.1", 74 | "@types/shelljs": "^0.8.11", 75 | "@types/tmp": "^0.2.3", 76 | "@types/update-notifier": "^6.0.1", 77 | "app-root-path": "^3.1.0", 78 | "concurrently": "^7.5.0", 79 | "cross-env": "^7.0.3", 80 | "rollup": "^3.3.0", 81 | "tslib": "^2.4.1", 82 | "typescript": "^4.9.3" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import appRootPath from 'app-root-path'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | import alias from '@rollup/plugin-alias'; 5 | import commonjs from '@rollup/plugin-commonjs'; 6 | import json from '@rollup/plugin-json'; 7 | 8 | export default { 9 | input: 'bin/cli.ts', 10 | output: { 11 | file: 'dist/cli.js', 12 | format: 'es' 13 | }, 14 | plugins: [ 15 | json(), 16 | typescript({ 17 | sourceMap: false, 18 | }), 19 | commonjs(), 20 | alias({ 21 | entries: [{ find: '@', replacement: path.join(appRootPath.path, 'bin') }], 22 | }), 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /script/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | chcp 65001 3 | 4 | if not exist node_modules ( 5 | call npm i 6 | ) 7 | 8 | if not exist output ( 9 | mkdir output 10 | ) 11 | 12 | 13 | if not exist output\windows ( 14 | mkdir output\windows 15 | ) 16 | 17 | echo. 18 | echo ======================= 19 | echo "build for windows" 20 | echo ======================= 21 | echo. 22 | 23 | :: total package number 24 | set /A index=1 25 | for /f %%a in (' find /c /v "" ^<"app.csv" ') do set /A total=%%a 26 | :: ignore first header line 27 | set /A total=total-1 28 | 29 | set old_name=weread 30 | set old_title=WeRead 31 | set old_zh_name=微信阅读 32 | set old_url=https://weread.qq.com/ 33 | 34 | :: set init name, we will recovery code to init when build finish. 35 | set init_name=%old_name% 36 | set init_title=%old_title% 37 | set init_zh_name=%old_zh_name% 38 | set init_url=%old_url% 39 | 40 | :: for windows, we need replace package name to title 41 | :: .\script\sd.exe "\"productName\": \"weread\"" "\"productName\": \"WeRead\"" src-tauri\tauri.conf.json 42 | 43 | for /f "skip=1 tokens=1-4 delims=," %%i in (app.csv) do ( 44 | setlocal enabledelayedexpansion 45 | set name=%%i 46 | set title=%%j 47 | set name_zh=%%k 48 | set url=%%l 49 | @echo on 50 | 51 | ::echo name is !name! !name_zh! !url! 52 | :: replace url 53 | .\script\sd.exe -s !old_url! !url! src-tauri\pake.json 54 | ::replace pacakge name 55 | .\script\sd.exe !old_title! !title! src-tauri\tauri.conf.json 56 | .\script\sd.exe !old_name! !name! src-tauri\tauri.conf.json 57 | .\script\sd.exe !old_name! !name! src-tauri\tauri.windows.conf.json 58 | 59 | echo. 60 | ::update package info 61 | set old_zh_name=!name_zh! 62 | set old_name=!name! 63 | set old_title=!title! 64 | set old_url=!url! 65 | ::build package 66 | echo building package !index!/!total! 67 | echo package name is !name! !name_zh! 68 | echo npm run build:windows 69 | @echo off 70 | call npm run tauri build -- --target x86_64-pc-windows-msvc 71 | move src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\*.msi output\windows\!title!_x64.msi 72 | ::rm cache 73 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\*.exe 74 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\resources\*.ico 75 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\png\*.ico 76 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\wix\*.* 77 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\app.* 78 | rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\resources 79 | rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\png 80 | rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\wix 81 | @echo on 82 | echo package build success! 83 | echo. 84 | echo. 85 | 86 | set /A index=index+1 87 | @echo off 88 | 89 | ) 90 | 91 | :: for windows, we need replace package name to lower again 92 | :: .\script\sd.exe "\"productName\": \"WeRead\"" "\"productName\": \"weread\"" src-tauri\tauri.conf.json 93 | echo "output dir is output\windows" 94 | 95 | ::recovery code 96 | .\script\sd.exe %url% %init_url% src-tauri\pake.json 97 | .\script\sd.exe %title% %init_title% src-tauri\tauri.conf.json 98 | .\script\sd.exe %name% %init_name% src-tauri\tauri.conf.json 99 | .\script\sd.exe %name% %init_name% src-tauri\tauri.windows.conf.json -------------------------------------------------------------------------------- /script/build.ps1: -------------------------------------------------------------------------------- 1 | chcp 65001 | Out-Null 2 | 3 | if (-not (Test-Path node_modules)) { 4 | npm i 5 | } 6 | 7 | if (-not (Test-Path output)) { 8 | New-Item -ItemType Directory -Path output 9 | } 10 | 11 | if (-not (Test-Path output\windows)) { 12 | New-Item -ItemType Directory -Path output\windows 13 | } else { 14 | Remove-Item output\windows -Recurse -Force 15 | New-Item -ItemType Directory -Path output\windows 16 | } 17 | 18 | Write-Host "`n=======================" 19 | Write-Host "build for windows" 20 | Write-Host "make ture powershell == 7.2.10" 21 | Write-Host "powershell 7.2.10 download url: https://github.com/PowerShell/PowerShell/releases/tag/v7.2.10" 22 | Write-Host "Powershell info in your localhost " 23 | $PSVersionTable 24 | Write-Host "=======================`n" 25 | 26 | 27 | $identifier_prefix = "com.tw93" 28 | 29 | # total package number 30 | $index = 1 31 | $total = (Get-Content ./app.csv | Measure-Object -Line).Lines 32 | $pake_conf_path = "src-tauri/pake.json" 33 | $common_conf_path = "src-tauri/tauri.conf.json" 34 | $windows_conf_path = "src-tauri/tauri.windows.conf.json" 35 | 36 | # ignore first header line 37 | $total = $total - 1 38 | 39 | # for windows, we need replace package name to title 40 | ForEach ($line in (Get-Content -Path .\app.csv | Select-Object -Skip 1)) { 41 | $name, $title, $name_zh, $url = $line.Split(",") 42 | Write-Host "building package ${index}/${total}" 43 | Write-Host "package name is ${name} ${name_zh}" 44 | Write-Host "==========================" 45 | Write-Host "building Args is:" 46 | Write-Host "name = ${name}" 47 | Write-Host "title = ${title}" 48 | Write-Host "name_zh = ${name_zh}" 49 | Write-Host "url = ${url}" 50 | Write-Host "==========================" 51 | # -- replace url -- # 52 | # clear url with regex 53 | (Get-Content -Path $pake_conf_path -Raw) -replace '"url":\s*"[^"]*"', '"url": ""' | Set-Content -Path $pake_conf_path 54 | # replace url with no regex 55 | (Get-Content -Path $pake_conf_path -Raw) | ForEach-Object { $_.Replace('"url": ""', "`"url`": `"${url}`"") } | Set-Content $pake_conf_path 56 | 57 | 58 | # -- replace package name -- # 59 | # clear package_name with regex 60 | (Get-Content -Path $common_conf_path -Raw) -replace '"productName":\s*"[^"]*"', '"productName": ""' | Set-Content -Path $common_conf_path 61 | # replace package_name with no regex 62 | (Get-Content -Path $common_conf_path -Raw) | ForEach-Object { $_.Replace('"productName": ""', "`"productName`": `"${title}`"") } | Set-Content $common_conf_path 63 | 64 | 65 | # -- replace systemTray iconPath -- # 66 | # clear systemTray iconPath with regex 67 | (Get-Content -Path $common_conf_path -Raw) -replace '"iconPath":\s*"[^"]*"', '"iconPath": ""' | Set-Content -Path $common_conf_path 68 | # replace systemTray iconPath with no regex 69 | (Get-Content -Path $common_conf_path -Raw) | ForEach-Object { $_.Replace('"iconPath": ""', "`"iconPath`": `"png/${name}_32.ico`"") } | Set-Content $common_conf_path 70 | 71 | # -- replace icon -- 72 | # clear icon path with regex 73 | (Get-Content -Path $windows_conf_path -Raw) -replace '(?s)"icon":\s*\[[^\]]*\]', '"icon": []' | Set-Content -Path $windows_conf_path 74 | # replace icon path with no regex 75 | (Get-Content -Path $windows_conf_path -Raw) | ForEach-Object { $_.Replace('"icon": []', "`"icon`": [`"png/${name}_256.ico`", `"png/${name}_32.ico`"]") } | Set-Content $windows_conf_path 76 | 77 | # -- replace identifier -- 78 | # clear identifier with regex 79 | (Get-Content -Path $windows_conf_path -Raw) -replace '"identifier":\s*"[^"]*"', '"identifier": ""' | Set-Content -Path $windows_conf_path 80 | # -- replace identifier with no regex -- 81 | (Get-Content -Path $windows_conf_path -Raw) | ForEach-Object { $_.Replace('"identifier": ""', "`"identifier`": `"${identifier_prefix}.${name}`"") } | Set-Content $windows_conf_path 82 | 83 | # -- replace icon resources -- 84 | # clear resources with regex 85 | (Get-Content -Path $windows_conf_path -Raw) -replace '(?s)"resources":\s*\[[^\]]*\]', '"resources": []' | Set-Content -Path $windows_conf_path 86 | # replace resources with no regex 87 | (Get-Content -Path $windows_conf_path -Raw) | ForEach-Object { $_.Replace('"resources": []', "`"resources`": [`"png/${name}_32.ico`"]") } | Set-Content $windows_conf_path 88 | 89 | if (-not (Test-Path "src-tauri\png\${name}_32.ico")) { 90 | Copy-Item "src-tauri\png\icon_32.ico" "src-tauri\png\${name}_32.ico" 91 | } 92 | 93 | if (-not (Test-Path "src-tauri\png\${name}_256.ico")) { 94 | Copy-Item "src-tauri\png\icon_256.ico" "src-tauri\png\${name}_256.ico" 95 | } 96 | 97 | # build package 98 | Write-Host "npm run build:windows" 99 | npm run tauri build -- --target x86_64-pc-windows-msvc 100 | Move-Item -Path "src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\*.msi" -Destination "output\windows\${title}_x64.msi" 101 | #rm cache 102 | Remove-Item -Path "src-tauri\target\x86_64-pc-windows-msvc\release\*.exe" -Recurse -Force 103 | Remove-Item -Path "src-tauri\target\x86_64-pc-windows-msvc\release\resources\*.ico" -Recurse -Force 104 | Remove-Item -Path "src-tauri\target\x86_64-pc-windows-msvc\release\png\*.ico" -Recurse -Force 105 | Remove-Item -Path "src-tauri\target\x86_64-pc-windows-msvc\release\wix\*.*" -Recurse -Force 106 | Remove-Item -Path "src-tauri\target\x86_64-pc-windows-msvc\release\app.*" -Force 107 | Remove-Item -Path "src-tauri\target\x86_64-pc-windows-msvc\release\resources" -Recurse -Force 108 | Write-Host "package build success!" 109 | Write-Host "" 110 | $index++ 111 | # strip blank line for common_conf_path 112 | $lines = Get-Content ${common_conf_path} 113 | $lastNonEmptyLineIndex = ($lines.Count - 1) 114 | while ($lastNonEmptyLineIndex -ge 0 -and -not $lines[$lastNonEmptyLineIndex].Trim()) { 115 | $lastNonEmptyLineIndex-- 116 | } 117 | if ($lastNonEmptyLineIndex -lt ($lines.Count - 1)) { 118 | $lines = $lines[0..$lastNonEmptyLineIndex] 119 | } 120 | Set-Content -Path ${common_conf_path} -Value $lines 121 | 122 | # strip blank line for windows conf_path 123 | $lines = Get-Content ${windows_conf_path} 124 | $lastNonEmptyLineIndex = ($lines.Count - 1) 125 | while ($lastNonEmptyLineIndex -ge 0 -and -not $lines[$lastNonEmptyLineIndex].Trim()) { 126 | $lastNonEmptyLineIndex-- 127 | } 128 | if ($lastNonEmptyLineIndex -lt ($lines.Count - 1)) { 129 | $lines = $lines[0..$lastNonEmptyLineIndex] 130 | } 131 | Set-Content -Path ${windows_conf_path} -Value $lines 132 | } 133 | 134 | Write-Host "output dir is output\windows" 135 | -------------------------------------------------------------------------------- /script/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d "node_modules" ]; then 4 | npm i 5 | fi 6 | 7 | 8 | if [ ! -d "output" ]; then 9 | mkdir output 10 | fi 11 | 12 | if [[ "$OSTYPE" =~ ^linux ]]; then 13 | if [ ! -d "output/linux" ]; then 14 | mkdir output/linux 15 | fi 16 | fi 17 | 18 | 19 | if [[ "$OSTYPE" =~ ^darwin ]]; then 20 | if [ ! -d "output/macos" ]; then 21 | mkdir output/macos 22 | fi 23 | fi 24 | 25 | SHELL_FOLDER=$(cd "$(dirname "$0")" || exit 1; pwd) 26 | PROJECT_FOLDER=`dirname ${SHELL_FOLDER}` 27 | echo "shell folder is ${SHELL_FOLDER}" 28 | echo "project folder is ${PROJECT_FOLDER}" 29 | 30 | # total app number, ignore first line 31 | total=$(sed -n '$=' app.csv) 32 | export total=$((total-1)) 33 | export index=1 34 | 35 | export package_prefix="com-tw93" 36 | export identifier_prefix="com.tw93" 37 | 38 | 39 | 40 | if [[ "$OSTYPE" =~ ^linux ]]; then 41 | echo "===============" 42 | echo "Build for Linux" 43 | echo "===============" 44 | export sd=${SHELL_FOLDER}/sd-linux-`arch` 45 | chmod +x "$sd" 46 | export desktop_file="src-tauri/assets/*.desktop" 47 | fi 48 | 49 | if [[ "$OSTYPE" =~ ^darwin ]]; then 50 | echo "===============" 51 | echo "Build for MacOS" 52 | echo "===============" 53 | 54 | export sd=${SHELL_FOLDER}/sd-apple-x64 55 | chmod +x "$sd" 56 | fi 57 | 58 | tail -n +2 app.csv | while IFS=, read -r -a arr; 59 | do 60 | package_name=${arr[0]} 61 | package_title=${arr[1]} 62 | package_zh_name=${arr[2]} 63 | url=${arr[3]} 64 | 65 | # replace package info 66 | # clear url with regex 67 | $sd "\"url\": \"(.*?)\"," "\"url\": \"\"," src-tauri/pake.json 68 | # replace url with no regex 69 | $sd -s "\"url\": \"\"," "\"url\": \"${url}\"," src-tauri/pake.json 70 | 71 | # for apple, need replace title 72 | if [[ "$OSTYPE" =~ ^darwin ]]; then 73 | # update icon 74 | # if icon exists, change icon path 75 | if [ ! -f "src-tauri/icons/${package_name}.icns" ]; then 76 | # else, replace icon to default 77 | echo "warning" 78 | echo "icon for MacOS not exist, will use default icon to replace it" 79 | echo "warning" 80 | cp "src-tauri/icons/icon.icns" "src-tauri/icons/${package_name}.icns" 81 | fi 82 | # clear package_name with regex 83 | $sd "\"productName\": \"(.*?)\"," "\"productName\": \"\"," src-tauri/tauri.conf.json 84 | # replace package_name with no regex 85 | $sd -s "\"productName\": \"\"," "\"productName\": \"${package_title}\"," src-tauri/tauri.conf.json 86 | # clear icon path with regex 87 | $sd "\"icon\": \[\"(.*?)\"\]," "\"icon\": [\"\"]," src-tauri/tauri.macos.conf.json 88 | # replace icon path with no regex 89 | $sd -s "\"icon\": [\"\"]," "\"icon\": [\"icons/${package_name}.icns\"]," src-tauri/tauri.macos.conf.json 90 | # clear identifier with regex 91 | $sd "\"identifier\": \"(.*?)\"," "\"identifier\": \"\"," src-tauri/tauri.macos.conf.json 92 | # replace identifier with not regex 93 | $sd -s "\"identifier\": \"\"," "\"identifier\": \"${identifier_prefix}.${package_name}\"," src-tauri/tauri.macos.conf.json 94 | fi 95 | 96 | # echo "update ico with 32x32 picture" 97 | # cp "src-tauri/png/${package_name}_32.ico" "src-tauri/icons/icon.ico" 98 | 99 | if [[ "$OSTYPE" =~ ^linux ]]; then 100 | # update icon 101 | # if icon exists, change icon path 102 | if [ ! -f "src-tauri/png/${package_name}_512.png" ]; then 103 | # else, replace icon to default 104 | echo "warning" 105 | echo "icon for linux not exist, will use default icon to replace it" 106 | echo "warning" 107 | cp "src-tauri/png/icon_512.png" "src-tauri/png/${package_name}_512.png" 108 | fi 109 | # -- replace package name -- # 110 | # clear package_name with regex 111 | $sd "\"productName\": \"(.*?)\"," "\"productName\": \"\"," src-tauri/tauri.conf.json 112 | # replace package_name with no regex 113 | $sd -s "\"productName\": \"\"," "\"productName\": \"${package_prefix}-${package_name}\"," src-tauri/tauri.conf.json 114 | 115 | # -- replace systemTray iconPath -- # 116 | # clear systemTray iconPath with regex 117 | $sd "\"iconPath\": \"(.*?)\"," "\"iconPath\": \"\"," src-tauri/tauri.conf.json 118 | # replace systemTray iconPath with no regex 119 | $sd -s "\"iconPath\": \"\"," "\"iconPath\": \"png/${package_name}_512.png\"," src-tauri/tauri.conf.json 120 | 121 | # -- replace icon -- # 122 | # clear icon path with regex 123 | $sd "\"icon\": \[\"(.*?)\"\]," "\"icon\": [\"\"]," src-tauri/tauri.linux.conf.json 124 | # replace icon path with no regex 125 | $sd -s "\"icon\": [\"\"]," "\"icon\": [\"png/${package_name}_512.png\"]," src-tauri/tauri.linux.conf.json 126 | 127 | # -- replace identifier -- # 128 | # clear identifier with regex 129 | $sd "\"identifier\": \"(.*?)\"," "\"identifier\": \"\"," src-tauri/tauri.linux.conf.json 130 | # replace identifier with not regex 131 | $sd -s "\"identifier\": \"\"," "\"identifier\": \"${identifier_prefix}.${package_name}\"," src-tauri/tauri.linux.conf.json 132 | echo "update desktop" 133 | 134 | new_desktop="${PROJECT_FOLDER}/src-tauri/assets/${package_prefix}-${package_name}.desktop" 135 | new_desktop_map_path="/usr/share/applications/${package_prefix}-${package_name}.desktop" 136 | for file in `ls ${PROJECT_FOLDER}/src-tauri/assets/*.desktop` 137 | do 138 | mv "${file}" "${new_desktop}" 139 | echo mv "${file}" "${new_desktop}" 140 | done 141 | # clear desktop file with regex 142 | $sd "\"files\": \{\"(.*)\"\}" "\"files\": \{\"\"\}" src-tauri/tauri.linux.conf.json 143 | # replace desktop file with no regex 144 | $sd -s "\"files\": \{\"\"\}" "\"files\": {\"${new_desktop_map_path}\": \"${new_desktop}\"}" src-tauri/tauri.linux.conf.json 145 | # clear desktop content with regex 146 | $sd "Exec=.*" "Exec=" "${new_desktop}" 147 | $sd "Icon=.*" "Icon=" "${new_desktop}" 148 | $sd "Name=.*" "Name=" "${new_desktop}" 149 | $sd "Name\[zh_CN\]=.*" "Name[zh_CN]=" "${new_desktop}" 150 | # repleace dekstop content with no reg 151 | $sd -s "Exec=" "Exec=${package_prefix}-${package_name}" "${new_desktop}" 152 | $sd -s "Icon=" "Icon=${package_prefix}-${package_name}" "${new_desktop}" 153 | $sd -s "Name=" "Name=${package_title}" "${new_desktop}" 154 | $sd -s "Name[zh_CN]=" "Name[zh_CN]=${package_zh_name}" "${new_desktop}" 155 | fi 156 | 157 | echo "building package ${index}/${total}" 158 | echo "package name is ${package_name} (${package_zh_name})" 159 | 160 | if [[ "$OSTYPE" =~ ^linux ]]; then 161 | npm run tauri build 162 | mv src-tauri/target/release/bundle/deb/${package_prefix}-"${package_name}"*.deb output/linux/"${package_title}"_`arch`.deb 163 | mv src-tauri/target/release/bundle/appimage/${package_prefix}-"${package_name}"*.AppImage output/linux/"${package_title}"_`arch`.AppImage 164 | echo clear cache 165 | rm src-tauri/target/release 166 | rm -rf src-tauri/target/release/bundle 167 | 168 | fi 169 | 170 | if [[ "$OSTYPE" =~ ^darwin ]]; then 171 | 172 | npm run tauri build -- --target universal-apple-darwin 173 | mv src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg output/macos/"${package_title}".dmg 174 | echo clear cache 175 | rm -rf src-tauri/target/universal-apple-darwin 176 | rm src-tauri/target/aarch64-apple-darwin/release 177 | rm src-tauri/target/x86_64-apple-darwin/release 178 | fi 179 | 180 | echo "package build success!" 181 | index=$((index+1)) 182 | done 183 | 184 | echo "build all package success!" 185 | echo "you run 'rm src-tauri/assets/*.desktop && git checkout src-tauri' to recovery code" 186 | -------------------------------------------------------------------------------- /script/build_with_pake_cli.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Welcome to use Powershell" 2 | Write-Host "`n=======================" 3 | Write-Host "build for windows" 4 | Write-Host "make ture powershell == 7.2.10" 5 | Write-Host "powershell 7.2.10 download url: https://github.com/PowerShell/PowerShell/releases/tag/v7.2.10" 6 | Write-Host "Powershell info in your localhost " 7 | $PSVersionTable 8 | Write-Host "`n=======================`n" 9 | 10 | Write-Host "`n=======================" 11 | Write-Host "pake parameters is: " 12 | Write-Host "url: " $env:URL 13 | Write-Host "name: " $env:NAME 14 | Write-Host "icon: " $env:ICON 15 | Write-Host "height: " $env:HEIGHT 16 | Write-Host "width: " $env:WIDTH 17 | Write-Host "transparent: " $env:TRANSPARENT 18 | # Write-Host "fullscreen: " $env:FULLSCREEN 19 | Write-Host "resize: " $env:RESIZE 20 | Write-Host "is multi arch? only for Mac: " $env:MULTI_ARCH 21 | Write-Host "targets type? only for Linux: " $env:TARGETS 22 | Write-Host "===========================`n" 23 | 24 | 25 | Set-Location node_modules/pake-cli 26 | # init params 27 | ${Params}="node cli.js $env:URL --name $env:NAME" 28 | 29 | # download icon 30 | if ((($null -ne $env:ICON) -and ($env:ICON -ne ""))){ 31 | if ($IsLinux) { 32 | curl -L "$env:ICON" -o icon.png 33 | ${Params}="${Params} --icon icon.png" 34 | } elseif ($IsMacOS) { 35 | curl -L "$env:ICON" -o icon.icns 36 | ${Params}="${Params} --icon icon.icns" 37 | } elseif ($IsWindows) { 38 | curl -L "$env:ICON" -o icon.ico 39 | ${Params}="${Params} --icon icon.ico" 40 | } else { 41 | Write-Host "it won't download icon, becase it can't detect you OS system!" 42 | } 43 | } 44 | 45 | # height && weight 46 | ${Params}="${Params} --height $env:HEIGHT --width $env:WIDTH" 47 | 48 | # transparent 49 | if ("$env:TRANSPARENT" -eq "true") { 50 | ${Params}="${Params} --transparent" 51 | } 52 | 53 | # fullscreen 54 | # if ("$env:FULLSCREEN" -eq "true") { 55 | # ${Params}="${Params} --fullscreen" 56 | # } 57 | 58 | # resize 59 | if ("$env:FULLSCREEN" -eq "true" ) { 60 | ${Params}="${Params} --resize" 61 | } 62 | 63 | # multi-arch only for mac 64 | if (($env:MULTI_ARCH -eq "true") -and ($IsMacOS)) { 65 | rustup target add aarch64-apple-darwin 66 | ${Params}="${Params} --multi-arch" 67 | } 68 | 69 | # targets type, only for linux 70 | if (($null -ne $env:TARGETS) -and ($env:TARGETS -ne "") -and ($IsLinux)) { 71 | ${Params}="${Params} --targets $env:TARGETS" 72 | } 73 | 74 | # add systemTray for Window / Linux default 75 | if ($IsWindows) { 76 | ${Params}="${Params} --show-system-tray" 77 | } 78 | 79 | if ($IsLinux) { 80 | ${Params}="${Params} --show-system-tray" 81 | } 82 | 83 | # add menu for MacOS default 84 | if ($IsMacOS) { 85 | ${Params}="${Params} --show-menu" 86 | } 87 | 88 | Write-Host "Pake parameters is: ${Params}" 89 | Write-Host "compile...." 90 | Invoke-Expression $Params 91 | 92 | # output 93 | if (-not(Test-Path output)) { 94 | New-Item -ItemType Directory -Path "output" 95 | } 96 | Move-Item -Path "$env:NAME.*" -Destination "output/" 97 | Write-Host "Build Success" 98 | Set-Location ../.. 99 | -------------------------------------------------------------------------------- /script/sd-apple-x64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/script/sd-apple-x64 -------------------------------------------------------------------------------- /script/sd-linux-aarch64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/script/sd-linux-aarch64 -------------------------------------------------------------------------------- /script/sd-linux-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/script/sd-linux-x86_64 -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | description = "🤱🏻 Turn any webpage into a desktop app with Rust." 5 | authors = ["Tw93"] 6 | license = "MIT" 7 | repository = "https://github.com/tw93/Pake" 8 | default-run = "app" 9 | edition = "2021" 10 | rust-version = "1.63.0" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "1.2.1", features = [] } 16 | 17 | [dependencies] 18 | serde_json = "1.0.89" 19 | serde = { version = "1.0.150", features = ["derive"] } 20 | tauri = { version = "1.2.4", features = ["api-all", "system-tray"] } 21 | download_rs = { version = "0.2.0", features = ["sync_download"] } 22 | tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } 23 | 24 | [dev-dependencies] 25 | cargo-bloat = "0.11.1" 26 | 27 | [features] 28 | # by default Tauri runs in production mode 29 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 30 | default = ["custom-protocol"] 31 | # this feature is used used for production builds where `devPath` points to the filesystem 32 | # DO NOT remove this 33 | custom-protocol = ["tauri/custom-protocol"] 34 | -------------------------------------------------------------------------------- /src-tauri/assets/com-tw93-weread.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Categories=Office 4 | Exec=com-tw93-weread 5 | Icon=com-tw93-weread 6 | Name=com-tw93-weread 7 | Name[zh_CN]=微信阅读 8 | StartupNotify=true 9 | Terminal=false 10 | Type=Application -------------------------------------------------------------------------------- /src-tauri/assets/main.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | {{#if allow_downgrades}} 33 | 34 | {{else}} 35 | 36 | {{/if}} 37 | 38 | 39 | Installed AND NOT UPGRADINGPRODUCTCODE 40 | 41 | 42 | 43 | 44 | {{#if banner_path}} 45 | 46 | {{/if}} 47 | {{#if dialog_image_path}} 48 | 49 | {{/if}} 50 | {{#if license}} 51 | 52 | {{/if}} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed 72 | 73 | 74 | 75 | {{#unless license}} 76 | 77 | 1 82 | 1 87 | {{/unless}} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | {{#each binaries as |bin| ~}} 118 | 119 | 120 | 121 | {{/each~}} 122 | {{#if enable_elevated_update_task}} 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | {{/if}} 133 | {{{resources}}} 134 | 135 | 136 | 141 | 142 | 144 | 145 | 151 | 152 | 153 | 154 | 155 | 156 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | {{#each merge_modules as |msm| ~}} 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | {{/each~}} 178 | 179 | 188 | 189 | 190 | 191 | {{#each resource_file_ids as |resource_file_id| ~}} 192 | 193 | {{/each~}} 194 | 195 | {{#if enable_elevated_update_task}} 196 | 197 | 198 | 199 | {{/if}} 200 | 201 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 216 | 217 | {{#each binaries as |bin| ~}} 218 | 219 | {{/each~}} 220 | 221 | 222 | 223 | 224 | {{#each component_group_refs as |id| ~}} 225 | 226 | {{/each~}} 227 | {{#each component_refs as |id| ~}} 228 | 229 | {{/each~}} 230 | {{#each feature_group_refs as |id| ~}} 231 | 232 | {{/each~}} 233 | {{#each feature_refs as |id| ~}} 234 | 235 | {{/each~}} 236 | {{#each merge_refs as |id| ~}} 237 | 238 | {{/each~}} 239 | 240 | 241 | {{#if install_webview}} 242 | 243 | 244 | 245 | 246 | 247 | 248 | {{#if download_bootstrapper}} 249 | 250 | 251 | 252 | 253 | 254 | 255 | {{/if}} 256 | 257 | 258 | {{#if webview2_bootstrapper_path}} 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | {{/if}} 267 | 268 | 269 | {{#if webview2_installer_path}} 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | {{/if}} 278 | 279 | {{/if}} 280 | 281 | {{#if enable_elevated_update_task}} 282 | 283 | 290 | 291 | 292 | NOT(REMOVE) 293 | 294 | 295 | 296 | 301 | 302 | 303 | (REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE 304 | 305 | 306 | {{/if}} 307 | 308 | 309 | 310 | 311 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/cn_config.bak: -------------------------------------------------------------------------------- 1 | [source.crates-io] 2 | # To use sparse index, change 'rsproxy' to 'rsproxy-sparse' 3 | replace-with = 'rsproxy' 4 | 5 | [source.rsproxy] 6 | registry = "https://rsproxy.cn/crates.io-index" 7 | [source.rsproxy-sparse] 8 | registry = "sparse+https://rsproxy.cn/index/" 9 | 10 | [registries.rsproxy] 11 | index = "https://rsproxy.cn/crates.io-index" 12 | 13 | [net] 14 | git-fetch-with-cli = true 15 | -------------------------------------------------------------------------------- /src-tauri/icons/ails.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/ails.icns -------------------------------------------------------------------------------- /src-tauri/icons/chatgpt.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/chatgpt.icns -------------------------------------------------------------------------------- /src-tauri/icons/coderunner.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/coderunner.icns -------------------------------------------------------------------------------- /src-tauri/icons/flomo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/flomo.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/lizhi.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/lizhi.icns -------------------------------------------------------------------------------- /src-tauri/icons/loop.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/loop.icns -------------------------------------------------------------------------------- /src-tauri/icons/poe.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/poe.icns -------------------------------------------------------------------------------- /src-tauri/icons/programmusic.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/programmusic.icns -------------------------------------------------------------------------------- /src-tauri/icons/qwerty.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/qwerty.icns -------------------------------------------------------------------------------- /src-tauri/icons/reference.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/reference.icns -------------------------------------------------------------------------------- /src-tauri/icons/tool.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/tool.icns -------------------------------------------------------------------------------- /src-tauri/icons/twitter.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/twitter.icns -------------------------------------------------------------------------------- /src-tauri/icons/wechat.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/wechat.icns -------------------------------------------------------------------------------- /src-tauri/icons/weread.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/weread.icns -------------------------------------------------------------------------------- /src-tauri/icons/witeboard.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/witeboard.icns -------------------------------------------------------------------------------- /src-tauri/icons/xapi.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/xapi.icns -------------------------------------------------------------------------------- /src-tauri/icons/xiaohongshu.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/xiaohongshu.icns -------------------------------------------------------------------------------- /src-tauri/icons/youtube.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/youtube.icns -------------------------------------------------------------------------------- /src-tauri/icons/yuque.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/yuque.icns -------------------------------------------------------------------------------- /src-tauri/icons/zlibrary.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/icons/zlibrary.icns -------------------------------------------------------------------------------- /src-tauri/pake.json: -------------------------------------------------------------------------------- 1 | { 2 | "windows": [ 3 | { 4 | "url": "https://weread.qq.com/", 5 | "transparent": true, 6 | "fullscreen": false, 7 | "width": 1200, 8 | "height": 780, 9 | "resizable": true, 10 | "url_type": "web" 11 | } 12 | ], 13 | "user_agent": { 14 | "macos": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15 AI.LS.Desktop/0.0.2 (Mac)", 15 | "linux": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 AI.LS.Desktop/0.0.2 (Linux)", 16 | "windows": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 AI.LS.Desktop/0.0.2 (Windows)" 17 | }, 18 | "menu": { 19 | "macos": true, 20 | "linux": false, 21 | "windows": false 22 | }, 23 | "system_tray": { 24 | "macos": false, 25 | "linux": true, 26 | "windows": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src-tauri/png/ails_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/ails_256.ico -------------------------------------------------------------------------------- /src-tauri/png/ails_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/ails_32.ico -------------------------------------------------------------------------------- /src-tauri/png/ails_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/ails_512.png -------------------------------------------------------------------------------- /src-tauri/png/chatgpt_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/chatgpt_256.ico -------------------------------------------------------------------------------- /src-tauri/png/chatgpt_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/chatgpt_32.ico -------------------------------------------------------------------------------- /src-tauri/png/chatgpt_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/chatgpt_512.png -------------------------------------------------------------------------------- /src-tauri/png/code_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/code_256.ico -------------------------------------------------------------------------------- /src-tauri/png/code_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/code_32.ico -------------------------------------------------------------------------------- /src-tauri/png/code_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/code_512.png -------------------------------------------------------------------------------- /src-tauri/png/coderunner_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/coderunner_256.ico -------------------------------------------------------------------------------- /src-tauri/png/coderunner_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/coderunner_32.ico -------------------------------------------------------------------------------- /src-tauri/png/coderunner_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/coderunner_512.png -------------------------------------------------------------------------------- /src-tauri/png/flomo_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/flomo_256.ico -------------------------------------------------------------------------------- /src-tauri/png/flomo_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/flomo_32.ico -------------------------------------------------------------------------------- /src-tauri/png/flomo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/flomo_512.png -------------------------------------------------------------------------------- /src-tauri/png/icon_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/icon_256.ico -------------------------------------------------------------------------------- /src-tauri/png/icon_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/icon_32.ico -------------------------------------------------------------------------------- /src-tauri/png/icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/icon_512.png -------------------------------------------------------------------------------- /src-tauri/png/lizhi_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/lizhi_256.ico -------------------------------------------------------------------------------- /src-tauri/png/lizhi_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/lizhi_32.ico -------------------------------------------------------------------------------- /src-tauri/png/lizhi_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/lizhi_512.png -------------------------------------------------------------------------------- /src-tauri/png/loop_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/loop_256.ico -------------------------------------------------------------------------------- /src-tauri/png/loop_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/loop_32.ico -------------------------------------------------------------------------------- /src-tauri/png/loop_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/loop_512.png -------------------------------------------------------------------------------- /src-tauri/png/poe_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/poe_256.ico -------------------------------------------------------------------------------- /src-tauri/png/poe_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/poe_32.ico -------------------------------------------------------------------------------- /src-tauri/png/poe_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/poe_512.png -------------------------------------------------------------------------------- /src-tauri/png/programmusic_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/programmusic_256.ico -------------------------------------------------------------------------------- /src-tauri/png/programmusic_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/programmusic_32.ico -------------------------------------------------------------------------------- /src-tauri/png/programmusic_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/programmusic_512.png -------------------------------------------------------------------------------- /src-tauri/png/qwerty_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/qwerty_256.ico -------------------------------------------------------------------------------- /src-tauri/png/qwerty_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/qwerty_32.ico -------------------------------------------------------------------------------- /src-tauri/png/qwerty_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/qwerty_512.png -------------------------------------------------------------------------------- /src-tauri/png/reference_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/reference_256.ico -------------------------------------------------------------------------------- /src-tauri/png/reference_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/reference_32.ico -------------------------------------------------------------------------------- /src-tauri/png/reference_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/reference_512.png -------------------------------------------------------------------------------- /src-tauri/png/tool_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/tool_256.ico -------------------------------------------------------------------------------- /src-tauri/png/tool_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/tool_32.ico -------------------------------------------------------------------------------- /src-tauri/png/tool_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/tool_512.png -------------------------------------------------------------------------------- /src-tauri/png/twitter_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/twitter_256.ico -------------------------------------------------------------------------------- /src-tauri/png/twitter_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/twitter_32.ico -------------------------------------------------------------------------------- /src-tauri/png/twitter_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/twitter_512.png -------------------------------------------------------------------------------- /src-tauri/png/wechat_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/wechat_256.ico -------------------------------------------------------------------------------- /src-tauri/png/wechat_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/wechat_32.ico -------------------------------------------------------------------------------- /src-tauri/png/wechat_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/wechat_512.png -------------------------------------------------------------------------------- /src-tauri/png/weread_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/weread_256.ico -------------------------------------------------------------------------------- /src-tauri/png/weread_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/weread_32.ico -------------------------------------------------------------------------------- /src-tauri/png/weread_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/weread_512.png -------------------------------------------------------------------------------- /src-tauri/png/witeboard_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/witeboard_256.ico -------------------------------------------------------------------------------- /src-tauri/png/witeboard_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/witeboard_32.ico -------------------------------------------------------------------------------- /src-tauri/png/witeboard_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/witeboard_512.png -------------------------------------------------------------------------------- /src-tauri/png/xapi_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/xapi_256.ico -------------------------------------------------------------------------------- /src-tauri/png/xapi_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/xapi_32.ico -------------------------------------------------------------------------------- /src-tauri/png/xapi_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/xapi_512.png -------------------------------------------------------------------------------- /src-tauri/png/xiaohongshu_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/xiaohongshu_256.ico -------------------------------------------------------------------------------- /src-tauri/png/xiaohongshu_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/xiaohongshu_32.ico -------------------------------------------------------------------------------- /src-tauri/png/xiaohongshu_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/xiaohongshu_512.png -------------------------------------------------------------------------------- /src-tauri/png/youtube_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/youtube_256.ico -------------------------------------------------------------------------------- /src-tauri/png/youtube_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/youtube_32.ico -------------------------------------------------------------------------------- /src-tauri/png/youtube_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/youtube_512.png -------------------------------------------------------------------------------- /src-tauri/png/yuque_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/yuque_256.ico -------------------------------------------------------------------------------- /src-tauri/png/yuque_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/yuque_32.ico -------------------------------------------------------------------------------- /src-tauri/png/yuque_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/yuque_512.png -------------------------------------------------------------------------------- /src-tauri/png/zlibrary_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/zlibrary_256.ico -------------------------------------------------------------------------------- /src-tauri/png/zlibrary_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/zlibrary_32.ico -------------------------------------------------------------------------------- /src-tauri/png/zlibrary_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airyland/Pake/8d41ee3a6c164a4a9dca9ac692e4bd35a36782ca/src-tauri/png/zlibrary_512.png -------------------------------------------------------------------------------- /src-tauri/src/app/config.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Debug, Deserialize)] 4 | pub struct WindowConfig { 5 | pub url: String, 6 | pub transparent: bool, 7 | pub fullscreen: bool, 8 | pub width: f64, 9 | pub height: f64, 10 | pub resizable: bool, 11 | pub url_type: String, 12 | } 13 | 14 | #[derive(Debug, Deserialize)] 15 | pub struct PlatformSpecific { 16 | pub macos: T, 17 | pub linux: T, 18 | pub windows: T, 19 | } 20 | 21 | impl PlatformSpecific { 22 | pub const fn get(&self) -> &T { 23 | #[cfg(target_os = "macos")] 24 | let platform = &self.macos; 25 | #[cfg(target_os = "linux")] 26 | let platform = &self.linux; 27 | #[cfg(target_os = "windows")] 28 | let platform = &self.windows; 29 | 30 | platform 31 | } 32 | } 33 | 34 | impl PlatformSpecific 35 | where 36 | T: Copy, 37 | { 38 | pub const fn copied(&self) -> T { 39 | *self.get() 40 | } 41 | } 42 | 43 | pub type UserAgent = PlatformSpecific; 44 | pub type FunctionON = PlatformSpecific; 45 | 46 | #[derive(Debug, Deserialize)] 47 | pub struct PakeConfig { 48 | pub windows: Vec, 49 | pub user_agent: UserAgent, 50 | pub menu: FunctionON, 51 | pub system_tray: FunctionON, 52 | } 53 | 54 | impl PakeConfig { 55 | pub fn show_menu(&self) -> bool { 56 | self.menu.copied() 57 | } 58 | 59 | #[cfg(not(target_os = "macos"))] 60 | pub fn show_system_tray(&self) -> bool { 61 | self.system_tray.copied() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src-tauri/src/app/invoke.rs: -------------------------------------------------------------------------------- 1 | use crate::util::{check_file_or_append, get_download_message, show_toast}; 2 | use download_rs::sync_download::Download; 3 | use tauri::{api, command, AppHandle, Manager, Window}; 4 | 5 | #[derive(serde::Deserialize)] 6 | pub struct DownloadFileParams { 7 | url: String, 8 | filename: String, 9 | } 10 | 11 | #[command] 12 | pub fn drag_window(app: AppHandle) { 13 | app.get_window("pake").unwrap().start_dragging().unwrap(); 14 | } 15 | 16 | #[command] 17 | pub fn fullscreen(app: AppHandle) { 18 | let win = app.get_window("pake").unwrap(); 19 | if win.is_fullscreen().unwrap() { 20 | win.set_fullscreen(false).unwrap(); 21 | } else { 22 | win.set_fullscreen(true).unwrap(); 23 | } 24 | } 25 | 26 | #[command] 27 | pub fn open_browser(app: AppHandle, url: String) { 28 | api::shell::open(&app.shell_scope(), url, None).unwrap(); 29 | } 30 | 31 | #[command] 32 | pub async fn download_file(app: AppHandle, params: DownloadFileParams) -> Result<(), String> { 33 | let window: Window = app.get_window("pake").unwrap(); 34 | let output_path = api::path::download_dir().unwrap().join(params.filename); 35 | let file_path = check_file_or_append(output_path.to_str().unwrap()); 36 | let download = Download::new(¶ms.url, Some(&file_path), None); 37 | match download.download() { 38 | Ok(_) => { 39 | show_toast(&window, &get_download_message()); 40 | Ok(()) 41 | } 42 | Err(e) => { 43 | show_toast(&window, &e.to_string()); 44 | Err(e.to_string()) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src-tauri/src/app/menu.rs: -------------------------------------------------------------------------------- 1 | use tauri::MenuItem; 2 | 3 | use tauri::{CustomMenuItem, Menu, Submenu, WindowMenuEvent}; 4 | 5 | #[cfg(any(target_os = "linux", target_os = "windows"))] 6 | use tauri::{Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl}; 7 | 8 | #[cfg(any(target_os = "linux", target_os = "windows"))] 9 | use tauri_plugin_window_state::{AppHandleExt, StateFlags}; 10 | 11 | pub fn get_menu() -> Menu { 12 | let close = CustomMenuItem::new("close".to_string(), "Close Window").accelerator("CmdOrCtrl+W"); 13 | let goto_url_item = CustomMenuItem::new("goto_url".to_string(), "Go to URL...") 14 | .accelerator("CmdOrCtrl+Shift+L"); 15 | let first_menu = Menu::new() 16 | .add_native_item(MenuItem::Copy) 17 | .add_native_item(MenuItem::Cut) 18 | .add_native_item(MenuItem::Paste) 19 | .add_native_item(MenuItem::Undo) 20 | .add_native_item(MenuItem::Redo) 21 | .add_native_item(MenuItem::SelectAll) 22 | .add_native_item(MenuItem::Separator) 23 | .add_item(goto_url_item) 24 | .add_native_item(MenuItem::Separator) 25 | .add_native_item(MenuItem::EnterFullScreen) 26 | .add_native_item(MenuItem::Minimize) 27 | .add_native_item(MenuItem::Hide) 28 | .add_native_item(MenuItem::HideOthers) 29 | .add_native_item(MenuItem::ShowAll) 30 | .add_native_item(MenuItem::Separator) 31 | .add_item(close) 32 | .add_native_item(MenuItem::Quit); 33 | 34 | let app_menu = Submenu::new("File", first_menu); 35 | Menu::new().add_submenu(app_menu) 36 | } 37 | 38 | pub fn menu_event_handle(event: WindowMenuEvent) { 39 | if event.menu_item_id() == "close" { 40 | event.window().minimize().expect("can't minimize window"); 41 | event.window().eval("toggleVideoPlayback(true);").unwrap(); 42 | } 43 | 44 | if event.menu_item_id() == "goto_url" { 45 | let js_code = "showUrlModal();"; 46 | event.window().eval(js_code).unwrap(); 47 | } 48 | } 49 | 50 | #[cfg(any(target_os = "linux", target_os = "windows"))] 51 | pub fn get_system_tray(show_menu: bool) -> SystemTray { 52 | let hide_app = CustomMenuItem::new("hide_app".to_string(), "Hide App"); 53 | let show_app = CustomMenuItem::new("show_app".to_string(), "Show App"); 54 | let quit = CustomMenuItem::new("quit".to_string(), "Quit"); 55 | let about = CustomMenuItem::new("about".to_string(), "About"); 56 | let tray_menu = SystemTrayMenu::new().add_item(hide_app).add_item(show_app); 57 | if show_menu { 58 | let hide_menu = CustomMenuItem::new("hide_menu".to_string(), "Hide Menu"); 59 | let show_menu = CustomMenuItem::new("show_menu".to_string(), "Show Menu"); 60 | let tray_menu = tray_menu 61 | .add_item(hide_menu) 62 | .add_item(show_menu) 63 | .add_item(quit) 64 | .add_item(about); 65 | SystemTray::new().with_menu(tray_menu) 66 | } else { 67 | let tray_menu = tray_menu.add_item(quit).add_item(about); 68 | SystemTray::new().with_menu(tray_menu) 69 | } 70 | } 71 | 72 | #[cfg(any(target_os = "linux", target_os = "windows"))] 73 | pub fn system_tray_handle(app: &tauri::AppHandle, event: SystemTrayEvent) { 74 | if let SystemTrayEvent::MenuItemClick { tray_id: _, id, .. } = event { 75 | match id.as_str() { 76 | "hide_app" => { 77 | app.get_window("pake").unwrap().hide().unwrap(); 78 | } 79 | "show_app" => { 80 | app.get_window("pake").unwrap().show().unwrap(); 81 | } 82 | "hide_menu" => { 83 | app.get_window("pake") 84 | .unwrap() 85 | .menu_handle() 86 | .hide() 87 | .unwrap(); 88 | } 89 | "show_menu" => { 90 | app.get_window("pake") 91 | .unwrap() 92 | .menu_handle() 93 | .show() 94 | .unwrap(); 95 | } 96 | "quit" => { 97 | let _res = app.save_window_state(StateFlags::all()); 98 | std::process::exit(0); 99 | } 100 | "about" => { 101 | let _about_window = WindowBuilder::new( 102 | app, 103 | "about", 104 | WindowUrl::App(std::path::PathBuf::from("about_pake.html")), 105 | ) 106 | .resizable(true) 107 | .title("About") 108 | .inner_size(600.0, 400.0) 109 | .build() 110 | .expect("can't open about!"); 111 | } 112 | _ => {} 113 | } 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /src-tauri/src/app/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod invoke; 3 | pub mod menu; 4 | pub mod window; 5 | -------------------------------------------------------------------------------- /src-tauri/src/app/window.rs: -------------------------------------------------------------------------------- 1 | use crate::app::config::PakeConfig; 2 | use std::path::PathBuf; 3 | use tauri::{App, Window, WindowBuilder, WindowUrl}; 4 | 5 | #[cfg(target_os = "macos")] 6 | use tauri::TitleBarStyle; 7 | 8 | pub fn get_window(app: &mut App, config: PakeConfig, _data_dir: PathBuf) -> Window { 9 | let window_config = config 10 | .windows 11 | .first() 12 | .expect("At least one window configuration is required"); 13 | 14 | let user_agent = config.user_agent.get(); 15 | 16 | let url = match window_config.url_type.as_str() { 17 | "web" => WindowUrl::App(window_config.url.parse().unwrap()), 18 | "local" => WindowUrl::App(PathBuf::from(&window_config.url)), 19 | _ => panic!("url type can only be web or local"), 20 | }; 21 | 22 | let mut window_builder = WindowBuilder::new(app, "pake", url) 23 | .title("") 24 | .user_agent(user_agent) 25 | .visible(false) // Prevent initial shaking 26 | .resizable(window_config.resizable) 27 | .fullscreen(window_config.fullscreen) 28 | .inner_size(window_config.width, window_config.height) 29 | .initialization_script(include_str!("../inject/style.js")) 30 | .initialization_script(include_str!("../inject/event.js")) 31 | .initialization_script(include_str!("../inject/component.js")); 32 | 33 | #[cfg(target_os = "macos")] 34 | { 35 | let title_bar_style = if window_config.transparent { 36 | TitleBarStyle::Overlay 37 | } else { 38 | TitleBarStyle::Visible 39 | }; 40 | window_builder = window_builder.title_bar_style(title_bar_style) 41 | } 42 | 43 | #[cfg(not(target_os = "macos"))] 44 | { 45 | window_builder = window_builder.data_directory(_data_dir); 46 | } 47 | 48 | window_builder.build().unwrap() 49 | } 50 | -------------------------------------------------------------------------------- /src-tauri/src/inject/component.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | // Create a modal 3 | const modalHtml = ` 4 |
5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | `; 15 | 16 | const modalStyle = ` 17 | .pake-modal { 18 | display: none; 19 | position: fixed; 20 | z-index: 1000; 21 | left: 0; 22 | top: 0; 23 | width: 100%; 24 | height: 100%; 25 | background-color: rgba(0, 0, 0, 0.4); 26 | } 27 | 28 | .pake-modal-container { 29 | display: flex; 30 | align-items: center; 31 | justify-content: center; 32 | width: 100%; 33 | height: 100%; 34 | } 35 | 36 | .pake-modal-content { 37 | background-color: #fff; 38 | padding: 20px; 39 | border-radius: 10px; 40 | width: 80%; 41 | max-width: 400px; 42 | font-size:14px; 43 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); 44 | } 45 | 46 | .pake-modal-content label { 47 | display: block; 48 | color: #11182B; 49 | margin-bottom: 12px; 50 | font-weight: bold; 51 | } 52 | 53 | .pake-modal-content input[type="text"] { 54 | width: 90%; 55 | padding: 8px; 56 | border: 1px solid #ccc; 57 | border-radius: 4px; 58 | font-size: 14px; 59 | margin-bottom: 12px; 60 | outline: none; 61 | } 62 | 63 | .pake-modal-content button { 64 | background: #11182B; 65 | color: #FFF; 66 | padding: 6px 14px; 67 | border-radius: 4px; 68 | cursor: pointer; 69 | margin-right: 4px; 70 | font-size:14px; 71 | border: 1px solid #11182B; 72 | } 73 | 74 | #pakeUrlClose{ 75 | background: #fff; 76 | color: #11182B; 77 | } 78 | `; 79 | 80 | const modalDiv = document.createElement('div'); 81 | modalDiv.innerHTML = modalHtml; 82 | document.body.appendChild(modalDiv); 83 | 84 | const modalStyleElement = document.createElement('style'); 85 | modalStyleElement.innerText = modalStyle; 86 | document.head.appendChild(modalStyleElement); 87 | 88 | const urlModal = document.getElementById('pakeUrlModal'); 89 | const urlInput = document.getElementById('pakeUrlInput'); 90 | const urlSubmit = document.getElementById('pakeUrlSubmit'); 91 | const urlClose = document.getElementById('pakeUrlClose'); 92 | 93 | urlSubmit.onclick = function () { 94 | const url = urlInput.value; 95 | if (url) { 96 | window.location.href = url; 97 | } 98 | }; 99 | 100 | urlClose.onclick = function () { 101 | urlModal.style.display = 'none'; 102 | }; 103 | 104 | urlInput.addEventListener('keydown', function (event) { 105 | if (event.key === 'Enter') { 106 | const url = urlInput.value; 107 | if (url) { 108 | window.location.href = url; 109 | } 110 | } 111 | }); 112 | 113 | document.addEventListener('keydown', function (event) { 114 | if (event.key === 'Escape' && urlModal.style.display === 'block') { 115 | urlModal.style.display = 'none'; 116 | } 117 | }); 118 | 119 | window.showUrlModal = function () { 120 | urlModal.style.display = 'block'; 121 | urlInput.focus(); 122 | }; 123 | 124 | // Toast 125 | function pakeToast(msg) { 126 | const m = document.createElement('div'); 127 | m.innerHTML = msg; 128 | m.style.cssText = 129 | 'max-width:60%;min-width: 80px;padding:0 12px;height: 32px;color: rgb(255, 255, 255);line-height: 32px;text-align: center;border-radius: 8px;position: fixed; bottom:24px;right: 28px;z-index: 999999;background: rgba(0, 0, 0,.8);font-size: 13px;'; 130 | document.body.appendChild(m); 131 | setTimeout(function () { 132 | const d = 0.5; 133 | m.style.transition = 134 | 'transform ' + d + 's ease-in, opacity ' + d + 's ease-in'; 135 | m.style.opacity = '0'; 136 | setTimeout(function () { 137 | document.body.removeChild(m); 138 | }, d * 1000); 139 | }, 3000); 140 | } 141 | 142 | window.pakeToast = pakeToast; 143 | }); 144 | -------------------------------------------------------------------------------- /src-tauri/src/inject/event.js: -------------------------------------------------------------------------------- 1 | const shortcuts = { 2 | ArrowUp: () => scrollTo(0, 0), 3 | ArrowDown: () => scrollTo(0, document.body.scrollHeight), 4 | ArrowLeft: () => window.history.back(), 5 | ArrowRight: () => window.history.forward(), 6 | '[': () => window.history.back(), 7 | ']': () => window.history.forward(), 8 | r: () => window.location.reload(), 9 | '-': () => zoomOut(), 10 | '=': () => zoomIn(), 11 | '+': () => zoomIn(), 12 | 0: () => setZoom('100%'), 13 | }; 14 | 15 | function setZoom(zoom) { 16 | const html = document.getElementsByTagName('html')[0]; 17 | html.style.zoom = zoom; 18 | window.localStorage.setItem('htmlZoom', zoom); 19 | } 20 | 21 | function zoomCommon(zoomChange) { 22 | const currentZoom = window.localStorage.getItem('htmlZoom') || '100%'; 23 | setZoom(zoomChange(currentZoom)); 24 | } 25 | 26 | function zoomIn() { 27 | zoomCommon((currentZoom) => `${Math.min(parseInt(currentZoom) + 10, 200)}%`); 28 | } 29 | 30 | function zoomOut() { 31 | zoomCommon((currentZoom) => `${Math.max(parseInt(currentZoom) - 10, 30)}%`); 32 | } 33 | 34 | function handleShortcut(event) { 35 | if (shortcuts[event.key]) { 36 | event.preventDefault(); 37 | shortcuts[event.key](); 38 | } 39 | } 40 | 41 | //这里参考 ChatGPT 的代码 42 | const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0]; 43 | function transformCallback(callback = () => {}, once = false) { 44 | const identifier = uid(); 45 | const prop = `_${identifier}`; 46 | Object.defineProperty(window, prop, { 47 | value: (result) => { 48 | if (once) { 49 | Reflect.deleteProperty(window, prop); 50 | } 51 | return callback(result); 52 | }, 53 | writable: false, 54 | configurable: true, 55 | }); 56 | return identifier; 57 | } 58 | async function invoke(cmd, args) { 59 | return new Promise((resolve, reject) => { 60 | if (!window.__TAURI_POST_MESSAGE__) 61 | reject('__TAURI_POST_MESSAGE__ does not exist~'); 62 | const callback = transformCallback((e) => { 63 | resolve(e); 64 | Reflect.deleteProperty(window, `_${error}`); 65 | }, true); 66 | const error = transformCallback((e) => { 67 | reject(e); 68 | Reflect.deleteProperty(window, `_${callback}`); 69 | }, true); 70 | window.__TAURI_POST_MESSAGE__({ 71 | cmd, 72 | callback, 73 | error, 74 | ...args, 75 | }); 76 | }); 77 | } 78 | 79 | document.addEventListener('DOMContentLoaded', () => { 80 | const topDom = document.createElement('div'); 81 | topDom.id = 'pack-top-dom'; 82 | document.body.appendChild(topDom); 83 | const domEl = document.getElementById('pack-top-dom'); 84 | 85 | domEl.addEventListener('mousedown', (e) => { 86 | e.preventDefault(); 87 | if (e.buttons === 1 && e.detail !== 2) { 88 | invoke('drag_window'); 89 | } 90 | }); 91 | 92 | domEl.addEventListener('touchstart', () => { 93 | invoke('drag_window'); 94 | }); 95 | 96 | domEl.addEventListener('dblclick', () => { 97 | invoke('fullscreen'); 98 | }); 99 | 100 | document.addEventListener('keyup', (event) => { 101 | if (/windows|linux/i.test(navigator.userAgent) && event.ctrlKey) { 102 | handleShortcut(event); 103 | } 104 | if (/macintosh|mac os x/i.test(navigator.userAgent) && event.metaKey) { 105 | handleShortcut(event); 106 | } 107 | }); 108 | 109 | document.addEventListener('click', (e) => { 110 | const anchorElement = e.target.closest('a'); 111 | 112 | if (anchorElement && anchorElement.href) { 113 | const target = anchorElement.target; 114 | anchorElement.target = '_self'; 115 | const hrefUrl = new URL(anchorElement.href); 116 | const absoluteUrl = hrefUrl.href; 117 | 118 | // Handling external link redirection. 119 | if ( 120 | window.location.host !== hrefUrl.host && 121 | (target === '_blank' || target === '_new') 122 | ) { 123 | e.preventDefault(); 124 | invoke('open_browser', { url: absoluteUrl }); 125 | return; 126 | } 127 | 128 | // Process download links for Rust to handle. 129 | if ( 130 | /\.[a-zA-Z0-9]+$/i.test(removeUrlParameters(absoluteUrl)) && 131 | !externalDownLoadLink() 132 | ) { 133 | e.preventDefault(); 134 | invoke('download_file', { 135 | params: { 136 | url: absoluteUrl, 137 | filename: getFilenameFromUrl(absoluteUrl), 138 | }, 139 | }); 140 | } 141 | } 142 | }); 143 | 144 | // Rewrite the window.open function. 145 | const originalWindowOpen = window.open; 146 | window.open = function (url, name, specs) { 147 | // Apple login and google login 148 | if (name === 'AppleAuthentication') { 149 | //do nothing 150 | } else if (specs.includes('height=') || specs.includes('width=')) { 151 | location.href = url; 152 | } else { 153 | const baseUrl = window.location.origin + window.location.pathname; 154 | const hrefUrl = new URL(url, baseUrl); 155 | invoke('open_browser', { url: hrefUrl.href }); 156 | } 157 | // Call the original window.open function to maintain its normal functionality. 158 | return originalWindowOpen.call(window, url, name, specs); 159 | }; 160 | 161 | // Set the default zoom, There are problems with Loop without using try-catch. 162 | try { 163 | setDefaultZoom(); 164 | } catch (e) { 165 | console.log(e); 166 | } 167 | }); 168 | 169 | function setDefaultZoom() { 170 | const htmlZoom = window.localStorage.getItem('htmlZoom'); 171 | if (htmlZoom) { 172 | setZoom(htmlZoom); 173 | } 174 | } 175 | 176 | function getFilenameFromUrl(url) { 177 | const urlPath = new URL(url).pathname; 178 | const filename = urlPath.substring(urlPath.lastIndexOf('/') + 1); 179 | return filename; 180 | } 181 | 182 | function removeUrlParameters(url) { 183 | const parsedUrl = new URL(url); 184 | parsedUrl.search = ''; 185 | return parsedUrl.toString(); 186 | } 187 | 188 | // No need to go to the download link. 189 | function externalDownLoadLink() { 190 | return ['quickref.me'].indexOf(location.hostname) > -1; 191 | } 192 | 193 | // Toggle video playback when the window is hidden. 194 | function toggleVideoPlayback(pause) { 195 | const videos = document.getElementsByTagName('video'); 196 | for (const video of videos) { 197 | if (pause) { 198 | video.pause(); 199 | } else { 200 | video.play(); 201 | } 202 | } 203 | } 204 | 205 | -------------------------------------------------------------------------------- /src-tauri/src/inject/style.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('DOMContentLoaded', (_event) => { 2 | const css = ` 3 | #page #footer-wrapper, 4 | .drawing-board .toolbar .toolbar-action, 5 | .c-swiper-container, 6 | .download_entry, 7 | .lang, .copyright, 8 | .wwads-cn, .adsbygoogle, 9 | #Bottom > div.content > div.inner, 10 | #Rightbar .sep20:nth-of-type(5), 11 | #Rightbar > div.box:nth-child(4), 12 | #Main > div.box:nth-child(8) > div 13 | #Wrapper > div.sep20, 14 | #Main > div.box:nth-child(8), 15 | #masthead-ad, 16 | #app > header > div > div.menu, 17 | #root > div > div.fixed.top-0.left-0.w-64.h-screen.p-10.pb-0.flex.flex-col.justify-between > div > div.space-y-4 > a:nth-child(3), 18 | #app > div.layout > div.main-container > div.side-bar > div, 19 | #app > div.layout > div.main-container > div.side-bar > li.divider, 20 | #Rightbar > div:nth-child(6) > div.sidebar_compliance, 21 | #__next > div.PageWithSidebarLayout_centeringDiv___L9br > aside > div > div > a.ChatPageFollowTwitterLink_followLink__Gl2tt, 22 | #__next > div.PageWithSidebarLayout_centeringDiv___L9br > aside > div > div > a.Button_buttonBase__0QP_m.Button_primary__pIDjn.ChatPageDownloadLinks_downloadButton__amBRh, 23 | #__next > div.PageWithSidebarLayout_centeringDiv___L9br > aside > div > div > section a[href*="/contact"] { 24 | display: none !important; 25 | } 26 | 27 | #app > header .right .avatar.logged-in{ 28 | opacity: 0; 29 | transition: opacity 0.3s; 30 | } 31 | 32 | #app > header .right .avatar.logged-in:hover{ 33 | opacity: 1; 34 | } 35 | 36 | #page .main_header, .cb-layout-basic--navbar, 37 | #app .splitpanes.splitpanes--horizontal.no-splitter header, 38 | .fui-FluentProvider .fui-Button[data-testid="HomeButton"], 39 | #__next > div.PageWithSidebarLayout_centeringDiv___L9br > aside .ChatPageSidebar_logo__9PIXq { 40 | padding-top: 20px; 41 | } 42 | 43 | #__next .PageWithSidebarLayout_mainSection__i1yOg { 44 | width: 100%; 45 | max-width: 1000px; 46 | } 47 | 48 | #__next > div.PageWithSidebarLayout_centeringDiv___L9br > aside{ 49 | min-width: 260px; 50 | } 51 | 52 | .chakra-ui-light #app .chakra-heading, 53 | .chakra-ui-dark #app .chakra-heading, 54 | .chakra-ui-light #app .chakra-stack, 55 | .chakra-ui-dark #app .chakra-stack, 56 | .app-main .sidebar-mouse-in-out, 57 | .chakra-modal__content-container .chakra-modal__header > div > div, 58 | #__next > div.PageWithSidebarLayout_centeringDiv___L9br > section > header { 59 | padding-top: 10px; 60 | } 61 | 62 | #__next .overflow-hidden>.hidden.bg-gray-900 span.rounded-md.bg-yellow-200 { 63 | display: none; 64 | } 65 | 66 | #__next .overflow-hidden>.hidden.bg-gray-900 .scrollbar-trigger{ 67 | padding-top: 20px; 68 | } 69 | 70 | #__next .absolute .px-3.pt-2.pb-3.text-center { 71 | visibility: hidden; 72 | } 73 | 74 | #__next > div.overflow-hidden.w-full.h-full.relative.flex > div.dark.hidden.flex-shrink-0.bg-gray-900.md\\:flex.md\\:w-\\[260px\\].md\\:flex-col > div > div > nav { 75 | width: 100%; 76 | } 77 | 78 | .lark > .dashboard-sidebar, .lark > .dashboard-sidebar > .sidebar-user-info , .lark > .dashboard-sidebar .index-module_wrapper_F-Wbq{ 79 | padding-top:15px; 80 | } 81 | 82 | .lark > .main-wrapper [data-testid="aside"] { 83 | top: 15px; 84 | } 85 | 86 | .panel.give_me .nav_view { 87 | top: 164px !important; 88 | } 89 | 90 | .columns .column #header, 91 | .main > div > div.panel.give_me > div.header { 92 | padding-top: 30px; 93 | } 94 | 95 | ytd-masthead>#container.style-scope.ytd-masthead { 96 | padding-top: 12px !important; 97 | } 98 | 99 | .wrap.h1body-exist.max-container > div.menu-tocs > div.menu-btn{ 100 | top: 28px; 101 | } 102 | 103 | #Wrapper{ 104 | background-color: #F8F8F8 !important; 105 | background-image:none !important; 106 | } 107 | 108 | #Top { 109 | border-bottom: none; 110 | } 111 | 112 | .container-with-note #home, .container-with-note #switcher{ 113 | top: 30px; 114 | } 115 | 116 | .geist-page nav.dashboard_nav__PRmJv, 117 | #app > div.layout > div.header-container.showSearchBoxOrHeaderFixed > header > a { 118 | padding-top:10px; 119 | } 120 | 121 | .geist-page .submenu button{ 122 | margin-top:24px; 123 | } 124 | 125 | #react-root [data-testid="placementTracking"] article, 126 | #react-root a[href*="quick_promote_web"], 127 | #react-root [data-testid="AppTabBar_Explore_Link"], 128 | #react-root a[href*="/lists"][role="link"][aria-label], 129 | #react-root a[href*="/i/verified-orgs-signup"][role="link"][aria-label] { 130 | display: none !important; 131 | } 132 | 133 | #react-root [data-testid="DMDrawer"] { 134 | visibility: hidden !important; 135 | } 136 | 137 | #react-root [data-testid="primaryColumn"] > div > div { 138 | position: relative !important; 139 | } 140 | 141 | #react-root [data-testid="sidebarColumn"] { 142 | visibility: hidden !important; 143 | width: 0 !important; 144 | margin: 0 !important; 145 | padding: 0 !important; 146 | z-index: 1 !important; 147 | } 148 | 149 | @media only screen and (min-width: 1000px) { 150 | #react-root main[role="main"] { 151 | align-items: center !important; 152 | overflow-x: clip !important; 153 | } 154 | 155 | #react-root [data-testid="primaryColumn"] { 156 | width: 700px !important; 157 | max-width: 700px !important; 158 | margin: 0 auto !important; 159 | } 160 | #react-root [data-testid="primaryColumn"] > div > div:last-child, 161 | #react-root [data-testid="primaryColumn"] > div > div:last-child div { 162 | max-width: unset !important; 163 | } 164 | 165 | #react-root div[aria-label][role="group"][id^="id__"] { 166 | margin-right: 81px !important; 167 | } 168 | 169 | #react-root header[role="banner"] { 170 | position: fixed !important; 171 | left: 0 !important; 172 | } 173 | 174 | #react-root header[role="banner"] > div > div > div { 175 | justify-content: center !important; 176 | padding-top: 0; 177 | overflow-x: hidden; 178 | } 179 | 180 | #react-root form[role="search"] > div:nth-child(1) > div { 181 | background-color: transparent !important; 182 | } 183 | 184 | #react-root h1[role="heading"] { 185 | padding-top: 4px !important; 186 | } 187 | 188 | #react-root header[role="banner"] 189 | nav[role="navigation"] 190 | * 191 | div[dir="auto"]:not([aria-label]) 192 | > span, 193 | #react-root [data-testid="SideNav_AccountSwitcher_Button"] > div:not(:first-child) { 194 | display: inline-block !important; 195 | opacity: 0 !important; 196 | transition: 0.5s cubic-bezier(0.2, 0.8, 0.2, 1); 197 | } 198 | #react-root header[role="banner"] 199 | nav[role="navigation"]:hover 200 | * 201 | div[dir="auto"]:not([aria-label]) 202 | > span, 203 | #react-root [data-testid="SideNav_AccountSwitcher_Button"]:hover > div:not(:first-child) { 204 | opacity: 1 !important; 205 | } 206 | #react-root header[role="banner"] nav[role="navigation"]:hover > * > div { 207 | backdrop-filter: blur(12px) !important; 208 | } 209 | #react-root header[role="banner"] nav[role="navigation"] > a { 210 | position: relative; 211 | } 212 | 213 | #react-root header[role="banner"] nav[role="navigation"] > a::before { 214 | content: ""; 215 | position: absolute; 216 | top: 0px; 217 | right: -40px; 218 | bottom: 0px; 219 | left: 0px; 220 | } 221 | #react-root [data-testid="SideNav_AccountSwitcher_Button"] { 222 | bottom: 18px !important; 223 | left: 1px !important; 224 | } 225 | 226 | #react-root [data-testid="SideNav_NewTweet_Button"], #react-root [aria-label="Twitter Blue"]{ 227 | display: none; 228 | } 229 | } 230 | 231 | @media only screen and (min-width: 1265px) { 232 | #react-root [data-testid="sidebarColumn"] form[role="search"] { 233 | visibility: visible !important; 234 | position: fixed !important; 235 | top: 12px !important; 236 | right: 16px !important; 237 | } 238 | 239 | #react-root [data-testid="sidebarColumn"] input[placeholder="Search Twitter"] { 240 | width: 150px; 241 | } 242 | 243 | #react-root [data-testid="sidebarColumn"] form[role="search"]:focus-within { 244 | width: 374px !important; 245 | backdrop-filter: blur(12px) !important; 246 | } 247 | 248 | #react-root [data-testid="sidebarColumn"] input[placeholder="Search Twitter"]:focus { 249 | width: 328px !important; 250 | } 251 | 252 | #react-root div[style*="left: -12px"] { 253 | left: unset !important; 254 | } 255 | 256 | #react-root div[style="left: -8px; width: 306px;"] { 257 | left: unset !important; 258 | width: 374px !important; 259 | } 260 | 261 | #react-root .searchFilters { 262 | visibility: visible !important; 263 | position: fixed; 264 | top: 12px; 265 | right: 16px; 266 | width: 240px; 267 | } 268 | #react-root .searchFilters > div > div:first-child { 269 | display: none; 270 | } 271 | } 272 | 273 | @media (min-width:1024px){ 274 | #__next .text-base.lg\\:max-w-xl, #__next form.stretch.lg\\:max-w-2xl { 275 | max-width: 44rem; 276 | } 277 | } 278 | 279 | @media (min-width:1280px){ 280 | #__next .text-base.xl\\:max-w-3xl, #__next form.stretch.xl\\:max-w-3xl { 281 | max-width: 48rem; 282 | } 283 | } 284 | 285 | @media (min-width:640px){ 286 | #__next .sticky.top-0{ 287 | padding-top: 15px; 288 | } 289 | } 290 | 291 | #__next .prose ol li p { 292 | margin: 0; 293 | display: inline; 294 | } 295 | 296 | #pack-top-dom:active { 297 | cursor: grabbing; 298 | cursor: -webkit-grabbing; 299 | } 300 | 301 | #pack-top-dom{ 302 | position:fixed; 303 | background:transparent; 304 | top:0; 305 | width: 100%; 306 | height: 20px; 307 | cursor: grab; 308 | -webkit-app-region: drag; 309 | user-select: none; 310 | -webkit-user-select: none; 311 | z-index: 90000; 312 | } 313 | `; 314 | const styleElement = document.createElement('style'); 315 | styleElement.innerHTML = css; 316 | document.head.appendChild(styleElement); 317 | }); 318 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | mod app; 7 | mod util; 8 | 9 | use app::{invoke, menu, window}; 10 | use invoke::{download_file, drag_window, fullscreen, open_browser}; 11 | use menu::{get_menu, menu_event_handle}; 12 | use tauri_plugin_window_state::Builder as windowStatePlugin; 13 | use util::{get_data_dir, get_pake_config}; 14 | use window::get_window; 15 | 16 | pub fn run_app() { 17 | let (pake_config, tauri_config) = get_pake_config(); 18 | let show_menu = pake_config.show_menu(); 19 | let menu = get_menu(); 20 | let data_dir = get_data_dir(tauri_config); 21 | 22 | let mut tauri_app = tauri::Builder::default(); 23 | 24 | if show_menu { 25 | tauri_app = tauri_app.menu(menu).on_menu_event(menu_event_handle); 26 | } 27 | 28 | #[cfg(not(target_os = "macos"))] 29 | { 30 | use menu::{get_system_tray, system_tray_handle}; 31 | 32 | let show_system_tray = pake_config.show_system_tray(); 33 | let system_tray = get_system_tray(show_menu); 34 | 35 | if show_system_tray { 36 | tauri_app = tauri_app 37 | .system_tray(system_tray) 38 | .on_system_tray_event(system_tray_handle); 39 | } 40 | } 41 | 42 | tauri_app 43 | .plugin(windowStatePlugin::default().build()) 44 | .invoke_handler(tauri::generate_handler![ 45 | drag_window, 46 | fullscreen, 47 | open_browser, 48 | download_file 49 | ]) 50 | .setup(|app| { 51 | let _window = get_window(app, pake_config, data_dir); 52 | // Prevent initial shaking 53 | _window.show().unwrap(); 54 | Ok(()) 55 | }) 56 | .on_window_event(|event| { 57 | if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() { 58 | #[cfg(target_os = "macos")] 59 | { 60 | event.window().minimize().unwrap(); 61 | event.window().eval("toggleVideoPlayback(true);").unwrap(); 62 | } 63 | 64 | #[cfg(not(target_os = "macos"))] 65 | event.window().close().unwrap(); 66 | 67 | api.prevent_close(); 68 | } 69 | }) 70 | .run(tauri::generate_context!()) 71 | .expect("error while running tauri application"); 72 | } 73 | 74 | fn main() { 75 | run_app() 76 | } 77 | -------------------------------------------------------------------------------- /src-tauri/src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::app::config::PakeConfig; 2 | use std::env; 3 | use std::path::PathBuf; 4 | use tauri::{api, Config, Window}; 5 | 6 | pub fn get_pake_config() -> (PakeConfig, Config) { 7 | let pake_config: PakeConfig = 8 | serde_json::from_str(include_str!("../pake.json")).expect("Failed to parse pake config"); 9 | 10 | let tauri_config: Config = serde_json::from_str(include_str!("../tauri.conf.json")) 11 | .expect("Failed to parse tauri config"); 12 | 13 | (pake_config, tauri_config) 14 | } 15 | 16 | pub fn get_data_dir(_tauri_config: Config) -> PathBuf { 17 | { 18 | let package_name = _tauri_config.package.product_name.unwrap(); 19 | let data_dir = api::path::config_dir() 20 | .expect("Failed to get data dirname") 21 | .join(package_name); 22 | 23 | if !data_dir.exists() { 24 | std::fs::create_dir(&data_dir) 25 | .unwrap_or_else(|_| panic!("Can't create dir {}", data_dir.display())); 26 | } 27 | data_dir 28 | } 29 | } 30 | 31 | pub fn show_toast(window: &Window, message: &str) { 32 | let script = format!(r#"pakeToast("{}");"#, message); 33 | window.eval(&script).unwrap(); 34 | } 35 | 36 | pub fn get_download_message() -> String { 37 | let default_message = "Download successful, saved to download directory~"; 38 | let chinese_message = "下载成功,已保存到下载目录~"; 39 | 40 | env::var("LANG") 41 | .map(|lang| { 42 | if lang.starts_with("zh") { 43 | chinese_message 44 | } else { 45 | default_message 46 | } 47 | }) 48 | .unwrap_or(default_message) 49 | .to_string() 50 | } 51 | 52 | // Check if the file exists, if it exists, add a number to file name 53 | pub fn check_file_or_append(file_path: &str) -> String { 54 | let mut new_path = PathBuf::from(file_path); 55 | let mut counter = 0; 56 | 57 | while new_path.exists() { 58 | let file_stem = new_path.file_stem().unwrap().to_string_lossy().to_string(); 59 | let extension = new_path.extension().unwrap().to_string_lossy().to_string(); 60 | let parent_dir = new_path.parent().unwrap(); 61 | 62 | let new_file_stem = match file_stem.rfind('-') { 63 | Some(index) if file_stem[index + 1..].parse::().is_ok() => { 64 | let base_name = &file_stem[..index]; 65 | counter = file_stem[index + 1..].parse::().unwrap() + 1; 66 | format!("{}-{}", base_name, counter) 67 | } 68 | _ => { 69 | counter += 1; 70 | format!("{}-{}", file_stem, counter) 71 | } 72 | }; 73 | 74 | new_path = parent_dir.join(format!("{}.{}", new_file_stem, extension)); 75 | } 76 | 77 | new_path.to_string_lossy().into_owned() 78 | } 79 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "productName": "WeRead", 4 | "version": "1.0.0" 5 | }, 6 | "tauri": { 7 | "security": { 8 | "csp": null 9 | }, 10 | "updater": { 11 | "active": false 12 | }, 13 | "systemTray": { 14 | "iconPath": "png/weread_512.png", 15 | "iconAsTemplate": true 16 | }, 17 | "allowlist": { 18 | "all": true 19 | } 20 | }, 21 | "build": { 22 | "withGlobalTauri": true, 23 | "devPath": "../dist", 24 | "distDir": "../dist", 25 | "beforeBuildCommand": "", 26 | "beforeDevCommand": "" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src-tauri/tauri.linux.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "bundle": { 4 | "icon": ["png/weread_512.png"], 5 | "identifier": "com.tw93.weread", 6 | "active": true, 7 | "category": "DeveloperTool", 8 | "copyright": "", 9 | "deb": { 10 | "depends": ["curl", "wget"], 11 | "files": {"/usr/share/applications/com-tw93-weread.desktop": "assets/com-tw93-weread.desktop"} 12 | }, 13 | "externalBin": [], 14 | "longDescription": "", 15 | "resources": [], 16 | "shortDescription": "", 17 | "targets": ["deb", "appimage"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src-tauri/tauri.macos.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "bundle": { 4 | "icon": ["icons/weread.icns"], 5 | "identifier": "com.tw93.weread", 6 | "active": true, 7 | "category": "DeveloperTool", 8 | "copyright": "", 9 | "externalBin": [], 10 | "longDescription": "", 11 | "macOS": { 12 | "entitlements": null, 13 | "exceptionDomain": "", 14 | "frameworks": [], 15 | "providerShortName": null, 16 | "signingIdentity": null 17 | }, 18 | "resources": [], 19 | "shortDescription": "", 20 | "targets": ["dmg"] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src-tauri/tauri.windows.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "bundle": { 4 | "icon": ["png/weread_256.ico", "png/weread_32.ico"], 5 | "identifier": "com.tw93.weread", 6 | "active": true, 7 | "category": "DeveloperTool", 8 | "copyright": "", 9 | "externalBin": [], 10 | "longDescription": "", 11 | "resources": ["png/weread_32.ico"], 12 | "shortDescription": "", 13 | "targets": ["msi"], 14 | "windows": { 15 | "certificateThumbprint": null, 16 | "digestAlgorithm": "sha256", 17 | "timestampUrl": "", 18 | "wix": { 19 | "language": ["en-US"], 20 | "template": "assets/main.wxs" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "esModuleInterop": true, 5 | "allowSyntheticDefaultImports": true, 6 | "target": "es6", 7 | "noImplicitAny": true, 8 | "moduleResolution": "Node16", 9 | "sourceMap": true, 10 | "outDir": "dist", 11 | "baseUrl": ".", 12 | "paths": { 13 | "@/*": ["bin/*"] 14 | } 15 | }, 16 | "include": ["bin/**/*"] 17 | } 18 | --------------------------------------------------------------------------------