├── dist └── .gitkeep ├── cli.js ├── src-tauri ├── build.rs ├── .gitignore ├── icons │ ├── code.icns │ ├── flomo.icns │ ├── icon.icns │ ├── lizhi.icns │ ├── tool.icns │ ├── yuque.icns │ ├── chatgpt.icns │ ├── lenster.icns │ ├── qwerty.icns │ ├── twitter.icns │ ├── weread.icns │ ├── youtube.icns │ ├── reference.icns │ ├── witeboard.icns │ └── zlibrary.icns ├── png │ ├── code_256.ico │ ├── code_32.ico │ ├── code_512.png │ ├── flomo_32.ico │ ├── icon_256.ico │ ├── icon_32.ico │ ├── icon_512.png │ ├── lizhi_32.ico │ ├── yuque_32.ico │ ├── chatgpt_32.ico │ ├── flomo_256.ico │ ├── flomo_512.png │ ├── lenster_32.ico │ ├── lizhi_256.ico │ ├── lizhi_512.png │ ├── qwerty_256.ico │ ├── qwerty_32.ico │ ├── qwerty_512.png │ ├── twitter_32.ico │ ├── weread_256.ico │ ├── weread_32.ico │ ├── weread_512.png │ ├── youtube_32.ico │ ├── yuque_256.ico │ ├── yuque_512.png │ ├── chatgpt_256.ico │ ├── chatgpt_512.png │ ├── lenster_256.ico │ ├── lenster_512.png │ ├── reference_256.ico │ ├── reference_32.ico │ ├── reference_512.png │ ├── twitter_256.ico │ ├── twitter_512.png │ ├── youtube_256.ico │ ├── youtube_512.png │ ├── zlibrary_256.ico │ ├── zlibrary_32.ico │ └── zlibrary_512.png ├── assets │ ├── com-tw93-weread.desktop │ └── main.wxs ├── tauri.macos.conf.json ├── tauri.conf.json ├── tauri.windows.conf.json ├── tauri.linux.conf.json ├── Cargo.toml └── src │ ├── main.rs │ └── pake.js ├── .ecrc.json ├── .prettierignore ├── script ├── sd.exe ├── sd-apple-x64 ├── sd-linux-x64 ├── build.bat └── build.sh ├── .github ├── FUNDING.yml ├── workflows │ ├── contribute_list.yml │ ├── editorconfig-check.yml │ ├── pake_build.yaml │ ├── rust-code-quality-check.yml │ └── pake_build_with_cache.yaml └── ISSUE_TEMPLATE │ └── bug.md ├── app.csv ├── bin ├── utils │ ├── platform.ts │ ├── dir.ts │ ├── shell.ts │ ├── validate.ts │ ├── url.ts │ └── tlds.ts ├── helpers │ ├── updater.ts │ ├── tauriConfig.ts │ └── rust.ts ├── defaults.ts ├── builders │ ├── base.ts │ ├── BuilderFactory.ts │ ├── tauriConf.js │ ├── MacBuilder.ts │ ├── WinBulider.ts │ ├── LinuxBuilder.ts │ └── common.ts ├── options │ ├── logger.ts │ ├── index.ts │ └── icon.ts ├── types.ts ├── README.md ├── cli.ts └── README_EN.md ├── .gitattributes ├── tsconfig.json ├── .gitignore ├── .editorconfig ├── rollup.config.js ├── CONTRIBUTING.md ├── LICENSE ├── icns2png.py ├── package.json ├── CODE_OF_CONDUCT.md ├── README_EN.md └── README.md /dist/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import './dist/cli.js'; 3 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /.ecrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "Exclude": ["Cargo\\.lock$", "dist", "*\\.md"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | dist/**/* 4 | !dist/twitter.css 5 | -------------------------------------------------------------------------------- /script/sd.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/script/sd.exe -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["tw93"] 2 | custom: ["https://miaoyan.app/cats.html?name=Pake"] 3 | -------------------------------------------------------------------------------- /script/sd-apple-x64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/script/sd-apple-x64 -------------------------------------------------------------------------------- /script/sd-linux-x64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/script/sd-linux-x64 -------------------------------------------------------------------------------- /app.csv: -------------------------------------------------------------------------------- 1 | name(Linux),name(Mac/Windows),name_zh,url 2 | lenster,Lenster,lenster,https://lenster.xyz/ 3 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/icons/code.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/code.icns -------------------------------------------------------------------------------- /src-tauri/icons/flomo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/flomo.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/lizhi.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/lizhi.icns -------------------------------------------------------------------------------- /src-tauri/icons/tool.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/tool.icns -------------------------------------------------------------------------------- /src-tauri/icons/yuque.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/yuque.icns -------------------------------------------------------------------------------- /src-tauri/png/code_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/code_256.ico -------------------------------------------------------------------------------- /src-tauri/png/code_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/code_32.ico -------------------------------------------------------------------------------- /src-tauri/png/code_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/code_512.png -------------------------------------------------------------------------------- /src-tauri/png/flomo_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/flomo_32.ico -------------------------------------------------------------------------------- /src-tauri/png/icon_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/icon_256.ico -------------------------------------------------------------------------------- /src-tauri/png/icon_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/icon_32.ico -------------------------------------------------------------------------------- /src-tauri/png/icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/icon_512.png -------------------------------------------------------------------------------- /src-tauri/png/lizhi_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/lizhi_32.ico -------------------------------------------------------------------------------- /src-tauri/png/yuque_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/yuque_32.ico -------------------------------------------------------------------------------- /src-tauri/icons/chatgpt.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/chatgpt.icns -------------------------------------------------------------------------------- /src-tauri/icons/lenster.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/lenster.icns -------------------------------------------------------------------------------- /src-tauri/icons/qwerty.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/qwerty.icns -------------------------------------------------------------------------------- /src-tauri/icons/twitter.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/twitter.icns -------------------------------------------------------------------------------- /src-tauri/icons/weread.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/weread.icns -------------------------------------------------------------------------------- /src-tauri/icons/youtube.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/youtube.icns -------------------------------------------------------------------------------- /src-tauri/png/chatgpt_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/chatgpt_32.ico -------------------------------------------------------------------------------- /src-tauri/png/flomo_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/flomo_256.ico -------------------------------------------------------------------------------- /src-tauri/png/flomo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/flomo_512.png -------------------------------------------------------------------------------- /src-tauri/png/lenster_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/lenster_32.ico -------------------------------------------------------------------------------- /src-tauri/png/lizhi_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/lizhi_256.ico -------------------------------------------------------------------------------- /src-tauri/png/lizhi_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/lizhi_512.png -------------------------------------------------------------------------------- /src-tauri/png/qwerty_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/qwerty_256.ico -------------------------------------------------------------------------------- /src-tauri/png/qwerty_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/qwerty_32.ico -------------------------------------------------------------------------------- /src-tauri/png/qwerty_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/qwerty_512.png -------------------------------------------------------------------------------- /src-tauri/png/twitter_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/twitter_32.ico -------------------------------------------------------------------------------- /src-tauri/png/weread_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/weread_256.ico -------------------------------------------------------------------------------- /src-tauri/png/weread_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/weread_32.ico -------------------------------------------------------------------------------- /src-tauri/png/weread_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/weread_512.png -------------------------------------------------------------------------------- /src-tauri/png/youtube_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/youtube_32.ico -------------------------------------------------------------------------------- /src-tauri/png/yuque_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/yuque_256.ico -------------------------------------------------------------------------------- /src-tauri/png/yuque_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/yuque_512.png -------------------------------------------------------------------------------- /src-tauri/icons/reference.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/reference.icns -------------------------------------------------------------------------------- /src-tauri/icons/witeboard.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/witeboard.icns -------------------------------------------------------------------------------- /src-tauri/icons/zlibrary.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/icons/zlibrary.icns -------------------------------------------------------------------------------- /src-tauri/png/chatgpt_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/chatgpt_256.ico -------------------------------------------------------------------------------- /src-tauri/png/chatgpt_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/chatgpt_512.png -------------------------------------------------------------------------------- /src-tauri/png/lenster_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/lenster_256.ico -------------------------------------------------------------------------------- /src-tauri/png/lenster_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/lenster_512.png -------------------------------------------------------------------------------- /src-tauri/png/reference_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/reference_256.ico -------------------------------------------------------------------------------- /src-tauri/png/reference_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/reference_32.ico -------------------------------------------------------------------------------- /src-tauri/png/reference_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/reference_512.png -------------------------------------------------------------------------------- /src-tauri/png/twitter_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/twitter_256.ico -------------------------------------------------------------------------------- /src-tauri/png/twitter_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/twitter_512.png -------------------------------------------------------------------------------- /src-tauri/png/youtube_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/youtube_256.ico -------------------------------------------------------------------------------- /src-tauri/png/youtube_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/youtube_512.png -------------------------------------------------------------------------------- /src-tauri/png/zlibrary_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/zlibrary_256.ico -------------------------------------------------------------------------------- /src-tauri/png/zlibrary_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/zlibrary_32.ico -------------------------------------------------------------------------------- /src-tauri/png/zlibrary_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoleno/lenster-pake/HEAD/src-tauri/png/zlibrary_512.png -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 11 | -------------------------------------------------------------------------------- /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/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 | debug: false, 11 | }; 12 | 13 | export const DEFAULT_APP_NAME = 'Pake'; 14 | -------------------------------------------------------------------------------- /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/utils/shell.ts: -------------------------------------------------------------------------------- 1 | import shelljs from "shelljs"; 2 | 3 | export function shellExec(command: string) { 4 | return new Promise((resolve, reject) => { 5 | shelljs.exec(command, { async: true, silent: false }, (code) => { 6 | if (code === 0) { 7 | resolve(0); 8 | } else { 9 | reject(new Error(`${code}`)); 10 | } 11 | }); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | bin/**/* linguist-vendored 2 | bin/* linguist-vendored 3 | dist/* linguist-vendored 4 | /cli.js linguist-vendored 5 | /rollup.config.js linguist-vendored 6 | .github/**/* linguist-vendored 7 | .github/* linguist-vendored 8 | script/* linguist-vendored 9 | /icns2png.py linguist-vendored 10 | src-tauri/src/pake.js linguist-vendored 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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/cli.js 35 | !dist/.gitkeep -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /bin/utils/validate.ts: -------------------------------------------------------------------------------- 1 | import * as Commander from 'commander'; 2 | import { normalizeUrl } from './url.js'; 3 | 4 | export function validateNumberInput(value: string) { 5 | const parsedValue = Number(value); 6 | if (isNaN(parsedValue)) { 7 | throw new Commander.InvalidArgumentError('Not a number.'); 8 | } 9 | return parsedValue; 10 | } 11 | 12 | export function validateUrlInput(url: string) { 13 | try { 14 | return normalizeUrl(url); 15 | } catch (error) { 16 | throw new Commander.InvalidArgumentError(error.message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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 | /** 调试模式,会输出更多日志 */ 24 | debug: boolean; 25 | } 26 | 27 | export interface PakeAppOptions extends PakeCliOptions { 28 | identifier: string; 29 | } 30 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src-tauri/tauri.macos.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "bundle": { 4 | "icon": [ 5 | "icons/weread.icns" 6 | ], 7 | "identifier": "com.tw93.weread", 8 | "active": true, 9 | "category": "DeveloperTool", 10 | "copyright": "", 11 | "externalBin": [], 12 | "longDescription": "", 13 | "macOS": { 14 | "entitlements": null, 15 | "exceptionDomain": "", 16 | "frameworks": [], 17 | "providerShortName": null, 18 | "signingIdentity": null 19 | }, 20 | "resources": [], 21 | "shortDescription": "", 22 | "targets": ["dmg"] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "productName": "WeRead", 4 | "version": "1.0.0" 5 | }, 6 | "tauri": { 7 | "windows": [ 8 | { 9 | "url": "https://weread.qq.com/", 10 | "transparent": true, 11 | "fullscreen": false, 12 | "width": 1200, 13 | "height": 780, 14 | "resizable": true 15 | } 16 | ], 17 | "security": { 18 | "csp": null 19 | }, 20 | "updater": { 21 | "active": false 22 | } 23 | }, 24 | "build": { 25 | "devPath": "../dist", 26 | "distDir": "../dist", 27 | "beforeBuildCommand": "", 28 | "beforeDevCommand": "" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: 讨论建议咨询请去讨论区 / Please go to discussion for suggestions. 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | 14 | -------------------------------------------------------------------------------- /bin/builders/tauriConf.js: -------------------------------------------------------------------------------- 1 | import CommonConf from '../../src-tauri/tauri.conf.json'; 2 | import WinConf from '../../src-tauri/tauri.windows.conf.json'; 3 | import MacConf from '../../src-tauri/tauri.macos.conf.json'; 4 | import LinuxConf from '../../src-tauri/tauri.linux.conf.json'; 5 | 6 | let tauriConf = { 7 | package: CommonConf.package, 8 | tauri: CommonConf.tauri 9 | } 10 | switch (process.platform) { 11 | case "win32": { 12 | tauriConf.tauri.bundle = WinConf.tauri.bundle; 13 | break; 14 | } 15 | case "darwin": { 16 | tauriConf.tauri.bundle = MacConf.tauri.bundle; 17 | break; 18 | } 19 | case "linux": { 20 | tauriConf.tauri.bundle = LinuxConf.tauri.bundle; 21 | break; 22 | } 23 | } 24 | 25 | export default tauriConf; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src-tauri/tauri.windows.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "bundle": { 4 | "icon": [ 5 | "png/weread_256.ico", 6 | "png/weread_32.ico" 7 | ], 8 | "identifier": "com.tw93.weread", 9 | "active": true, 10 | "category": "DeveloperTool", 11 | "copyright": "", 12 | "externalBin": [], 13 | "longDescription": "", 14 | "resources": ["png/weread_32.ico"], 15 | "shortDescription": "", 16 | "targets": ["msi"], 17 | "windows": { 18 | "certificateThumbprint": null, 19 | "digestAlgorithm": "sha256", 20 | "timestampUrl": "", 21 | "wix": { 22 | "language": ["en-US"], 23 | "template": "assets/main.wxs" 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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 | 7 | export default async function handleOptions(options: PakeCliOptions, url: string): Promise { 8 | const appOptions: PakeAppOptions = { 9 | ...options, 10 | identifier: '', 11 | }; 12 | 13 | if (!appOptions.name) { 14 | appOptions.name = await promptText('please input your application name', getDomain(url)); 15 | } 16 | 17 | appOptions.identifier = getIdentifier(appOptions.name, url); 18 | 19 | appOptions.icon = await handleIcon(appOptions, url); 20 | 21 | return appOptions; 22 | } 23 | -------------------------------------------------------------------------------- /bin/helpers/rust.ts: -------------------------------------------------------------------------------- 1 | import { IS_WIN } from '@/utils/platform.js'; 2 | import ora from 'ora'; 3 | import shelljs from 'shelljs'; 4 | import { shellExec } from '../utils/shell.js'; 5 | 6 | const RustInstallScriptFocMac = 7 | "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; 8 | const RustInstallScriptForWin = 'winget install --id Rustlang.Rustup'; 9 | 10 | export async function installRust() { 11 | const spinner = ora('Downloading Rust').start(); 12 | try { 13 | await shellExec(IS_WIN ? RustInstallScriptForWin : RustInstallScriptFocMac); 14 | spinner.succeed(); 15 | } catch (error) { 16 | console.error('install rust return code', error.message); 17 | spinner.fail(); 18 | 19 | process.exit(1); 20 | } 21 | } 22 | 23 | export function checkRustInstalled() { 24 | return shelljs.exec('rustc --version', { silent: true }).code === 0; 25 | } 26 | -------------------------------------------------------------------------------- /src-tauri/tauri.linux.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "bundle": { 4 | "icon": [ 5 | "png/weread_256.ico", 6 | "png/weread_512.png" 7 | ], 8 | "identifier": "com.tw93.weread", 9 | "active": true, 10 | "category": "DeveloperTool", 11 | "copyright": "", 12 | "deb": { 13 | "depends": [ 14 | "libwebkit2gtk-4.0-dev", 15 | "build-essential", 16 | "curl", 17 | "wget", 18 | "libssl-dev", 19 | "libgtk-3-dev", 20 | "libayatana-appindicator3-dev", 21 | "librsvg2-dev", 22 | "gnome-video-effects", 23 | "gnome-video-effects-extra" 24 | ], 25 | "files": { 26 | "/usr/share/applications/com-tw93-weread.desktop": "assets/com-tw93-weread.desktop" 27 | } 28 | }, 29 | "externalBin": [], 30 | "longDescription": "", 31 | "resources": [], 32 | "shortDescription": "", 33 | "targets": ["deb", "appimage"] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | description = "Pake 打包工具" 5 | authors = ["Tw93"] 6 | license = "" 7 | repository = "" 8 | default-run = "app" 9 | edition = "2021" 10 | rust-version = "1.61.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.2", features = [] } 21 | image = "0.24.5" 22 | home = "0.5" 23 | tauri-utils = "1.2.1" 24 | webbrowser = "0.8.2" 25 | wry = "0.23.4" 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 | # Enable DevTools for debugging. 35 | devtools = [] 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 downloadIcon(iconUrl: string) { 37 | let iconResponse; 38 | try { 39 | iconResponse = await axios.get(iconUrl, { 40 | responseType: 'arraybuffer', 41 | }); 42 | } catch (error) { 43 | if (error.response && error.response.status === 404) { 44 | return null; 45 | } 46 | throw error; 47 | } 48 | 49 | const iconData = await iconResponse.data; 50 | if (!iconData) { 51 | return null; 52 | } 53 | const fileDetails = await fileTypeFromBuffer(iconData); 54 | if (!fileDetails) { 55 | return null; 56 | } 57 | const { path } = await dir(); 58 | const iconPath = `${path}/icon.${fileDetails.ext}`; 59 | await fs.writeFile(iconPath, iconData); 60 | return iconPath; 61 | } 62 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | ## 安装 2 | 3 | ```bash 4 | npm install -g pake-cli 5 | ``` 6 | 7 | 如果安装失败提示没有权限,请参考该贴解决:[链接](https://gist.github.com/Giancarlos/d087f8a9e6516716da98ad0c0f5a8f58)。 8 | 9 | 此外,请确保你使用的是正确的 Node.js 版本(`^14.13 || >=16.0.0`)。如果你在使用 [nvm](https://github.com/nvm-sh/nvm) 进行 Node.js 版本管理,可以尝试在项目的目录下运行 `nvm use`,就会拿到正确的版本;其他一众 Node.js 版本工具,比如 [fnm](https://github.com/Schniz/fnm)、[tj/n](https://github.com/tj/n) 应该也有类似的功能。 10 | 11 | **尽量不要使用 `sudo` 权限**。 如果实在要用 sudo,请手动安装 rust 到系统环境。Mac 可以用 brew 命令安装,Linux 如 Ubuntu 可以用 apt 命令安装,此外 Ubuntu 在开始之前可以运行如下命令,按照前期所需依赖。 12 | 13 | ```bash 14 | sudo apt install libdbus-1-dev libgtk-3-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev libwebkit2gtk-4.0-dev 15 | ``` 16 | 17 | ## 用法 18 | 19 | ```bash 20 | pake url [options] 21 | ``` 22 | 23 | 打包完成后的应用程序默认为当前工作目录,首次打包由于需配置好环境,需要一些时间,请耐心等待即可。 24 | 25 | Note: 打包需要用 `Rust` 环境,如果没有 `Rust`,会提示确认安装。如遇安装失败或超时,可[自行安装](https://www.rust-lang.org/tools/install)。 26 | 27 | ### url 28 | 29 | url 为你需要打包的网页链接 🔗,必须提供。 30 | 31 | ### [options] 32 | 33 | 提供了一些特定的选项,打包时可以传递对应参数达到定制化的效果。 34 | 35 | #### [name] 36 | 37 | 应用名称,如输入时未指定,会提示你输入,尽量使用英语。 38 | 39 | ```shell 40 | --name 41 | ``` 42 | 43 | #### [icon] 44 | 45 | 应用 icon,支持本地/远程文件,默认为 Pake 自带图标,定制的可以去 [icon-icons](https://icon-icons.com) 或 [macOSicons](https://macosicons.com/#/) 搜索下载。 46 | 47 | - MacOS 下必须为 `.icns` 48 | - Windows 下必须为 `.ico` 49 | - Linux 下必须为 `.png` 50 | 51 | ```shell 52 | --icon 53 | ``` 54 | 55 | #### [height] 56 | 57 | 打包后的应用窗口高度,默认 `780px`。 58 | 59 | ```shell 60 | --height 61 | ``` 62 | 63 | #### [width] 64 | 65 | 打包后的应用窗口宽度,默认 `1200px`。 66 | 67 | ```shell 68 | --width 69 | ``` 70 | 71 | #### [transparent] 72 | 73 | 是否开启沉浸式头部,默认为 `false` 不开启。 74 | 75 | ```shell 76 | --transparent 77 | ``` 78 | 79 | #### [resize] 80 | 81 | 是否可以拖动大小,默认为 `true` 可拖动。 82 | 83 | ```shell 84 | --no-resizable 85 | ``` 86 | 87 | #### [fullscreen] 88 | 89 | 打开应用后是否开启全屏,默认为 `false`。 90 | 91 | ```shell 92 | --fullscreen 93 | ``` 94 | -------------------------------------------------------------------------------- /bin/cli.ts: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import log from 'loglevel'; 3 | import { DEFAULT_PAKE_OPTIONS } from './defaults.js'; 4 | import { PakeCliOptions } from './types.js'; 5 | import { validateNumberInput, validateUrlInput } from './utils/validate.js'; 6 | import handleInputOptions from './options/index.js'; 7 | import BuilderFactory from './builders/BuilderFactory.js'; 8 | import { checkUpdateTips } from './helpers/updater.js'; 9 | // @ts-expect-error 10 | import packageJson from '../package.json'; 11 | 12 | program.version(packageJson.version).description('A cli application can package a web page to desktop application.'); 13 | 14 | program 15 | .showHelpAfterError() 16 | .argument('[url]', 'the web url you want to package', validateUrlInput) 17 | .option('--name ', 'application name') 18 | .option('--icon ', 'application icon', DEFAULT_PAKE_OPTIONS.icon) 19 | .option('--height ', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height) 20 | .option('--width ', 'window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width) 21 | .option('--no-resizable', 'whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable) 22 | .option('--fullscreen', 'makes the packaged app start in full screen', DEFAULT_PAKE_OPTIONS.fullscreen) 23 | .option('--transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent) 24 | .option('--debug', 'debug', DEFAULT_PAKE_OPTIONS.transparent) 25 | .action(async (url: string, options: PakeCliOptions) => { 26 | 27 | await checkUpdateTips(); 28 | 29 | if (!url) { 30 | // 直接 pake 不需要出现url提示 31 | program.help(); 32 | } 33 | 34 | log.setDefaultLevel('info'); 35 | if (options.debug) { 36 | log.setLevel('debug'); 37 | } 38 | 39 | const builder = BuilderFactory.create(); 40 | await builder.prepare(); 41 | 42 | const appOptions = await handleInputOptions(options, url); 43 | 44 | await builder.build(url, appOptions); 45 | }); 46 | 47 | program.parse(); 48 | -------------------------------------------------------------------------------- /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 logger from '@/options/logger.js'; 15 | 16 | export default class MacBuilder implements IBuilder { 17 | async prepare() { 18 | if (checkRustInstalled()) { 19 | return; 20 | } 21 | 22 | const res = await prompts({ 23 | type: 'confirm', 24 | message: 'We detected that you have not installed Rust. Install it now?', 25 | name: 'value', 26 | }); 27 | 28 | if (res.value) { 29 | // TODO 国内有可能会超时 30 | await installRust(); 31 | } else { 32 | log.error('Error: Pake need Rust to package your webapp!'); 33 | process.exit(2); 34 | } 35 | } 36 | 37 | async build(url: string, options: PakeAppOptions) { 38 | log.debug('PakeAppOptions', options); 39 | const { name } = options; 40 | 41 | await mergeTauriConfig(url, options, tauriConf); 42 | 43 | //这里直接使用 universal-apple-darwin 的打包,而非当前系统的包 44 | await shellExec(`cd ${npmDirectory} && npm install && npm run build:mac`); 45 | 46 | const dmgName = `${name}_${tauriConf.package.version}_universal.dmg`; 47 | const appPath = this.getBuildAppPath(npmDirectory, dmgName); 48 | const distPath = path.resolve(`${name}.dmg`); 49 | await fs.copyFile(appPath, distPath); 50 | await fs.unlink(appPath); 51 | 52 | logger.success('Build success!'); 53 | logger.success('You can find the app installer in', distPath); 54 | } 55 | 56 | getBuildAppPath(npmDirectory: string, dmgName: string) { 57 | return path.join( 58 | npmDirectory, 59 | 'src-tauri/target/universal-apple-darwin/release/bundle/dmg', 60 | dmgName 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /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 9 | import tauriConf from './tauriConf.js'; 10 | 11 | import logger from '@/options/logger.js'; 12 | import { mergeTauriConfig } from './common.js'; 13 | import { npmDirectory } from '@/utils/dir.js'; 14 | 15 | export default class WinBuilder implements IBuilder { 16 | async prepare() { 17 | logger.info( 18 | 'To build the Windows app, you need to install Rust and VS Build Tools.' 19 | ); 20 | logger.info( 21 | 'See more in https://tauri.app/v1/guides/getting-started/prerequisites#installing\n' 22 | ); 23 | if (checkRustInstalled()) { 24 | return; 25 | } 26 | 27 | const res = await prompts({ 28 | type: 'confirm', 29 | message: 'We detected that you have not installed Rust. Install it now?', 30 | name: 'value', 31 | }); 32 | 33 | if (res.value) { 34 | // TODO 国内有可能会超时 35 | await installRust(); 36 | } else { 37 | logger.error('Error: Pake needs Rust to package your webapp!!!'); 38 | process.exit(2); 39 | } 40 | } 41 | 42 | async build(url: string, options: PakeAppOptions) { 43 | logger.debug('PakeAppOptions', options); 44 | const { name } = options; 45 | 46 | await mergeTauriConfig(url, options, tauriConf); 47 | 48 | await shellExec(`cd ${npmDirectory} && npm install && npm run build`); 49 | const language = tauriConf.tauri.bundle.windows.wix.language[0]; 50 | const arch = process.arch; 51 | const msiName = `${name}_${tauriConf.package.version}_${arch}_${language}.msi`; 52 | const appPath = this.getBuildAppPath(npmDirectory, msiName); 53 | const distPath = path.resolve(`${name}.msi`); 54 | await fs.copyFile(appPath, distPath); 55 | await fs.unlink(appPath); 56 | logger.success('Build success!'); 57 | logger.success('You can find the app installer in', distPath); 58 | } 59 | 60 | getBuildAppPath(npmDirectory: string, dmgName: string) { 61 | return path.join( 62 | npmDirectory, 63 | 'src-tauri/target/release/bundle/msi', 64 | dmgName 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.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 | # archive-name: target-linux.tar.gz 21 | - build: windows 22 | os: windows-latest 23 | rust: stable-x86_64-msvc 24 | target: x86_64-pc-windows-msvc 25 | # archive-name: target-windows.tar.gz 26 | - build: macos 27 | os: macos-latest 28 | rust: stable 29 | target: x86_64-apple-darwin 30 | # archive-name: target-macos.tar.gz 31 | fail-fast: false 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v2 36 | 37 | - name: install node 38 | uses: actions/setup-node@v1 39 | with: 40 | node-version: 18 41 | 42 | - name: Install Rust 43 | uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: ${{ matrix.rust }} 46 | profile: minimal 47 | override: true 48 | target: ${{ matrix.target }} 49 | 50 | - name: install dependencies (ubuntu only) 51 | if: matrix.os == 'ubuntu-20.04' 52 | uses: awalsh128/cache-apt-pkgs-action@latest 53 | with: 54 | 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 55 | version: 1.1 56 | 57 | - name: build for Ubuntu 58 | if: matrix.os == 'ubuntu-20.04' 59 | run: npm run build:all-unix 60 | 61 | - name: build for MacOS 62 | if: matrix.os == 'macos-latest' 63 | run: | 64 | rustup target add aarch64-apple-darwin 65 | npm run build:all-unix 66 | 67 | - name: build for windows 68 | if: matrix.os == 'windows-latest' 69 | run: | 70 | npm run build:all-windows 71 | 72 | - name: Upload files 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | run: | 76 | curl -L https://github.com/probonopd/uploadtool/raw/master/upload.sh --output upload.sh 77 | bash upload.sh output/*/*.* 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pake-cli", 3 | "version": "1.2.1", 4 | "description": "🤱🏻 很简单的用 Rust 打包网页生成很小的桌面 App 🤱🏻 A simple way to make any web page a desktop application using Rust.", 5 | "engines": { 6 | "node": "^14.13 || >=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 | "dev:debug": "npm run tauri dev -- --features devtools", 36 | "build": "npm run tauri build --release", 37 | "build:mac": "npm run tauri build -- --target universal-apple-darwin", 38 | "build:all-unix": "chmod +x ./script/build.sh && ./script/build.sh", 39 | "build:all-windows": ".\\script\\build.bat", 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.2", 51 | "axios": "^1.1.3", 52 | "chalk": "^5.1.2", 53 | "commander": "^9.4.1", 54 | "file-type": "^18.0.0", 55 | "is-url": "^1.2.4", 56 | "loglevel": "^1.8.1", 57 | "ora": "^6.1.2", 58 | "prompts": "^2.4.2", 59 | "shelljs": "^0.8.5", 60 | "tmp-promise": "^3.0.3", 61 | "update-notifier": "^6.0.2" 62 | }, 63 | "devDependencies": { 64 | "@rollup/plugin-alias": "^4.0.2", 65 | "@rollup/plugin-commonjs": "^23.0.2", 66 | "@rollup/plugin-json": "^5.0.1", 67 | "@rollup/plugin-terser": "^0.1.0", 68 | "@rollup/plugin-typescript": "^9.0.2", 69 | "@types/is-url": "^1.2.30", 70 | "@types/page-icon": "^0.3.4", 71 | "@types/prompts": "^2.4.1", 72 | "@types/shelljs": "^0.8.11", 73 | "@types/tmp": "^0.2.3", 74 | "@types/update-notifier": "^6.0.1", 75 | "app-root-path": "^3.1.0", 76 | "concurrently": "^7.5.0", 77 | "cross-env": "^7.0.3", 78 | "rollup": "^3.3.0", 79 | "tslib": "^2.4.1", 80 | "typescript": "^4.9.3" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | // @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 | 17 | export default class LinuxBuilder implements IBuilder { 18 | async prepare() { 19 | logger.info( 20 | 'To build the Linux app, you need to install Rust and Linux package' 21 | ); 22 | logger.info( 23 | 'See more in https://tauri.app/v1/guides/getting-started/prerequisites#installing\n' 24 | ); 25 | if (checkRustInstalled()) { 26 | return; 27 | } 28 | 29 | const res = await prompts({ 30 | type: 'confirm', 31 | message: 'We detected that you have not installed Rust. Install it now?', 32 | name: 'value', 33 | }); 34 | 35 | if (res.value) { 36 | // TODO 国内有可能会超时 37 | await installRust(); 38 | } else { 39 | logger.error('Error: Pake needs Rust to package your webapp!!!'); 40 | process.exit(2); 41 | } 42 | } 43 | 44 | async build(url: string, options: PakeAppOptions) { 45 | logger.debug('PakeAppOptions', options); 46 | const { name } = options; 47 | 48 | await mergeTauriConfig(url, options, tauriConf); 49 | await shellExec(`cd ${npmDirectory} && npm install && npm run build`); 50 | 51 | let arch: string; 52 | if (process.arch === "x64") { 53 | arch = "amd64"; 54 | } else { 55 | arch = process.arch; 56 | } 57 | const debName = `${name}_${tauriConf.package.version}_${arch}.deb`; 58 | const appPath = this.getBuildAppPath(npmDirectory, "deb", debName); 59 | const distPath = path.resolve(`${name}.deb`); 60 | await fs.copyFile(appPath, distPath); 61 | await fs.unlink(appPath); 62 | 63 | const appImageName = `${name}_${tauriConf.package.version}_${arch}.AppImage`; 64 | const appImagePath = this.getBuildAppPath(npmDirectory, "appimage", appImageName); 65 | const distAppPath = path.resolve(`${name}.AppImage`); 66 | await fs.copyFile(appImagePath, distAppPath); 67 | await fs.unlink(appImagePath); 68 | logger.success('Build success!'); 69 | logger.success('You can find the deb app installer in', distPath); 70 | logger.success('You can find the AppImage app installer in', distAppPath); 71 | } 72 | 73 | getBuildAppPath(npmDirectory: string, packageType: string, packageName: string) { 74 | return path.join( 75 | npmDirectory, 76 | 'src-tauri/target/release/bundle/', 77 | packageType, 78 | packageName 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /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\tauri.conf.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.windows.conf.json 57 | 58 | echo. 59 | ::update package info 60 | set old_zh_name=!name_zh! 61 | set old_name=!name! 62 | set old_title=!title! 63 | set old_url=!url! 64 | ::build package 65 | echo building package !index!/!total! 66 | echo package name is !name! !name_zh! 67 | echo npm run build:windows 68 | @echo off 69 | call npm run tauri build -- --target x86_64-pc-windows-msvc 70 | move src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\*.msi output\windows\!title!_x64.msi 71 | ::rm cache 72 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\*.exe 73 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\resources\*.ico 74 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\png\*.ico 75 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\wix\*.* 76 | del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\app.* 77 | rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\resources 78 | rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\png 79 | rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\wix 80 | @echo on 81 | echo package build success! 82 | echo. 83 | echo. 84 | 85 | set /A index=index+1 86 | @echo off 87 | 88 | ) 89 | 90 | :: for windows, we need replace package name to lower again 91 | :: .\script\sd.exe "\"productName\": \"WeRead\"" "\"productName\": \"weread\"" src-tauri\tauri.conf.json 92 | echo "output dir is output\windows" 93 | 94 | ::recovery code 95 | .\script\sd.exe %url% %init_url% src-tauri\tauri.conf.json 96 | .\script\sd.exe %title% %init_title% src-tauri\tauri.conf.json 97 | .\script\sd.exe %name% %init_name% src-tauri\tauri.windows.conf.json -------------------------------------------------------------------------------- /bin/README_EN.md: -------------------------------------------------------------------------------- 1 | ## Install 2 | 3 | ```bash 4 | npm install -g pake-cli 5 | ``` 6 | 7 | If the installation fails and you are prompted that you do not have permission, please see this [website](https://gist.github.com/Giancarlos/d087f8a9e6516716da98ad0c0f5a8f58) . 8 | 9 | Also make sure that you're using a correct Node.js version (`^14.13 || >=16.0.0`). If you're using [nvm](https://github.com/nvm-sh/nvm) for Node.js version management you may run `nvm use` from the root folder of the project and the correct version will be picked up. Other Node.js version management tools, such as [fnm](https://github.com/Schniz/fnm) and [tj/n](https://github.com/tj/n), should also have similar feature. 10 | 11 | **try not to use `sudo` permissions**, If you must use sudo, you need install rust in you system environment. For Mac, you can use brew to install it. For Linux like Ubuntu, you need apt to install it. In addition, Ubuntu can run the following commands before starting, depending on the previous needs. 12 | 13 | ```bash 14 | sudo apt install libdbus-1-dev libgtk-3-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev libwebkit2gtk-4.0-dev 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```bash 20 | pake url [options] 21 | ``` 22 | 23 | After the packaging, the application defaults to the current working directory. Since the environment needs to be configured for the first packaging, it will take some time. Please wait patiently. 24 | 25 | Note: The Rust environment is required for packaging. If you do not have 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) it yourself. 26 | 27 | ### url 28 | 29 | The url🔗 is the webpage link you need to package, Must be provided. 30 | 31 | ### [options] 32 | 33 | Some specific options are provided. When packaging, corresponding parameters can be passed to achieve customized effects. 34 | 35 | #### [name] 36 | 37 | The application name, if not specified when entering, will prompt you to enter, input must be English. 38 | 39 | ```shell 40 | --name 41 | ``` 42 | 43 | #### [icon] 44 | 45 | The application icon, support local and remote files, the default is brand icon of Pake. Customized product icon can go to [icon icons](https://icon-icons.com) Or [macOSicons](https://macosicons.com/#/) download it. 46 | 47 | - MacOS must be `.icns` 48 | - Windows must be `.ico` 49 | - Linux must be `.png` 50 | 51 | ```shell 52 | --icon 53 | ``` 54 | 55 | #### [height] 56 | 57 | The height of the packaged application window. The default is `780px`. 58 | 59 | ```shell 60 | --height 61 | ``` 62 | 63 | #### [width] 64 | 65 | The width of the packaged application window. The default is `1200px`. 66 | 67 | ```shell 68 | --width 69 | ``` 70 | 71 | #### [transparent] 72 | 73 | Whether to enable the immersive header. The default is `false`. 74 | 75 | ```shell 76 | --transparent 77 | ``` 78 | 79 | #### [resize] 80 | 81 | Whether the size can be dragged. The default value is `true`. 82 | 83 | ```shell 84 | --no-resizable 85 | ``` 86 | 87 | #### [fullscreen] 88 | 89 | Whether to open the full screen after opening the application. The default is `false`. 90 | 91 | ```shell 92 | --fullscreen 93 | ``` 94 | -------------------------------------------------------------------------------- /.github/workflows/pake_build_with_cache.yaml: -------------------------------------------------------------------------------- 1 | name: Build App with Cache 2 | on: 3 | push: 4 | # Sequence of patterns matched against refs/tags 5 | branches: 6 | - release 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 | # archive-name: target-linux.tar.gz 21 | - build: windows 22 | os: windows-latest 23 | rust: stable-x86_64-msvc 24 | target: x86_64-pc-windows-msvc 25 | # archive-name: target-windows.tar.gz 26 | - build: macos 27 | os: macos-latest 28 | rust: stable 29 | target: x86_64-apple-darwin 30 | # archive-name: target-macos.tar.gz 31 | fail-fast: false 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v2 36 | 37 | - name: install node 38 | uses: actions/setup-node@v1 39 | with: 40 | node-version: 18 41 | 42 | - name: Install Rust 43 | uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: ${{ matrix.rust }} 46 | profile: minimal 47 | override: true 48 | target: ${{ matrix.target }} 49 | 50 | - name: install dependencies (ubuntu only) 51 | if: matrix.os == 'ubuntu-20.04' 52 | uses: awalsh128/cache-apt-pkgs-action@latest 53 | with: 54 | 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 55 | version: 1.1 56 | 57 | - name: rust cache restore 58 | uses: ylemkimon/cache-restore@v2 59 | with: 60 | path: | 61 | ~/.cargo/bin/ 62 | ~/.cargo/registry/index/ 63 | ~/.cargo/registry/cache/ 64 | ~/.cargo/git/db/ 65 | src-tauri/target/ 66 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 67 | 68 | - name: build for Ubuntu 69 | if: matrix.os == 'ubuntu-20.04' 70 | run: npm run build:all-unix 71 | 72 | - name: build for MacOS 73 | if: matrix.os == 'macos-latest' 74 | run: | 75 | rustup target add aarch64-apple-darwin 76 | npm run build:all-unix 77 | 78 | - name: build for windows 79 | if: matrix.os == 'windows-latest' 80 | run: | 81 | npm run build:all-windows 82 | 83 | - name: Upload files 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | UPLOADTOOL_ISPRERELEASE: true 87 | run: | 88 | curl -L https://github.com/probonopd/uploadtool/raw/master/upload.sh --output upload.sh 89 | bash upload.sh output/*/*.* 90 | 91 | - name: rust cache store 92 | uses: actions/cache@v3 93 | with: 94 | path: | 95 | ~/.cargo/bin/ 96 | ~/.cargo/registry/index/ 97 | ~/.cargo/registry/cache/ 98 | ~/.cargo/git/db/ 99 | src-tauri/target/ 100 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 101 | 102 | # - name: Build archive 103 | # shell: bash 104 | # run: | 105 | # cd src-tauri 106 | # tar -czf "${{ matrix.archive-name }}" target 107 | 108 | # - name: Upload archive 109 | # uses: actions/upload-artifact@v1 110 | # with: 111 | # name: ${{ matrix.archive-name }} 112 | # path: src-tauri/${{ matrix.archive-name }} 113 | -------------------------------------------------------------------------------- /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 | # total app number, ignore first line 27 | total=$(sed -n '$=' app.csv) 28 | export total=$((total-1)) 29 | export index=1 30 | 31 | export old_name="weread" 32 | export old_title="WeRead" 33 | export old_zh_name="微信阅读" 34 | export old_url="https://weread.qq.com/" 35 | export package_prefix="com-tw93" 36 | 37 | 38 | 39 | if [[ "$OSTYPE" =~ ^linux ]]; then 40 | echo "===============" 41 | echo "Build for Linux" 42 | echo "===============" 43 | export sd=${SHELL_FOLDER}/sd-linux-x64 44 | chmod +x "$sd" 45 | # for linux, package name may be com.xxx.xxx 46 | echo "rename package name" 47 | export desktop_file="src-tauri/assets/${package_prefix}.weread.desktop" 48 | $sd "\"productName\": \"WeRead\"" "\"productName\": \"${package_prefix}-weread\"" src-tauri/tauri.conf.json 49 | fi 50 | 51 | if [[ "$OSTYPE" =~ ^darwin ]]; then 52 | echo "===============" 53 | echo "Build for MacOS" 54 | echo "===============" 55 | 56 | export sd=${SHELL_FOLDER}/sd-apple-x64 57 | chmod +x "$sd" 58 | echo "rename package name" 59 | $sd "\"productName\": \"weread\"" "\"productName\": \"WeRead\"" src-tauri/tauri.conf.json 60 | fi 61 | 62 | tail -n +2 app.csv | while IFS=, read -r -a arr; 63 | do 64 | package_name=${arr[0]} 65 | package_title=${arr[1]} 66 | package_zh_name=${arr[2]} 67 | url=${arr[3]} 68 | # replace package info 69 | $sd -s "${old_url}" "${url}" src-tauri/tauri.conf.json 70 | $sd "${old_name}" "${package_name}" src-tauri/tauri.conf.json 71 | # echo "update ico with 32x32 pictue" 72 | # $sd "${old_name}" "${package_name}" src-tauri/src/main.rs 73 | 74 | # for apple, need replace title 75 | if [[ "$OSTYPE" =~ ^darwin ]]; then 76 | $sd "${old_name}" "${package_name}" src-tauri/tauri.macos.conf.json 77 | $sd "${old_title}" "${package_title}" src-tauri/tauri.conf.json 78 | fi 79 | 80 | # echo "update ico with 32x32 pictue" 81 | # cp "src-tauri/png/${package_name}_32.ico" "src-tauri/icons/icon.ico" 82 | 83 | if [[ "$OSTYPE" =~ ^linux ]]; then 84 | $sd "${old_name}" "${package_name}" src-tauri/tauri.linux.conf.json 85 | echo "update desktop" 86 | old_desktop="src-tauri/assets/${package_prefix}-${old_name}.desktop" 87 | new_desktop="src-tauri/assets/${package_prefix}-${package_name}.desktop" 88 | mv "${old_desktop}" "${new_desktop}" 89 | $sd "${old_zh_name}" "${package_zh_name}" "${new_desktop}" 90 | $sd "${old_name}" "${package_name}" "${new_desktop}" 91 | fi 92 | 93 | # update package info 94 | old_name=${package_name} 95 | old_title=${package_title} 96 | old_zh_name=${package_zh_name} 97 | old_url=${url} 98 | 99 | echo "building package ${index}/${total}" 100 | echo "package name is ${package_name} (${package_zh_name})" 101 | 102 | if [[ "$OSTYPE" =~ ^linux ]]; then 103 | npm run tauri build 104 | mv src-tauri/target/release/bundle/deb/${package_prefix}-${package_name}*.deb output/linux/${package_title}_amd64.deb 105 | mv src-tauri/target/release/bundle/appimage/${package_prefix}-${package_name}*.AppImage output/linux/${package_title}_amd64.AppImage 106 | echo clear cache 107 | rm src-tauri/target/release 108 | rm -rf src-tauri/target/release/bundle 109 | 110 | fi 111 | 112 | if [[ "$OSTYPE" =~ ^darwin ]]; then 113 | 114 | npm run tauri build -- --target universal-apple-darwin 115 | mv src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg output/macos/${package_title}.dmg 116 | echo clear cache 117 | rm -rf src-tauri/target/universal-apple-darwin 118 | rm src-tauri/target/aarch64-apple-darwin/release 119 | rm src-tauri/target/x86_64-apple-darwin/release 120 | fi 121 | 122 | echo "package build success!" 123 | index=$((index+1)) 124 | done 125 | 126 | echo "build all package success!" 127 | echo "you run 'rm src-tauri/assets/*.desktop && git checkout src-tauri' to recovery code" 128 | -------------------------------------------------------------------------------- /bin/builders/common.ts: -------------------------------------------------------------------------------- 1 | import { PakeAppOptions } from '@/types.js'; 2 | import prompts from 'prompts'; 3 | import path from 'path'; 4 | import fs from 'fs/promises'; 5 | import { npmDirectory } from '@/utils/dir.js'; 6 | import logger from '@/options/logger.js'; 7 | 8 | export async function promptText(message: string, initial?: string) { 9 | const response = await prompts({ 10 | type: 'text', 11 | name: 'content', 12 | message, 13 | initial, 14 | }); 15 | return response.content; 16 | } 17 | 18 | export async function mergeTauriConfig( 19 | url: string, 20 | options: PakeAppOptions, 21 | tauriConf: any 22 | ) { 23 | const { 24 | width, 25 | height, 26 | fullscreen, 27 | transparent, 28 | resizable, 29 | identifier, 30 | name, 31 | } = options; 32 | 33 | const tauriConfWindowOptions = { 34 | width, 35 | height, 36 | fullscreen, 37 | transparent, 38 | resizable, 39 | }; 40 | // Package name is valid ? 41 | // for Linux, package name must be a-z, 0-9 or "-", not allow to A-Z and other 42 | if (process.platform === "linux") { 43 | const reg = new RegExp(/[0-9]*[a-z]+[0-9]*\-?[0-9]*[a-z]*[0-9]*\-?[0-9]*[a-z]*[0-9]*/); 44 | if (!reg.test(name) || reg.exec(name)[0].length != name.length) { 45 | logger.error("package name is illegal, it must be lowercase letters, numbers, dashes, and it must contain the lowercase letters.") 46 | logger.error("E.g com-123-xxx, 123pan, pan123,weread, we-read"); 47 | process.exit(); 48 | } 49 | } 50 | if (process.platform === "win32" || process.platform === "darwin" ) { 51 | const reg = new RegExp(/([0-9]*[a-zA-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 letters, numbers, and it must contain the letters") 54 | logger.error("E.g 123pan,123Pan,Pan123,weread,WeRead,WERead"); 55 | process.exit(); 56 | } 57 | } 58 | 59 | 60 | Object.assign(tauriConf.tauri.windows[0], { url, ...tauriConfWindowOptions }); 61 | tauriConf.package.productName = name; 62 | tauriConf.tauri.bundle.identifier = identifier; 63 | const exists = await fs.stat(options.icon) 64 | .then(() => true) 65 | .catch(() => false); 66 | if (exists) { 67 | let updateIconPath = true; 68 | let customIconExt = path.extname(options.icon).toLowerCase(); 69 | if (process.platform === "win32") { 70 | if (customIconExt === ".ico") { 71 | const ico_path = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}_32.ico`); 72 | tauriConf.tauri.bundle.resources = [`png/${name.toLowerCase()}_32.ico`]; 73 | await fs.copyFile(options.icon, ico_path); 74 | } else { 75 | updateIconPath = false; 76 | logger.warn(`icon file in Windows must be 256 * 256 pix with .ico type, but you give ${customIconExt}`); 77 | } 78 | } 79 | if (process.platform === "linux") { 80 | delete tauriConf.tauri.bundle.deb.files; 81 | if (customIconExt != ".png") { 82 | updateIconPath = false; 83 | logger.warn(`icon file in Linux must be 512 * 512 pix with .png type, but you give ${customIconExt}`); 84 | } 85 | } 86 | 87 | if (process.platform === "darwin" && customIconExt !== ".icns") { 88 | updateIconPath = false; 89 | logger.warn(`icon file in MacOS must be .icns type, but you give ${customIconExt}`); 90 | } 91 | if (updateIconPath) { 92 | tauriConf.tauri.bundle.icon = [options.icon]; 93 | } else { 94 | logger.warn(`icon file will not change with default.`); 95 | } 96 | } else { 97 | logger.warn("the custom icon path may not exists. we will use default icon to replace it"); 98 | } 99 | 100 | 101 | let configPath = ""; 102 | switch (process.platform) { 103 | case "win32": { 104 | configPath = path.join(npmDirectory, 'src-tauri/tauri.windows.conf.json'); 105 | break; 106 | } 107 | case "darwin": { 108 | configPath = path.join(npmDirectory, 'src-tauri/tauri.macos.conf.json'); 109 | break; 110 | } 111 | case "linux": { 112 | configPath = path.join(npmDirectory, 'src-tauri/tauri.linux.conf.json'); 113 | break; 114 | } 115 | } 116 | 117 | let bundleConf = {tauri: {bundle: tauriConf.tauri.bundle}}; 118 | await fs.writeFile( 119 | configPath, 120 | Buffer.from(JSON.stringify(bundleConf), 'utf-8') 121 | ); 122 | 123 | 124 | const configJsonPath = path.join(npmDirectory, 'src-tauri/tauri.conf.json') 125 | await fs.writeFile( 126 | configJsonPath, 127 | Buffer.from(JSON.stringify(tauriConf), 'utf-8') 128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /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 sexualized 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 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // at the top of main.rs - that will prevent the console from showing 2 | #![windows_subsystem = "windows"] 3 | extern crate image; 4 | use tauri_utils::config::{Config, WindowConfig}; 5 | use wry::{ 6 | application::{ 7 | event::{Event, StartCause, WindowEvent}, 8 | event_loop::{ControlFlow, EventLoop}, 9 | menu::MenuType, 10 | window::{Fullscreen, Window, WindowBuilder}, 11 | }, 12 | webview::WebViewBuilder, 13 | }; 14 | 15 | // enum UserEvent { 16 | // NewWindow(String), 17 | // } 18 | 19 | #[cfg(target_os = "macos")] 20 | use wry::application::{ 21 | accelerator::{Accelerator, SysMods}, 22 | keyboard::KeyCode, 23 | menu::{MenuBar as Menu, MenuItem, MenuItemAttributes}, 24 | platform::macos::WindowBuilderExtMacOS, 25 | }; 26 | 27 | #[cfg(target_os = "windows")] 28 | use wry::application::window::Icon; 29 | 30 | #[cfg(any(target_os = "linux", target_os = "windows"))] 31 | use wry::webview::WebContext; 32 | 33 | fn main() -> wry::Result<()> { 34 | #[cfg(target_os = "macos")] 35 | let (menu_bar_menu, close_item) = { 36 | let mut menu_bar_menu = Menu::new(); 37 | let mut first_menu = Menu::new(); 38 | first_menu.add_native_item(MenuItem::Hide); 39 | first_menu.add_native_item(MenuItem::EnterFullScreen); 40 | first_menu.add_native_item(MenuItem::Minimize); 41 | first_menu.add_native_item(MenuItem::Separator); 42 | first_menu.add_native_item(MenuItem::Copy); 43 | first_menu.add_native_item(MenuItem::Cut); 44 | first_menu.add_native_item(MenuItem::Paste); 45 | first_menu.add_native_item(MenuItem::Undo); 46 | first_menu.add_native_item(MenuItem::Redo); 47 | first_menu.add_native_item(MenuItem::SelectAll); 48 | first_menu.add_native_item(MenuItem::Separator); 49 | let close_item = first_menu.add_item( 50 | MenuItemAttributes::new("CloseWindow") 51 | .with_accelerators(&Accelerator::new(SysMods::Cmd, KeyCode::KeyW)), 52 | ); 53 | first_menu.add_native_item(MenuItem::Quit); 54 | menu_bar_menu.add_submenu("App", true, first_menu); 55 | (menu_bar_menu, close_item) 56 | }; 57 | 58 | #[cfg(any(target_os = "linux", target_os = "windows"))] 59 | let ( 60 | package_name, 61 | WindowConfig { 62 | url, 63 | width, 64 | height, 65 | resizable, 66 | fullscreen, 67 | .. 68 | }, 69 | ) = { 70 | let (package_name, windows_config) = get_windows_config(); 71 | ( 72 | package_name 73 | .expect("can't get package name in config file") 74 | .to_lowercase(), 75 | windows_config.unwrap_or_default(), 76 | ) 77 | }; 78 | 79 | #[cfg(target_os = "macos")] 80 | let WindowConfig { 81 | url, 82 | width, 83 | height, 84 | resizable, 85 | transparent, 86 | fullscreen, 87 | .. 88 | } = get_windows_config().1.unwrap_or_default(); 89 | 90 | // let event_loop: EventLoop = EventLoop::with_user_event(); 91 | // let proxy = event_loop.create_proxy(); 92 | let event_loop = EventLoop::new(); 93 | let common_window = WindowBuilder::new() 94 | .with_title("") 95 | .with_resizable(resizable) 96 | .with_fullscreen(if fullscreen { 97 | Some(Fullscreen::Borderless(None)) 98 | } else { 99 | None 100 | }) 101 | .with_inner_size(wry::application::dpi::LogicalSize::new(width, height)); 102 | 103 | #[cfg(target_os = "windows")] 104 | let window = { 105 | let mut icon_path = format!("png/{}_32.ico", package_name); 106 | // 假如没有设置,就使用默认的即可 107 | if !std::path::Path::new(&icon_path).exists() { 108 | icon_path = "png/icon_32.ico".to_string(); 109 | } 110 | let icon = load_icon(std::path::Path::new(&icon_path)); 111 | common_window 112 | .with_decorations(true) 113 | .with_window_icon(Some(icon)) 114 | .build(&event_loop) 115 | .unwrap() 116 | }; 117 | 118 | #[cfg(target_os = "linux")] 119 | let window = common_window.build(&event_loop).unwrap(); 120 | 121 | #[cfg(target_os = "macos")] 122 | let window = common_window 123 | .with_fullsize_content_view(true) 124 | .with_titlebar_buttons_hidden(false) 125 | .with_titlebar_transparent(transparent) 126 | .with_title_hidden(true) 127 | .with_menu(menu_bar_menu) 128 | .build(&event_loop) 129 | .unwrap(); 130 | 131 | let handler = move |window: &Window, req: String| { 132 | if req == "drag_window" { 133 | let _ = window.drag_window(); 134 | } else if req == "fullscreen" { 135 | let is_maximized = window.is_maximized(); 136 | window.set_maximized(!is_maximized); 137 | } else if req.starts_with("open_browser") { 138 | let href = req.replace("open_browser:", ""); 139 | webbrowser::open(&href).expect("no browser"); 140 | } 141 | }; 142 | 143 | // 用于欺骗部分页面对于浏览器的强检测 144 | 145 | #[cfg(target_os = "macos")] 146 | let webview = { 147 | let user_agent_string = "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"; 148 | WebViewBuilder::new(window)? 149 | .with_user_agent(user_agent_string) 150 | .with_url(&url.to_string())? 151 | .with_devtools(cfg!(feature = "devtools")) 152 | .with_initialization_script(include_str!("pake.js")) 153 | .with_ipc_handler(handler) 154 | // .with_new_window_req_handler(move |uri: String| { 155 | // let submitted = proxy.send_event(UserEvent::NewWindow(uri.clone())).is_ok(); 156 | // submitted 157 | // }) 158 | .with_back_forward_navigation_gestures(true) 159 | .build()? 160 | }; 161 | 162 | #[cfg(any(target_os = "linux", target_os = "windows"))] 163 | let webview = { 164 | let home_dir = match home::home_dir() { 165 | Some(path1) => path1, 166 | None => panic!("Error, can't found you home dir!!"), 167 | }; 168 | #[cfg(target_os = "windows")] 169 | let data_dir = home_dir.join("AppData").join("Roaming").join(package_name); 170 | #[cfg(target_os = "linux")] 171 | let data_dir = home_dir.join(".config").join(package_name); 172 | if !data_dir.exists() { 173 | std::fs::create_dir(&data_dir) 174 | .unwrap_or_else(|_| panic!("can't create dir {}", data_dir.display())); 175 | } 176 | let mut web_content = WebContext::new(Some(data_dir)); 177 | #[cfg(target_os = "windows")] 178 | let user_agent_string = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"; 179 | #[cfg(target_os = "linux")] 180 | let user_agent_string = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"; 181 | WebViewBuilder::new(window)? 182 | .with_user_agent(user_agent_string) 183 | .with_url(&url.to_string())? 184 | .with_devtools(cfg!(feature = "devtools")) 185 | .with_initialization_script(include_str!("pake.js")) 186 | .with_ipc_handler(handler) 187 | .with_web_context(&mut web_content) 188 | // .with_new_window_req_handler(move |uri: String| { 189 | // let submitted = proxy.send_event(UserEvent::NewWindow(uri.clone())).is_ok(); 190 | // submitted 191 | // }) 192 | .build()? 193 | }; 194 | #[cfg(feature = "devtools")] 195 | { 196 | webview.open_devtools(); 197 | } 198 | 199 | event_loop.run(move |event, _, control_flow| { 200 | *control_flow = ControlFlow::Wait; 201 | 202 | match event { 203 | Event::NewEvents(StartCause::Init) => println!("Wry has started!"), 204 | Event::WindowEvent { 205 | event: WindowEvent::CloseRequested, 206 | .. 207 | } => *control_flow = ControlFlow::Exit, 208 | Event::MenuEvent { 209 | menu_id, 210 | origin: MenuType::MenuBar, 211 | .. 212 | } => { 213 | #[cfg(target_os = "macos")] 214 | if menu_id == close_item.clone().id() { 215 | webview.window().set_minimized(true); 216 | } 217 | println!("Clicked on {:?}", menu_id); 218 | println!("Clicked on {:?}", webview.window().is_visible()); 219 | } 220 | // Event::UserEvent(UserEvent::NewWindow(uri)) => { 221 | // webbrowser::open(&uri).expect("no browser"); 222 | // } 223 | _ => (), 224 | } 225 | }); 226 | } 227 | 228 | fn get_windows_config() -> (Option, Option) { 229 | let config_file = include_str!("../tauri.conf.json"); 230 | let config: Config = serde_json::from_str(config_file).expect("failed to parse windows config"); 231 | ( 232 | config.package.product_name.clone(), 233 | config.tauri.windows.first().cloned(), 234 | ) 235 | } 236 | 237 | #[cfg(target_os = "windows")] 238 | fn load_icon(path: &std::path::Path) -> Icon { 239 | let (icon_rgba, icon_width, icon_height) = { 240 | // alternatively, you can embed the icon in the binary through `include_bytes!` macro and use `image::load_from_memory` 241 | let image = image::open(path) 242 | .expect("Failed to open icon path") 243 | .into_rgba8(); 244 | let (width, height) = image.dimensions(); 245 | let rgba = image.into_raw(); 246 | (rgba, width, height) 247 | }; 248 | Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") 249 | } 250 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 |

中文 | English

2 |

3 | 4 |

5 |

Pake

6 |
7 | 8 | twitter 9 | 10 | telegram 11 | 12 | GitHub downloads 13 | 14 | GitHub commit 15 | 16 | GitHub closed issues 17 |
18 |
A simple way to package a web page to desktop application, supporting Mac / Windows / Linux, app download、command line one-click packaging、custom development can be found in the following documents, welcome to Discussions to see if there have anything you're interesting.
19 | 20 | ## Features 21 | 22 | 🏂 **Small**:Nearly 40 times smaller than Electron package, less than 3M. 23 | 😂 **Fast**:Using the Rust Tauri, the performance experience is much lighter and faster than JS, memory is much smaller. 24 | 🩴 **Special**:Not just packaged, with shortcut pass-through, immersive windows, minimalist customization of products. 25 | 🐶 **Toy**:Just a very simple little toy, a way to play with Rust instead of the old idea of shelling the web. 26 | 27 | ## Download 28 | 29 | 30 | 31 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 68 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 84 | 89 | 90 | 91 | 92 | 93 | 94 |
WeRead 32 | Mac 33 | Windows 34 | Linux 35 | Twitter 37 | Mac 38 | Windows 39 | Linux 40 |
LiZhi 48 | Mac 49 | Windows 50 | Linux 51 | YouTube 53 | Mac 54 | Windows 55 | Linux 56 |
ZLibrary 64 | Mac 65 | Windows 66 | Linux 67 | Reference 69 | Mac 70 | Windows 71 | Linux 72 |
Qwerty 80 | Mac 81 | Windows 82 | Linux 83 | ChatGPT 85 | Mac 86 | Windows 87 | Linux 88 |
95 | 96 |
97 | 🏂 Expand the shortcut key 98 | 99 |
100 | 101 | | Mac | Windows/Linux | Function | 102 | | --------------------------- | ------------------------------ | ----------------------------- | 103 | | + [ | Ctrl + | Return to the previous page | 104 | | + ] | Ctrl + | Go to the next page | 105 | | + | Ctrl + | Auto scroll to top of page | 106 | | + | Ctrl + | Auto scroll to bottom of page | 107 | | + r | Ctrl + r | Refresh Page | 108 | | + w | Ctrl + w | Hide window, not quite | 109 | | + - | Ctrl + - | Zoom out the page | 110 | | + + | Ctrl + + | Zoom in the page | 111 | | + = | Ctrl + = | Zoom in the Page | 112 | | + 0 | Ctrl + 0 | Reset the page zoom | 113 | 114 | In addition, it supports double-clicking the head to switch to full screen, Mac users support gesture mode to return and go to the next page, and dragging the head to move the window. 115 | 116 |
117 | 118 | ## Before you start 119 | 120 | 1. **Beginner users**: Use the 「Download」 method to play with Pake's capabilities, go to discussion groups for help, or try the [Action](https://github.com/tw93/Pake/wiki/GitHub-Actions-Online-Compilation-Multi-system-Version) method. 121 | 2. **Development users**: Use 「command line packing」, Mac friendly, Windows/Linux requires a bit of tinkering, but both require environment [configuration](https://tauri.app/v1/guides/getting-started/prerequisites). 122 | 3. **Hacker users**: If you know both front-end and rust, try the following custom development, which allows you to customize your features with deep secondary development. 123 | 124 | ## Command line packing 125 | 126 | 127 | 128 | 129 |

130 | 131 | **Pake provides a command line tool that makes it quicker and easier to customize the packages you need, as detailed in [documentation](./.github/workflows/docs/README_EN.md).** 132 | 133 | ```bash 134 | // Install with npm 135 | npm install -g pake-cli 136 | 137 | // Command usage 138 | pake url [options] 139 | 140 | // Play casually, first time due to the installation environment will be a little slow 141 | pake https://weekly.tw93.fun --name Weekly --transparent 142 | ``` 143 | 144 | If you are a little white who doesn't know how to use the command line, a good option is to use **GitHub Actions online compilation**, see the [tutorial](https://github.com/tw93/Pake/wiki/GitHub-Actions-Online-Compilation-Multi-system-Version). 145 | 146 | ## Development 147 | 148 | Before starting, make sure that the computer has installed the Rust and Node environment,refer to the [Tauri documentation](https://tauri.app/v1/guides/getting-started/prerequisites) to quickly configure your environment before you start. If you don't understand, it will be more appropriate to use the above command line to pack with one click. 149 | 150 | ```sh 151 | // Install Dependencies 152 | npm i 153 | 154 | // Local development 155 | npm run dev 156 | 157 | // Local debug 158 | npm run dev:debug 159 | 160 | // Pack application 161 | npm run build 162 | 163 | ``` 164 | 165 | ## Advanced use 166 | 167 | 1. The code structure can be referred to [Here](https://github.com/tw93/Pake/wiki/Description-of-Pake's-code-structure), it is convenient for you to learn more before development. 168 | 2. Modify the `tauri.conf.json` and `tauri.xxx.conf.json` in the `src-tauri` directory to include 4 fields `url, productName, icon, identifier`, icon can be selected from the `icons` directory or downloaded from [macOSicons](https://macosicons.com/#/) to match the product. 169 | 3. For window property settings, you can modify the `width/height` of the `windows` property in `tauri.conf.json`, whether it is `fullscreen`, whether it is `resizable`, If you want to adapt the immersive header under Mac, you can set `transparent` to `true` and then find header element and add the `padding-top` style. 170 | 4. About style rewriting, advertising shielding, js injection, container message communication, and user-defined shortcut keys, you can see [Advanced Usage of Make](https://github.com/tw93/Pake/wiki/Advanced-Usage-of-Make). 171 | 172 | ## Support 173 | 174 | 1. I have two cats, one is called TangYuan, and one is called Coke, If you think Pake makes your life better, you can give my cats feed canned food 🥩🍤. 175 | 2. If you like Pake, you can star it in Github. Also welcome to [recommend Pake](https://twitter.com/intent/tweet?url=https://github.com/tw93/Pake&text=%23Pake%20-%20A%20simple%20Rust%20packaged%20web%20pages%20to%20generate%20Mac%20App%20tool,%20compared%20to%20traditional%20Electron%20package,%20the%20size%20of%20nearly%2040%20times%20smaller,%20generally%20about%202M,%20the%20underlying%20use%20of%20Tauri,%20performance%20experience%20than%20the%20JS%20framework%20is%20much%20lighter~) to your friends. 176 | 3. You can follow my [Twitter](https://twitter.com/HiTw93) to get the latest news of Pake, or join [Telegram](https://t.me/miaoyan) chat group. 177 | 4. I hope that you will enjoy playing with it. If you find a page that would be great for a Mac App, please let me know. 178 | -------------------------------------------------------------------------------- /src-tauri/src/pake.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {string} KeyboardKey `event.key` 的代号, 3 | * 见 4 | * @typedef {() => void} OnKeyDown 使用者按下 [CtrlKey] 或者 ⌘ [KeyboardKey]时应该执行的行为 5 | * 以 Ctrl键或者Meta 键 (⌘) 为首的快捷键清单。 6 | * 每个写在这里的 shortcuts 都会运行 {@link Event.preventDefault}. 7 | * @type {Record} 8 | */ 9 | 10 | const metaKeyShortcuts = { 11 | ArrowUp: () => scrollTo(0, 0), 12 | ArrowDown: () => scrollTo(0, document.body.scrollHeight), 13 | "[": () => window.history.back(), 14 | "]": () => window.history.forward(), 15 | r: () => window.location.reload(), 16 | "-": () => zoomOut(), 17 | "=": () => zoomIn(), 18 | "+": () => zoomIn(), 19 | 0: () => zoomCommon(() => "100%"), 20 | }; 21 | 22 | const ctrlKeyShortcuts = { 23 | ArrowUp: () => scrollTo(0, 0), 24 | ArrowDown: () => scrollTo(0, document.body.scrollHeight), 25 | ArrowLeft: () => window.history.back(), 26 | ArrowRight: () => window.history.forward(), 27 | r: () => window.location.reload(), 28 | "-": () => zoomOut(), 29 | "=": () => zoomIn(), 30 | "+": () => zoomIn(), 31 | 0: () => zoomCommon(() => "100%"), 32 | }; 33 | 34 | window.addEventListener("DOMContentLoaded", (_event) => { 35 | const style = document.createElement("style"); 36 | style.innerHTML = ` 37 | #page #footer-wrapper, 38 | .drawing-board .toolbar .toolbar-action, 39 | .c-swiper-container, 40 | .download_entry, 41 | .lang, .copyright, 42 | .wwads-cn, .adsbygoogle, 43 | #Bottom > div.content > div.inner, 44 | #Rightbar .sep20:nth-of-type(5), 45 | #Rightbar > div.box:nth-child(4), 46 | #Main > div.box:nth-child(8) > div 47 | #Wrapper > div.sep20, 48 | #Main > div.box:nth-child(8), 49 | #masthead-ad, 50 | #Rightbar > div:nth-child(6) > div.sidebar_compliance { 51 | display: none !important; 52 | } 53 | 54 | #page .main_header, .cb-layout-basic--navbar { 55 | padding-top: 20px; 56 | } 57 | 58 | .chakra-ui-light #app .chakra-heading, 59 | .chakra-ui-dark #app .chakra-heading, 60 | .chakra-ui-light #app .chakra-stack, 61 | .chakra-ui-dark #app .chakra-stack, 62 | .app-main .sidebar-mouse-in-out { 63 | padding-top: 10px; 64 | } 65 | 66 | #__next .overflow-hidden .flex.flex-1.flex-col { 67 | padding-left: 0; 68 | } 69 | 70 | #__next .overflow-hidden>.hidden.bg-gray-900 { 71 | display: none; 72 | } 73 | 74 | #__next .overflow-hidden main .absolute .text-xs{ 75 | visibility: hidden; 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: 154px !important; 88 | } 89 | 90 | .columns .column #header{ 91 | padding-top: 30px; 92 | } 93 | 94 | ytd-masthead>#container.style-scope.ytd-masthead { 95 | padding-top: 12px !important; 96 | } 97 | 98 | .wrap.h1body-exist.max-container > div.menu-tocs > div.menu-btn{ 99 | top: 28px; 100 | } 101 | 102 | #Wrapper{ 103 | background-color: #F8F8F8 !important; 104 | background-image:none !important; 105 | } 106 | 107 | #Top { 108 | border-bottom: none; 109 | } 110 | 111 | .container-with-note #home, .container-with-note #switcher{ 112 | top: 30px; 113 | } 114 | 115 | .geist-page nav.dashboard_nav__PRmJv { 116 | padding-top:10px; 117 | } 118 | 119 | .geist-page .submenu button{ 120 | margin-top:24px; 121 | } 122 | 123 | #react-root [data-testid="placementTracking"] article, 124 | #react-root a[href*="quick_promote_web"], 125 | #react-root [data-testid="AppTabBar_Explore_Link"], 126 | #react-root a[href*="/lists"][role="link"][aria-label], 127 | #react-root a[href="/i/bookmarks"] { 128 | display: none !important; 129 | } 130 | 131 | #react-root [data-testid="DMDrawer"] { 132 | visibility: hidden !important; 133 | } 134 | 135 | #react-root [data-testid="primaryColumn"] > div > div { 136 | position: relative !important; 137 | } 138 | 139 | #react-root [data-testid="sidebarColumn"] { 140 | visibility: hidden !important; 141 | width: 0 !important; 142 | margin: 0 !important; 143 | padding: 0 !important; 144 | z-index: 1 !important; 145 | } 146 | 147 | @media only screen and (min-width: 1000px) { 148 | #react-root main[role="main"] { 149 | align-items: center !important; 150 | overflow-x: clip !important; 151 | } 152 | 153 | #react-root [data-testid="primaryColumn"] { 154 | width: 700px !important; 155 | max-width: 700px !important; 156 | margin: 0 auto !important; 157 | } 158 | #react-root [data-testid="primaryColumn"] > div > div:last-child, 159 | #react-root [data-testid="primaryColumn"] > div > div:last-child div { 160 | max-width: unset !important; 161 | } 162 | 163 | #react-root div[aria-label][role="group"][id^="id__"] { 164 | margin-right: 81px !important; 165 | } 166 | 167 | #react-root header[role="banner"] { 168 | position: fixed !important; 169 | left: 0 !important; 170 | } 171 | 172 | #react-root header[role="banner"] > div > div > div { 173 | justify-content: center !important; 174 | padding-top: 0; 175 | } 176 | 177 | #react-root form[role="search"] > div:nth-child(1) > div { 178 | background-color: transparent !important; 179 | } 180 | 181 | #react-root h1[role="heading"] { 182 | padding-top: 4px !important; 183 | } 184 | 185 | #react-root header[role="banner"] 186 | nav[role="navigation"] 187 | * 188 | div[dir="auto"]:not([aria-label]) 189 | > span, 190 | #react-root [data-testid="SideNav_AccountSwitcher_Button"] > div:not(:first-child) { 191 | display: inline-block !important; 192 | opacity: 0 !important; 193 | transition: 0.5s cubic-bezier(0.2, 0.8, 0.2, 1); 194 | } 195 | #react-root header[role="banner"] 196 | nav[role="navigation"]:hover 197 | * 198 | div[dir="auto"]:not([aria-label]) 199 | > span, 200 | #react-root [data-testid="SideNav_AccountSwitcher_Button"]:hover > div:not(:first-child) { 201 | opacity: 1 !important; 202 | } 203 | #react-root header[role="banner"] nav[role="navigation"]:hover > * > div { 204 | backdrop-filter: blur(12px) !important; 205 | } 206 | #react-root header[role="banner"] nav[role="navigation"] > a { 207 | position: relative; 208 | } 209 | 210 | #react-root header[role="banner"] nav[role="navigation"] > a::before { 211 | content: ""; 212 | position: absolute; 213 | top: 0px; 214 | right: -40px; 215 | bottom: 0px; 216 | left: 0px; 217 | } 218 | #react-root [data-testid="SideNav_AccountSwitcher_Button"] { 219 | bottom: 18px !important; 220 | left: 1px !important; 221 | } 222 | 223 | #react-root [data-testid="SideNav_NewTweet_Button"], #react-root [aria-label="Twitter Blue"]{ 224 | display: none; 225 | } 226 | } 227 | 228 | @media only screen and (min-width: 1265px) { 229 | #react-root [data-testid="sidebarColumn"] form[role="search"] { 230 | visibility: visible !important; 231 | position: fixed !important; 232 | top: 12px !important; 233 | right: 16px !important; 234 | } 235 | 236 | #react-root [data-testid="sidebarColumn"] input[placeholder="Search Twitter"] { 237 | width: 150px; 238 | } 239 | 240 | #react-root [data-testid="sidebarColumn"] form[role="search"]:focus-within { 241 | width: 374px !important; 242 | backdrop-filter: blur(12px) !important; 243 | } 244 | 245 | #react-root [data-testid="sidebarColumn"] input[placeholder="Search Twitter"]:focus { 246 | width: 328px !important; 247 | } 248 | 249 | #react-root div[style*="left: -12px"] { 250 | left: unset !important; 251 | } 252 | 253 | #react-root div[style="left: -8px; width: 306px;"] { 254 | left: unset !important; 255 | width: 374px !important; 256 | } 257 | 258 | #react-root .searchFilters { 259 | visibility: visible !important; 260 | position: fixed; 261 | top: 12px; 262 | right: 16px; 263 | width: 240px; 264 | } 265 | #react-root .searchFilters > div > div:first-child { 266 | display: none; 267 | } 268 | } 269 | 270 | #pack-top-dom:active { 271 | cursor: grabbing; 272 | cursor: -webkit-grabbing; 273 | } 274 | 275 | #pack-top-dom{ 276 | position:fixed; 277 | background:transparent; 278 | top:0; 279 | width: 100%; 280 | height: 20px; 281 | cursor: grab; 282 | cursor: -webkit-grab; 283 | z-index: 90000; 284 | } 285 | `; 286 | document.head.append(style); 287 | const topDom = document.createElement("div"); 288 | topDom.id = "pack-top-dom"; 289 | document.body.appendChild(topDom); 290 | 291 | const domEl = document.getElementById("pack-top-dom"); 292 | 293 | domEl.addEventListener("mousedown", (e) => { 294 | if (e.buttons === 1 && e.detail !== 2) { 295 | window.ipc.postMessage("drag_window"); 296 | } 297 | }); 298 | 299 | domEl.addEventListener("touchstart", () => { 300 | window.ipc.postMessage("drag_window"); 301 | }); 302 | 303 | domEl.addEventListener("dblclick", () => { 304 | window.ipc.postMessage("fullscreen"); 305 | }); 306 | 307 | document.addEventListener("keyup", function (event) { 308 | const preventDefault = (f) => { 309 | event.preventDefault(); 310 | f(); 311 | }; 312 | if (/windows|linux/i.test(navigator.userAgent)) { 313 | if (event.ctrlKey && event.key in ctrlKeyShortcuts) { 314 | preventDefault(ctrlKeyShortcuts[event.key]); 315 | } 316 | } 317 | if (/macintosh|mac os x/i.test(navigator.userAgent)) { 318 | if (event.metaKey && event.key in metaKeyShortcuts) { 319 | preventDefault(metaKeyShortcuts[event.key]); 320 | } 321 | } 322 | }); 323 | 324 | document.addEventListener("click", (e) => { 325 | const origin = e.target.closest("a"); 326 | if (origin && origin.href) { 327 | const target = origin.target 328 | origin.target = "_self"; 329 | const hrefUrl = new URL(origin.href) 330 | 331 | if ( 332 | window.location.host !== hrefUrl.host && // 如果 a 标签内链接的域名和当前页面的域名不一致 且 333 | target === '_blank' // a 标签内链接的 target 属性为 _blank 时 334 | ) { 335 | e.preventDefault(); 336 | window.ipc.postMessage(`open_browser:${origin.href}`); 337 | } 338 | } 339 | }); 340 | }); 341 | 342 | setDefaultZoom(); 343 | 344 | function setDefaultZoom() { 345 | const htmlZoom = window.localStorage.getItem("htmlZoom"); 346 | if (htmlZoom) { 347 | document.getElementsByTagName("html")[0].style.zoom = htmlZoom; 348 | } 349 | } 350 | 351 | /** 352 | * @param {(htmlZoom: string) => string} [zoomRule] 353 | */ 354 | function zoomCommon(zoomRule) { 355 | const htmlZoom = window.localStorage.getItem("htmlZoom") || "100%"; 356 | const html = document.getElementsByTagName("html")[0]; 357 | const zoom = zoomRule(htmlZoom); 358 | html.style.zoom = zoom; 359 | window.localStorage.setItem("htmlZoom", zoom); 360 | } 361 | 362 | function zoomIn() { 363 | zoomCommon((htmlZoom) => `${Math.min(parseInt(htmlZoom) + 10, 200)}%`); 364 | } 365 | 366 | function zoomOut() { 367 | zoomCommon((htmlZoom) => `${Math.max(parseInt(htmlZoom) - 10, 30)}%`); 368 | } 369 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

中文 | English

2 |

3 | 4 |

5 |

Pake

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

131 | 132 | **Pake 提供了命令行工具,可以更快捷方便地一键自定义打你需要的包,详细可见 [文档](./bin/README.md)。** 133 | 134 | ```bash 135 | // 使用 npm 进行安装 136 | npm install -g pake-cli 137 | 138 | // 命令使用 139 | pake url [options] 140 | 141 | // 随便玩玩,首次由于安装环境会有些慢,后面就快了 142 | pake https://weekly.tw93.fun --name Weekly --transparent 143 | ``` 144 | 145 | 假如你不太会使用命令行,或许使用 **GitHub Actions 在线编译多系统版本** 是一个不错的选择,可查看 [文档](https://github.com/tw93/Pake/wiki/GitHub-Actions-%E5%9C%A8%E7%BA%BF%E7%BC%96%E8%AF%91%E5%A4%9A%E7%B3%BB%E7%BB%9F%E7%89%88%E6%9C%AC)。 146 | 147 | ## 定制开发 148 | 149 | 开始前请确保电脑已经安装了 Rust 和 Node 的环境,此外需参考 [Tauri 文档](https://tauri.app/v1/guides/getting-started/prerequisites) 快速配置好环境才可以开始使用,假如你太不懂,使用上面的命令行打包会更加合适。 150 | 151 | ```sh 152 | // 安装依赖 153 | npm i 154 | 155 | // 本地开发 156 | npm run dev 157 | 158 | // 本地调试 159 | npm run dev:debug 160 | 161 | // 打包应用 162 | npm run build 163 | 164 | ``` 165 | 166 | ## 高级使用 167 | 168 | 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),便于你在开发前了解更多。 169 | 2. 修改 src-tauri 目录下的 `tauri.conf.json`以及 `tauri.xxx.conf.json` 中的 url、productName、icon、identifier 这 4 个字段,其中 icon 可以从 icons 目录选择一个,也可以去 [macOSicons](https://macosicons.com/#/) 下载符合效果的。 170 | 3. 关于窗口属性设置,可以在 `tauri.conf.json` 修改 windows 属性对应的 `width/height`,fullscreen 是否全屏,resizable 是否可以调整大小,假如想适配 Mac 沉浸式头部,可以将 transparent 设置成 `true`,找到 Header 元素加一个 padding-top 样式即可,不想适配改成 `false` 也行。 171 | 4. 此外样式改写、屏蔽广告、逻辑代码注入、容器消息通信、自定义快捷键可见 [高级用法](https://github.com/tw93/Pake/wiki/Pake-%E7%9A%84%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95)。 172 | 173 | ## 开发者 174 | 175 | Pake 的发展离不开这些 Hacker 们,一起贡献了大量能力,也欢迎关注他们 ❤️ 176 | 177 | 178 | 179 | 180 | 187 | 194 | 201 | 208 | 215 | 222 | 229 | 230 | 237 | 244 | 251 | 258 | 265 | 272 | 279 | 280 | 287 | 294 | 301 |
181 | 182 | tw93 183 |
184 | Tw93 185 |
186 |
188 | 189 | Tlntin 190 |
191 | Tlntin 192 |
193 |
195 | 196 | pan93412 197 |
198 | Pan93412 199 |
200 |
202 | 203 | wanghanzhen 204 |
205 | Volare 206 |
207 |
209 | 210 | liby 211 |
212 | Bryan Lee 213 |
214 |
216 | 217 | essesoul 218 |
219 | Essesoul 220 |
221 |
223 | 224 | m1911star 225 |
226 | Horus 227 |
228 |
231 | 232 | AielloChan 233 |
234 | Aiello 235 |
236 |
238 | 239 | QingZ11 240 |
241 | Steam 242 |
243 |
245 | 246 | 2nthony 247 |
248 | 2nthony 249 |
250 |
252 | 253 | nekomeowww 254 |
255 | Ayaka Neko 256 |
257 |
259 | 260 | turkyden 261 |
262 | Dengju Deng 263 |
264 |
266 | 267 | Fechin 268 |
269 | Fechin 270 |
271 |
273 | 274 | princemaple 275 |
276 | Po Chen 277 |
278 |
281 | 282 | houhoz 283 |
284 | Hyzhao 285 |
286 |
288 | 289 | liusishan 290 |
291 | Liusishan 292 |
293 |
295 | 296 | piaoyidage 297 |
298 | Ranger 299 |
300 |
302 | 303 | 304 | ## 支持 305 | 306 | 1. 我有两只猫,一只叫汤圆,一只叫可乐,假如觉得 Pake 让你生活更美好,可以给汤圆可乐 喂罐头 🥩🍤。 307 | 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~) 给你志同道合的朋友使用。 308 | 3. 可以关注我的 [Twitter](https://twitter.com/HiTw93) 获取到最新的 Pake 更新消息,也欢迎加入 [Telegram](https://t.me/miaoyan) 聊天群。 309 | 4. 希望大伙玩的过程中有一种学习新技术的喜悦感,假如你发现有很适合做成桌面 App 的网页也很欢迎告诉我。 310 | -------------------------------------------------------------------------------- /bin/utils/tlds.ts: -------------------------------------------------------------------------------- 1 | const tlds = [ 2 | "aaa", 3 | "aarp", 4 | "abarth", 5 | "abb", 6 | "abbott", 7 | "abbvie", 8 | "abc", 9 | "able", 10 | "abogado", 11 | "abudhabi", 12 | "ac", 13 | "academy", 14 | "accenture", 15 | "accountant", 16 | "accountants", 17 | "aco", 18 | "actor", 19 | "ad", 20 | "adac", 21 | "ads", 22 | "adult", 23 | "ae", 24 | "aeg", 25 | "aero", 26 | "aetna", 27 | "af", 28 | "afl", 29 | "africa", 30 | "ag", 31 | "agakhan", 32 | "agency", 33 | "ai", 34 | "aig", 35 | "airbus", 36 | "airforce", 37 | "airtel", 38 | "akdn", 39 | "al", 40 | "alfaromeo", 41 | "alibaba", 42 | "alipay", 43 | "allfinanz", 44 | "allstate", 45 | "ally", 46 | "alsace", 47 | "alstom", 48 | "am", 49 | "amazon", 50 | "americanexpress", 51 | "americanfamily", 52 | "amex", 53 | "amfam", 54 | "amica", 55 | "amsterdam", 56 | "analytics", 57 | "android", 58 | "anquan", 59 | "anz", 60 | "ao", 61 | "aol", 62 | "apartments", 63 | "app", 64 | "apple", 65 | "aq", 66 | "aquarelle", 67 | "ar", 68 | "arab", 69 | "aramco", 70 | "archi", 71 | "army", 72 | "arpa", 73 | "art", 74 | "arte", 75 | "as", 76 | "asda", 77 | "asia", 78 | "associates", 79 | "at", 80 | "athleta", 81 | "attorney", 82 | "au", 83 | "auction", 84 | "audi", 85 | "audible", 86 | "audio", 87 | "auspost", 88 | "author", 89 | "auto", 90 | "autos", 91 | "avianca", 92 | "aw", 93 | "aws", 94 | "ax", 95 | "axa", 96 | "az", 97 | "azure", 98 | "ba", 99 | "baby", 100 | "baidu", 101 | "banamex", 102 | "bananarepublic", 103 | "band", 104 | "bank", 105 | "bar", 106 | "barcelona", 107 | "barclaycard", 108 | "barclays", 109 | "barefoot", 110 | "bargains", 111 | "baseball", 112 | "basketball", 113 | "bauhaus", 114 | "bayern", 115 | "bb", 116 | "bbc", 117 | "bbt", 118 | "bbva", 119 | "bcg", 120 | "bcn", 121 | "bd", 122 | "be", 123 | "beats", 124 | "beauty", 125 | "beer", 126 | "bentley", 127 | "berlin", 128 | "best", 129 | "bestbuy", 130 | "bet", 131 | "bf", 132 | "bg", 133 | "bh", 134 | "bharti", 135 | "bi", 136 | "bible", 137 | "bid", 138 | "bike", 139 | "bing", 140 | "bingo", 141 | "bio", 142 | "biz", 143 | "bj", 144 | "black", 145 | "blackfriday", 146 | "blockbuster", 147 | "blog", 148 | "bloomberg", 149 | "blue", 150 | "bm", 151 | "bms", 152 | "bmw", 153 | "bn", 154 | "bnpparibas", 155 | "bo", 156 | "boats", 157 | "boehringer", 158 | "bofa", 159 | "bom", 160 | "bond", 161 | "boo", 162 | "book", 163 | "booking", 164 | "bosch", 165 | "bostik", 166 | "boston", 167 | "bot", 168 | "boutique", 169 | "box", 170 | "br", 171 | "bradesco", 172 | "bridgestone", 173 | "broadway", 174 | "broker", 175 | "brother", 176 | "brussels", 177 | "bs", 178 | "bt", 179 | "build", 180 | "builders", 181 | "business", 182 | "buy", 183 | "buzz", 184 | "bv", 185 | "bw", 186 | "by", 187 | "bz", 188 | "bzh", 189 | "ca", 190 | "cab", 191 | "cafe", 192 | "cal", 193 | "call", 194 | "calvinklein", 195 | "cam", 196 | "camera", 197 | "camp", 198 | "canon", 199 | "capetown", 200 | "capital", 201 | "capitalone", 202 | "car", 203 | "caravan", 204 | "cards", 205 | "care", 206 | "career", 207 | "careers", 208 | "cars", 209 | "casa", 210 | "case", 211 | "cash", 212 | "casino", 213 | "cat", 214 | "catering", 215 | "catholic", 216 | "cba", 217 | "cbn", 218 | "cbre", 219 | "cbs", 220 | "cc", 221 | "cd", 222 | "center", 223 | "ceo", 224 | "cern", 225 | "cf", 226 | "cfa", 227 | "cfd", 228 | "cg", 229 | "ch", 230 | "chanel", 231 | "channel", 232 | "charity", 233 | "chase", 234 | "chat", 235 | "cheap", 236 | "chintai", 237 | "christmas", 238 | "chrome", 239 | "church", 240 | "ci", 241 | "cipriani", 242 | "circle", 243 | "cisco", 244 | "citadel", 245 | "citi", 246 | "citic", 247 | "city", 248 | "cityeats", 249 | "ck", 250 | "cl", 251 | "claims", 252 | "cleaning", 253 | "click", 254 | "clinic", 255 | "clinique", 256 | "clothing", 257 | "cloud", 258 | "club", 259 | "clubmed", 260 | "cm", 261 | "cn", 262 | "co", 263 | "coach", 264 | "codes", 265 | "coffee", 266 | "college", 267 | "cologne", 268 | "com", 269 | "comcast", 270 | "commbank", 271 | "community", 272 | "company", 273 | "compare", 274 | "computer", 275 | "comsec", 276 | "condos", 277 | "construction", 278 | "consulting", 279 | "contact", 280 | "contractors", 281 | "cooking", 282 | "cookingchannel", 283 | "cool", 284 | "coop", 285 | "corsica", 286 | "country", 287 | "coupon", 288 | "coupons", 289 | "courses", 290 | "cpa", 291 | "cr", 292 | "credit", 293 | "creditcard", 294 | "creditunion", 295 | "cricket", 296 | "crown", 297 | "crs", 298 | "cruise", 299 | "cruises", 300 | "cu", 301 | "cuisinella", 302 | "cv", 303 | "cw", 304 | "cx", 305 | "cy", 306 | "cymru", 307 | "cyou", 308 | "cz", 309 | "dabur", 310 | "dad", 311 | "dance", 312 | "data", 313 | "date", 314 | "dating", 315 | "datsun", 316 | "day", 317 | "dclk", 318 | "dds", 319 | "de", 320 | "deal", 321 | "dealer", 322 | "deals", 323 | "degree", 324 | "delivery", 325 | "dell", 326 | "deloitte", 327 | "delta", 328 | "democrat", 329 | "dental", 330 | "dentist", 331 | "desi", 332 | "design", 333 | "dev", 334 | "dhl", 335 | "diamonds", 336 | "diet", 337 | "digital", 338 | "direct", 339 | "directory", 340 | "discount", 341 | "discover", 342 | "dish", 343 | "diy", 344 | "dj", 345 | "dk", 346 | "dm", 347 | "dnp", 348 | "do", 349 | "docs", 350 | "doctor", 351 | "dog", 352 | "domains", 353 | "dot", 354 | "download", 355 | "drive", 356 | "dtv", 357 | "dubai", 358 | "dunlop", 359 | "dupont", 360 | "durban", 361 | "dvag", 362 | "dvr", 363 | "dz", 364 | "earth", 365 | "eat", 366 | "ec", 367 | "eco", 368 | "edeka", 369 | "edu", 370 | "education", 371 | "ee", 372 | "eg", 373 | "email", 374 | "emerck", 375 | "energy", 376 | "engineer", 377 | "engineering", 378 | "enterprises", 379 | "epson", 380 | "equipment", 381 | "er", 382 | "ericsson", 383 | "erni", 384 | "es", 385 | "esq", 386 | "estate", 387 | "et", 388 | "etisalat", 389 | "eu", 390 | "eurovision", 391 | "eus", 392 | "events", 393 | "exchange", 394 | "expert", 395 | "exposed", 396 | "express", 397 | "extraspace", 398 | "fage", 399 | "fail", 400 | "fairwinds", 401 | "faith", 402 | "family", 403 | "fan", 404 | "fans", 405 | "farm", 406 | "farmers", 407 | "fashion", 408 | "fast", 409 | "fedex", 410 | "feedback", 411 | "ferrari", 412 | "ferrero", 413 | "fi", 414 | "fiat", 415 | "fidelity", 416 | "fido", 417 | "film", 418 | "final", 419 | "finance", 420 | "financial", 421 | "fire", 422 | "firestone", 423 | "firmdale", 424 | "fish", 425 | "fishing", 426 | "fit", 427 | "fitness", 428 | "fj", 429 | "fk", 430 | "flickr", 431 | "flights", 432 | "flir", 433 | "florist", 434 | "flowers", 435 | "fly", 436 | "fm", 437 | "fo", 438 | "foo", 439 | "food", 440 | "foodnetwork", 441 | "football", 442 | "ford", 443 | "forex", 444 | "forsale", 445 | "forum", 446 | "foundation", 447 | "fox", 448 | "fr", 449 | "free", 450 | "fresenius", 451 | "frl", 452 | "frogans", 453 | "frontdoor", 454 | "frontier", 455 | "ftr", 456 | "fujitsu", 457 | "fun", 458 | "fund", 459 | "furniture", 460 | "futbol", 461 | "fyi", 462 | "ga", 463 | "gal", 464 | "gallery", 465 | "gallo", 466 | "gallup", 467 | "game", 468 | "games", 469 | "gap", 470 | "garden", 471 | "gay", 472 | "gb", 473 | "gbiz", 474 | "gd", 475 | "gdn", 476 | "ge", 477 | "gea", 478 | "gent", 479 | "genting", 480 | "george", 481 | "gf", 482 | "gg", 483 | "ggee", 484 | "gh", 485 | "gi", 486 | "gift", 487 | "gifts", 488 | "gives", 489 | "giving", 490 | "gl", 491 | "glass", 492 | "gle", 493 | "global", 494 | "globo", 495 | "gm", 496 | "gmail", 497 | "gmbh", 498 | "gmo", 499 | "gmx", 500 | "gn", 501 | "godaddy", 502 | "gold", 503 | "goldpoint", 504 | "golf", 505 | "goo", 506 | "goodyear", 507 | "goog", 508 | "google", 509 | "gop", 510 | "got", 511 | "gov", 512 | "gp", 513 | "gq", 514 | "gr", 515 | "grainger", 516 | "graphics", 517 | "gratis", 518 | "green", 519 | "gripe", 520 | "grocery", 521 | "group", 522 | "gs", 523 | "gt", 524 | "gu", 525 | "guardian", 526 | "gucci", 527 | "guge", 528 | "guide", 529 | "guitars", 530 | "guru", 531 | "gw", 532 | "gy", 533 | "hair", 534 | "hamburg", 535 | "hangout", 536 | "haus", 537 | "hbo", 538 | "hdfc", 539 | "hdfcbank", 540 | "health", 541 | "healthcare", 542 | "help", 543 | "helsinki", 544 | "here", 545 | "hermes", 546 | "hgtv", 547 | "hiphop", 548 | "hisamitsu", 549 | "hitachi", 550 | "hiv", 551 | "hk", 552 | "hkt", 553 | "hm", 554 | "hn", 555 | "hockey", 556 | "holdings", 557 | "holiday", 558 | "homedepot", 559 | "homegoods", 560 | "homes", 561 | "homesense", 562 | "honda", 563 | "horse", 564 | "hospital", 565 | "host", 566 | "hosting", 567 | "hot", 568 | "hoteles", 569 | "hotels", 570 | "hotmail", 571 | "house", 572 | "how", 573 | "hr", 574 | "hsbc", 575 | "ht", 576 | "hu", 577 | "hughes", 578 | "hyatt", 579 | "hyundai", 580 | "ibm", 581 | "icbc", 582 | "ice", 583 | "icu", 584 | "id", 585 | "ie", 586 | "ieee", 587 | "ifm", 588 | "ikano", 589 | "il", 590 | "im", 591 | "imamat", 592 | "imdb", 593 | "immo", 594 | "immobilien", 595 | "in", 596 | "inc", 597 | "industries", 598 | "infiniti", 599 | "info", 600 | "ing", 601 | "ink", 602 | "institute", 603 | "insurance", 604 | "insure", 605 | "int", 606 | "international", 607 | "intuit", 608 | "investments", 609 | "io", 610 | "ipiranga", 611 | "iq", 612 | "ir", 613 | "irish", 614 | "is", 615 | "ismaili", 616 | "ist", 617 | "istanbul", 618 | "it", 619 | "itau", 620 | "itv", 621 | "jaguar", 622 | "java", 623 | "jcb", 624 | "je", 625 | "jeep", 626 | "jetzt", 627 | "jewelry", 628 | "jio", 629 | "jll", 630 | "jm", 631 | "jmp", 632 | "jnj", 633 | "jo", 634 | "jobs", 635 | "joburg", 636 | "jot", 637 | "joy", 638 | "jp", 639 | "jpmorgan", 640 | "jprs", 641 | "juegos", 642 | "juniper", 643 | "kaufen", 644 | "kddi", 645 | "ke", 646 | "kerryhotels", 647 | "kerrylogistics", 648 | "kerryproperties", 649 | "kfh", 650 | "kg", 651 | "kh", 652 | "ki", 653 | "kia", 654 | "kids", 655 | "kim", 656 | "kinder", 657 | "kindle", 658 | "kitchen", 659 | "kiwi", 660 | "km", 661 | "kn", 662 | "koeln", 663 | "komatsu", 664 | "kosher", 665 | "kp", 666 | "kpmg", 667 | "kpn", 668 | "kr", 669 | "krd", 670 | "kred", 671 | "kuokgroup", 672 | "kw", 673 | "ky", 674 | "kyoto", 675 | "kz", 676 | "la", 677 | "lacaixa", 678 | "lamborghini", 679 | "lamer", 680 | "lancaster", 681 | "lancia", 682 | "land", 683 | "landrover", 684 | "lanxess", 685 | "lasalle", 686 | "lat", 687 | "latino", 688 | "latrobe", 689 | "law", 690 | "lawyer", 691 | "lb", 692 | "lc", 693 | "lds", 694 | "lease", 695 | "leclerc", 696 | "lefrak", 697 | "legal", 698 | "lego", 699 | "lexus", 700 | "lgbt", 701 | "li", 702 | "lidl", 703 | "life", 704 | "lifeinsurance", 705 | "lifestyle", 706 | "lighting", 707 | "like", 708 | "lilly", 709 | "limited", 710 | "limo", 711 | "lincoln", 712 | "linde", 713 | "link", 714 | "lipsy", 715 | "live", 716 | "living", 717 | "lk", 718 | "llc", 719 | "llp", 720 | "loan", 721 | "loans", 722 | "locker", 723 | "locus", 724 | "loft", 725 | "lol", 726 | "london", 727 | "lotte", 728 | "lotto", 729 | "love", 730 | "lpl", 731 | "lplfinancial", 732 | "lr", 733 | "ls", 734 | "lt", 735 | "ltd", 736 | "ltda", 737 | "lu", 738 | "lundbeck", 739 | "luxe", 740 | "luxury", 741 | "lv", 742 | "ly", 743 | "ma", 744 | "macys", 745 | "madrid", 746 | "maif", 747 | "maison", 748 | "makeup", 749 | "man", 750 | "management", 751 | "mango", 752 | "map", 753 | "market", 754 | "marketing", 755 | "markets", 756 | "marriott", 757 | "marshalls", 758 | "maserati", 759 | "mattel", 760 | "mba", 761 | "mc", 762 | "mckinsey", 763 | "md", 764 | "me", 765 | "med", 766 | "media", 767 | "meet", 768 | "melbourne", 769 | "meme", 770 | "memorial", 771 | "men", 772 | "menu", 773 | "merckmsd", 774 | "mg", 775 | "mh", 776 | "miami", 777 | "microsoft", 778 | "mil", 779 | "mini", 780 | "mint", 781 | "mit", 782 | "mitsubishi", 783 | "mk", 784 | "ml", 785 | "mlb", 786 | "mls", 787 | "mm", 788 | "mma", 789 | "mn", 790 | "mo", 791 | "mobi", 792 | "mobile", 793 | "moda", 794 | "moe", 795 | "moi", 796 | "mom", 797 | "monash", 798 | "money", 799 | "monster", 800 | "mormon", 801 | "mortgage", 802 | "moscow", 803 | "moto", 804 | "motorcycles", 805 | "mov", 806 | "movie", 807 | "mp", 808 | "mq", 809 | "mr", 810 | "ms", 811 | "msd", 812 | "mt", 813 | "mtn", 814 | "mtr", 815 | "mu", 816 | "museum", 817 | "music", 818 | "mutual", 819 | "mv", 820 | "mw", 821 | "mx", 822 | "my", 823 | "mz", 824 | "na", 825 | "nab", 826 | "nagoya", 827 | "name", 828 | "natura", 829 | "navy", 830 | "nba", 831 | "nc", 832 | "ne", 833 | "nec", 834 | "net", 835 | "netbank", 836 | "netflix", 837 | "network", 838 | "neustar", 839 | "new", 840 | "news", 841 | "next", 842 | "nextdirect", 843 | "nexus", 844 | "nf", 845 | "nfl", 846 | "ng", 847 | "ngo", 848 | "nhk", 849 | "ni", 850 | "nico", 851 | "nike", 852 | "nikon", 853 | "ninja", 854 | "nissan", 855 | "nissay", 856 | "nl", 857 | "no", 858 | "nokia", 859 | "northwesternmutual", 860 | "norton", 861 | "now", 862 | "nowruz", 863 | "nowtv", 864 | "np", 865 | "nr", 866 | "nra", 867 | "nrw", 868 | "ntt", 869 | "nu", 870 | "nyc", 871 | "nz", 872 | "obi", 873 | "observer", 874 | "office", 875 | "okinawa", 876 | "olayan", 877 | "olayangroup", 878 | "oldnavy", 879 | "ollo", 880 | "om", 881 | "omega", 882 | "one", 883 | "ong", 884 | "onl", 885 | "online", 886 | "ooo", 887 | "open", 888 | "oracle", 889 | "orange", 890 | "org", 891 | "organic", 892 | "origins", 893 | "osaka", 894 | "otsuka", 895 | "ott", 896 | "ovh", 897 | "pa", 898 | "page", 899 | "panasonic", 900 | "paris", 901 | "pars", 902 | "partners", 903 | "parts", 904 | "party", 905 | "passagens", 906 | "pay", 907 | "pccw", 908 | "pe", 909 | "pet", 910 | "pf", 911 | "pfizer", 912 | "pg", 913 | "ph", 914 | "pharmacy", 915 | "phd", 916 | "philips", 917 | "phone", 918 | "photo", 919 | "photography", 920 | "photos", 921 | "physio", 922 | "pics", 923 | "pictet", 924 | "pictures", 925 | "pid", 926 | "pin", 927 | "ping", 928 | "pink", 929 | "pioneer", 930 | "pizza", 931 | "pk", 932 | "pl", 933 | "place", 934 | "play", 935 | "playstation", 936 | "plumbing", 937 | "plus", 938 | "pm", 939 | "pn", 940 | "pnc", 941 | "pohl", 942 | "poker", 943 | "politie", 944 | "porn", 945 | "post", 946 | "pr", 947 | "pramerica", 948 | "praxi", 949 | "press", 950 | "prime", 951 | "pro", 952 | "prod", 953 | "productions", 954 | "prof", 955 | "progressive", 956 | "promo", 957 | "properties", 958 | "property", 959 | "protection", 960 | "pru", 961 | "prudential", 962 | "ps", 963 | "pt", 964 | "pub", 965 | "pw", 966 | "pwc", 967 | "py", 968 | "qa", 969 | "qpon", 970 | "quebec", 971 | "quest", 972 | "racing", 973 | "radio", 974 | "re", 975 | "read", 976 | "realestate", 977 | "realtor", 978 | "realty", 979 | "recipes", 980 | "red", 981 | "redstone", 982 | "redumbrella", 983 | "rehab", 984 | "reise", 985 | "reisen", 986 | "reit", 987 | "reliance", 988 | "ren", 989 | "rent", 990 | "rentals", 991 | "repair", 992 | "report", 993 | "republican", 994 | "rest", 995 | "restaurant", 996 | "review", 997 | "reviews", 998 | "rexroth", 999 | "rich", 1000 | "richardli", 1001 | "ricoh", 1002 | "ril", 1003 | "rio", 1004 | "rip", 1005 | "ro", 1006 | "rocher", 1007 | "rocks", 1008 | "rodeo", 1009 | "rogers", 1010 | "room", 1011 | "rs", 1012 | "rsvp", 1013 | "ru", 1014 | "rugby", 1015 | "ruhr", 1016 | "run", 1017 | "rw", 1018 | "rwe", 1019 | "ryukyu", 1020 | "sa", 1021 | "saarland", 1022 | "safe", 1023 | "safety", 1024 | "sakura", 1025 | "sale", 1026 | "salon", 1027 | "samsclub", 1028 | "samsung", 1029 | "sandvik", 1030 | "sandvikcoromant", 1031 | "sanofi", 1032 | "sap", 1033 | "sarl", 1034 | "sas", 1035 | "save", 1036 | "saxo", 1037 | "sb", 1038 | "sbi", 1039 | "sbs", 1040 | "sc", 1041 | "sca", 1042 | "scb", 1043 | "schaeffler", 1044 | "schmidt", 1045 | "scholarships", 1046 | "school", 1047 | "schule", 1048 | "schwarz", 1049 | "science", 1050 | "scot", 1051 | "sd", 1052 | "se", 1053 | "search", 1054 | "seat", 1055 | "secure", 1056 | "security", 1057 | "seek", 1058 | "select", 1059 | "sener", 1060 | "services", 1061 | "ses", 1062 | "seven", 1063 | "sew", 1064 | "sex", 1065 | "sexy", 1066 | "sfr", 1067 | "sg", 1068 | "sh", 1069 | "shangrila", 1070 | "sharp", 1071 | "shaw", 1072 | "shell", 1073 | "shia", 1074 | "shiksha", 1075 | "shoes", 1076 | "shop", 1077 | "shopping", 1078 | "shouji", 1079 | "show", 1080 | "showtime", 1081 | "si", 1082 | "silk", 1083 | "sina", 1084 | "singles", 1085 | "site", 1086 | "sj", 1087 | "sk", 1088 | "ski", 1089 | "skin", 1090 | "sky", 1091 | "skype", 1092 | "sl", 1093 | "sling", 1094 | "sm", 1095 | "smart", 1096 | "smile", 1097 | "sn", 1098 | "sncf", 1099 | "so", 1100 | "soccer", 1101 | "social", 1102 | "softbank", 1103 | "software", 1104 | "sohu", 1105 | "solar", 1106 | "solutions", 1107 | "song", 1108 | "sony", 1109 | "soy", 1110 | "spa", 1111 | "space", 1112 | "sport", 1113 | "spot", 1114 | "sr", 1115 | "srl", 1116 | "ss", 1117 | "st", 1118 | "stada", 1119 | "staples", 1120 | "star", 1121 | "statebank", 1122 | "statefarm", 1123 | "stc", 1124 | "stcgroup", 1125 | "stockholm", 1126 | "storage", 1127 | "store", 1128 | "stream", 1129 | "studio", 1130 | "study", 1131 | "style", 1132 | "su", 1133 | "sucks", 1134 | "supplies", 1135 | "supply", 1136 | "support", 1137 | "surf", 1138 | "surgery", 1139 | "suzuki", 1140 | "sv", 1141 | "swatch", 1142 | "swiss", 1143 | "sx", 1144 | "sy", 1145 | "sydney", 1146 | "systems", 1147 | "sz", 1148 | "tab", 1149 | "taipei", 1150 | "talk", 1151 | "taobao", 1152 | "target", 1153 | "tatamotors", 1154 | "tatar", 1155 | "tattoo", 1156 | "tax", 1157 | "taxi", 1158 | "tc", 1159 | "tci", 1160 | "td", 1161 | "tdk", 1162 | "team", 1163 | "tech", 1164 | "technology", 1165 | "tel", 1166 | "temasek", 1167 | "tennis", 1168 | "teva", 1169 | "tf", 1170 | "tg", 1171 | "th", 1172 | "thd", 1173 | "theater", 1174 | "theatre", 1175 | "tiaa", 1176 | "tickets", 1177 | "tienda", 1178 | "tiffany", 1179 | "tips", 1180 | "tires", 1181 | "tirol", 1182 | "tj", 1183 | "tjmaxx", 1184 | "tjx", 1185 | "tk", 1186 | "tkmaxx", 1187 | "tl", 1188 | "tm", 1189 | "tmall", 1190 | "tn", 1191 | "to", 1192 | "today", 1193 | "tokyo", 1194 | "tools", 1195 | "top", 1196 | "toray", 1197 | "toshiba", 1198 | "total", 1199 | "tours", 1200 | "town", 1201 | "toyota", 1202 | "toys", 1203 | "tr", 1204 | "trade", 1205 | "trading", 1206 | "training", 1207 | "travel", 1208 | "travelchannel", 1209 | "travelers", 1210 | "travelersinsurance", 1211 | "trust", 1212 | "trv", 1213 | "tt", 1214 | "tube", 1215 | "tui", 1216 | "tunes", 1217 | "tushu", 1218 | "tv", 1219 | "tvs", 1220 | "tw", 1221 | "tz", 1222 | "ua", 1223 | "ubank", 1224 | "ubs", 1225 | "ug", 1226 | "uk", 1227 | "unicom", 1228 | "university", 1229 | "uno", 1230 | "uol", 1231 | "ups", 1232 | "us", 1233 | "uy", 1234 | "uz", 1235 | "va", 1236 | "vacations", 1237 | "vana", 1238 | "vanguard", 1239 | "vc", 1240 | "ve", 1241 | "vegas", 1242 | "ventures", 1243 | "verisign", 1244 | "vermögensberater", 1245 | "vermögensberatung", 1246 | "versicherung", 1247 | "vet", 1248 | "vg", 1249 | "vi", 1250 | "viajes", 1251 | "video", 1252 | "vig", 1253 | "viking", 1254 | "villas", 1255 | "vin", 1256 | "vip", 1257 | "virgin", 1258 | "visa", 1259 | "vision", 1260 | "viva", 1261 | "vivo", 1262 | "vlaanderen", 1263 | "vn", 1264 | "vodka", 1265 | "volkswagen", 1266 | "volvo", 1267 | "vote", 1268 | "voting", 1269 | "voto", 1270 | "voyage", 1271 | "vu", 1272 | "vuelos", 1273 | "wales", 1274 | "walmart", 1275 | "walter", 1276 | "wang", 1277 | "wanggou", 1278 | "watch", 1279 | "watches", 1280 | "weather", 1281 | "weatherchannel", 1282 | "webcam", 1283 | "weber", 1284 | "website", 1285 | "wed", 1286 | "wedding", 1287 | "weibo", 1288 | "weir", 1289 | "wf", 1290 | "whoswho", 1291 | "wien", 1292 | "wiki", 1293 | "williamhill", 1294 | "win", 1295 | "windows", 1296 | "wine", 1297 | "winners", 1298 | "wme", 1299 | "wolterskluwer", 1300 | "woodside", 1301 | "work", 1302 | "works", 1303 | "world", 1304 | "wow", 1305 | "ws", 1306 | "wtc", 1307 | "wtf", 1308 | "xbox", 1309 | "xerox", 1310 | "xfinity", 1311 | "xihuan", 1312 | "xin", 1313 | "xxx", 1314 | "xyz", 1315 | "yachts", 1316 | "yahoo", 1317 | "yamaxun", 1318 | "yandex", 1319 | "ye", 1320 | "yodobashi", 1321 | "yoga", 1322 | "yokohama", 1323 | "you", 1324 | "youtube", 1325 | "yt", 1326 | "yun", 1327 | "za", 1328 | "zappos", 1329 | "zara", 1330 | "zero", 1331 | "zip", 1332 | "zm", 1333 | "zone", 1334 | "zuerich", 1335 | "zw", 1336 | "ελ", 1337 | "ευ", 1338 | "бг", 1339 | "бел", 1340 | "дети", 1341 | "ею", 1342 | "католик", 1343 | "ком", 1344 | "мкд", 1345 | "мон", 1346 | "москва", 1347 | "онлайн", 1348 | "орг", 1349 | "рус", 1350 | "рф", 1351 | "сайт", 1352 | "срб", 1353 | "укр", 1354 | "қаз", 1355 | "հայ", 1356 | "ישראל", 1357 | "קום", 1358 | "ابوظبي", 1359 | "اتصالات", 1360 | "ارامكو", 1361 | "الاردن", 1362 | "البحرين", 1363 | "الجزائر", 1364 | "السعودية", 1365 | "العليان", 1366 | "المغرب", 1367 | "امارات", 1368 | "ایران", 1369 | "بارت", 1370 | "بازار", 1371 | "بيتك", 1372 | "بھارت", 1373 | "تونس", 1374 | "سودان", 1375 | "سورية", 1376 | "شبكة", 1377 | "عراق", 1378 | "عرب", 1379 | "عمان", 1380 | "فلسطين", 1381 | "قطر", 1382 | "كاثوليك", 1383 | "كوم", 1384 | "مصر", 1385 | "مليسيا", 1386 | "موريتانيا", 1387 | "موقع", 1388 | "همراه", 1389 | "پاکستان", 1390 | "ڀارت", 1391 | "कॉम", 1392 | "नेट", 1393 | "भारत", 1394 | "भारतम्", 1395 | "भारोत", 1396 | "संगठन", 1397 | "বাংলা", 1398 | "ভারত", 1399 | "ভাৰত", 1400 | "ਭਾਰਤ", 1401 | "ભારત", 1402 | "ଭାରତ", 1403 | "இந்தியா", 1404 | "இலங்கை", 1405 | "சிங்கப்பூர்", 1406 | "భారత్", 1407 | "ಭಾರತ", 1408 | "ഭാരതം", 1409 | "ලංකා", 1410 | "คอม", 1411 | "ไทย", 1412 | "ລາວ", 1413 | "გე", 1414 | "みんな", 1415 | "アマゾン", 1416 | "クラウド", 1417 | "グーグル", 1418 | "コム", 1419 | "ストア", 1420 | "セール", 1421 | "ファッション", 1422 | "ポイント", 1423 | "世界", 1424 | "中信", 1425 | "中国", 1426 | "中國", 1427 | "中文网", 1428 | "亚马逊", 1429 | "企业", 1430 | "佛山", 1431 | "信息", 1432 | "健康", 1433 | "八卦", 1434 | "公司", 1435 | "公益", 1436 | "台湾", 1437 | "台灣", 1438 | "商城", 1439 | "商店", 1440 | "商标", 1441 | "嘉里", 1442 | "嘉里大酒店", 1443 | "在线", 1444 | "大拿", 1445 | "天主教", 1446 | "娱乐", 1447 | "家電", 1448 | "广东", 1449 | "微博", 1450 | "慈善", 1451 | "我爱你", 1452 | "手机", 1453 | "招聘", 1454 | "政务", 1455 | "政府", 1456 | "新加坡", 1457 | "新闻", 1458 | "时尚", 1459 | "書籍", 1460 | "机构", 1461 | "淡马锡", 1462 | "游戏", 1463 | "澳門", 1464 | "点看", 1465 | "移动", 1466 | "组织机构", 1467 | "网址", 1468 | "网店", 1469 | "网站", 1470 | "网络", 1471 | "联通", 1472 | "诺基亚", 1473 | "谷歌", 1474 | "购物", 1475 | "通販", 1476 | "集团", 1477 | "電訊盈科", 1478 | "飞利浦", 1479 | "食品", 1480 | "餐厅", 1481 | "香格里拉", 1482 | "香港", 1483 | "닷넷", 1484 | "닷컴", 1485 | "삼성", 1486 | "한국", 1487 | ]; 1488 | 1489 | export default tlds; 1490 | --------------------------------------------------------------------------------