├── .editorconfig ├── .erb ├── configs │ ├── webpack.config.base.ts │ ├── webpack.config.eslint.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.renderer.dev.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.paths.ts ├── img │ ├── erb-banner.svg │ └── erb-logo.png ├── mocks │ └── fileMock.js └── scripts │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ └── notarize.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 1-Bug_report.md │ ├── 2-Question.md │ └── 3-Feature_request.md ├── config.yml ├── stale.yml └── workflows │ ├── publish.yml │ └── quality-check.yml ├── .gitignore ├── .husky └── pre-commit ├── LICENSE ├── README.md ├── assets ├── art.txt ├── assets.d.ts ├── entitlements.mac.plist ├── icon.icns ├── icon.ico ├── icon.png └── icons │ ├── 1024x1024.png │ ├── 16x16.png │ ├── 180x180.png │ ├── 192x192.png │ ├── 32x32.png │ ├── 512x512.png │ └── logo.png ├── install.sh ├── package.json ├── release └── app │ └── package.json ├── src ├── __tests__ │ └── App.test.tsx ├── main │ ├── api │ │ └── ipcHandler.ts │ ├── constants │ │ └── index.ts │ ├── main.ts │ ├── menu.ts │ ├── preload.js │ ├── types │ │ └── index.ts │ └── utils │ │ ├── appUpdater.ts │ │ ├── batchInstall.ts │ │ ├── downloadFile.ts │ │ ├── executeCmd.ts │ │ ├── index.ts │ │ ├── installExtensions.ts │ │ └── resolveHtmlPath.ts └── renderer │ ├── App.css │ ├── App.tsx │ ├── api │ └── index.ts │ ├── components │ ├── AccordionDropwdown.tsx │ ├── ApkActions.tsx │ ├── ConnectionActions.tsx │ ├── FixedWidthBtn.tsx │ ├── RevancedActions.tsx │ ├── SystemActions.tsx │ ├── TvActions.tsx │ └── UpdateBtn.tsx │ ├── constants │ └── debloatCommands.ts │ ├── context │ ├── TerminalContext.tsx │ ├── TerminalProvider.tsx │ └── useTerminalContext.tsx │ ├── index.ejs │ ├── index.tsx │ ├── theme.ts │ └── types │ └── index.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | indent_size = 2 13 | 14 | [*.yml] 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import webpackPaths from './webpack.paths'; 7 | import { dependencies as externals } from '../../release/app/package.json'; 8 | 9 | export default { 10 | externals: [...Object.keys(externals || {})], 11 | 12 | stats: 'errors-only', 13 | 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.[jt]sx?$/, 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'ts-loader', 21 | options: { 22 | // Remove this line to enable type checking in webpack builds 23 | transpileOnly: true, 24 | }, 25 | }, 26 | }, 27 | ], 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.srcPath, 32 | // https://github.com/webpack/webpack/issues/1114 33 | library: { 34 | type: 'commonjs2', 35 | }, 36 | }, 37 | 38 | /** 39 | * Determine the array of extensions that should be used to resolve modules. 40 | */ 41 | resolve: { 42 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 43 | modules: [webpackPaths.srcPath, 'node_modules'], 44 | }, 45 | 46 | plugins: [ 47 | new webpack.EnvironmentPlugin({ 48 | NODE_ENV: 'production', 49 | }), 50 | ], 51 | }; 52 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const devtoolsConfig = 19 | process.env.DEBUG_PROD === 'true' 20 | ? { 21 | devtool: 'source-map', 22 | } 23 | : {}; 24 | 25 | export default merge(baseConfig, { 26 | ...devtoolsConfig, 27 | 28 | mode: 'production', 29 | 30 | target: 'electron-main', 31 | 32 | entry: { 33 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 34 | preload: path.join(webpackPaths.srcMainPath, 'preload.js'), 35 | }, 36 | 37 | output: { 38 | path: webpackPaths.distMainPath, 39 | filename: '[name].js', 40 | }, 41 | 42 | optimization: { 43 | minimizer: [ 44 | new TerserPlugin({ 45 | parallel: true, 46 | }), 47 | ], 48 | }, 49 | 50 | plugins: [ 51 | new BundleAnalyzerPlugin({ 52 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 53 | }), 54 | 55 | /** 56 | * Create global constants which can be configured at compile time. 57 | * 58 | * Useful for allowing different behaviour between development builds and 59 | * release builds 60 | * 61 | * NODE_ENV should be production so that modules do not perform certain 62 | * development checks 63 | */ 64 | new webpack.EnvironmentPlugin({ 65 | NODE_ENV: 'production', 66 | DEBUG_PROD: false, 67 | START_MINIMIZED: false, 68 | }), 69 | ], 70 | 71 | /** 72 | * Disables webpack processing of __dirname and __filename. 73 | * If you run the bundle in node.js it falls back to these values of node.js. 74 | * https://github.com/webpack/webpack/issues/2010 75 | */ 76 | node: { 77 | __dirname: false, 78 | __filename: false, 79 | }, 80 | }); 81 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | export default merge(baseConfig, { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }); 76 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import webpack from 'webpack'; 4 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 5 | import chalk from 'chalk'; 6 | import { merge } from 'webpack-merge'; 7 | import { spawn, execSync } from 'child_process'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import checkNodeEnv from '../scripts/check-node-env'; 11 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; 12 | 13 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 14 | // at the dev webpack config is not accidentally run in a production environment 15 | if (process.env.NODE_ENV === 'production') { 16 | checkNodeEnv('development'); 17 | } 18 | 19 | const port = process.env.PORT || 1212; 20 | const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json'); 21 | const requiredByDLLConfig = module.parent.filename.includes( 22 | 'webpack.config.renderer.dev.dll' 23 | ); 24 | 25 | /** 26 | * Warn if the DLL is not built 27 | */ 28 | if ( 29 | !requiredByDLLConfig && 30 | !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest)) 31 | ) { 32 | console.log( 33 | chalk.black.bgYellow.bold( 34 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 35 | ) 36 | ); 37 | execSync('npm run postinstall'); 38 | } 39 | 40 | export default merge(baseConfig, { 41 | devtool: 'inline-source-map', 42 | 43 | mode: 'development', 44 | 45 | target: ['web', 'electron-renderer'], 46 | 47 | entry: [ 48 | `webpack-dev-server/client?http://localhost:${port}/dist`, 49 | 'webpack/hot/only-dev-server', 50 | 'core-js', 51 | 'regenerator-runtime/runtime', 52 | path.join(webpackPaths.srcRendererPath, 'index.tsx'), 53 | ], 54 | 55 | output: { 56 | path: webpackPaths.distRendererPath, 57 | publicPath: '/', 58 | filename: 'renderer.dev.js', 59 | library: { 60 | type: 'umd', 61 | }, 62 | }, 63 | 64 | module: { 65 | rules: [ 66 | { 67 | test: /\.s?css$/, 68 | use: [ 69 | 'style-loader', 70 | { 71 | loader: 'css-loader', 72 | options: { 73 | modules: true, 74 | sourceMap: true, 75 | importLoaders: 1, 76 | }, 77 | }, 78 | 'sass-loader', 79 | ], 80 | include: /\.module\.s?(c|a)ss$/, 81 | }, 82 | { 83 | test: /\.s?css$/, 84 | use: ['style-loader', 'css-loader', 'sass-loader'], 85 | exclude: /\.module\.s?(c|a)ss$/, 86 | }, 87 | // Fonts 88 | { 89 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 90 | type: 'asset/resource', 91 | }, 92 | // Images 93 | { 94 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 95 | type: 'asset/resource', 96 | }, 97 | ], 98 | }, 99 | plugins: [ 100 | requiredByDLLConfig 101 | ? null 102 | : new webpack.DllReferencePlugin({ 103 | context: webpackPaths.dllPath, 104 | manifest: require(manifest), 105 | sourceType: 'var', 106 | }), 107 | 108 | new webpack.NoEmitOnErrorsPlugin(), 109 | 110 | /** 111 | * Create global constants which can be configured at compile time. 112 | * 113 | * Useful for allowing different behaviour between development builds and 114 | * release builds 115 | * 116 | * NODE_ENV should be production so that modules do not perform certain 117 | * development checks 118 | * 119 | * By default, use 'development' as NODE_ENV. This can be overriden with 120 | * 'staging', for example, by changing the ENV variables in the npm scripts 121 | */ 122 | new webpack.EnvironmentPlugin({ 123 | NODE_ENV: 'development', 124 | }), 125 | 126 | new webpack.LoaderOptionsPlugin({ 127 | debug: true, 128 | }), 129 | 130 | new ReactRefreshWebpackPlugin(), 131 | 132 | new HtmlWebpackPlugin({ 133 | filename: path.join('index.html'), 134 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 135 | minify: { 136 | collapseWhitespace: true, 137 | removeAttributeQuotes: true, 138 | removeComments: true, 139 | }, 140 | isBrowser: false, 141 | env: process.env.NODE_ENV, 142 | isDevelopment: process.env.NODE_ENV !== 'production', 143 | nodeModules: webpackPaths.appNodeModulesPath, 144 | }), 145 | ], 146 | 147 | node: { 148 | __dirname: false, 149 | __filename: false, 150 | }, 151 | 152 | devServer: { 153 | port, 154 | compress: true, 155 | hot: true, 156 | headers: { 'Access-Control-Allow-Origin': '*' }, 157 | static: { 158 | publicPath: '/', 159 | }, 160 | client: { 161 | logging: 'none' 162 | }, 163 | historyApiFallback: { 164 | verbose: true, 165 | disableDotRule: false, 166 | }, 167 | onBeforeSetupMiddleware() { 168 | console.log('Starting Main Process...'); 169 | spawn('npm', ['run', 'start:main'], { 170 | shell: true, 171 | env: process.env, 172 | stdio: 'inherit', 173 | }) 174 | .on('close', (code) => process.exit(code)) 175 | .on('error', (spawnError) => console.error(spawnError)); 176 | }, 177 | }, 178 | }); 179 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; 11 | import { merge } from 'webpack-merge'; 12 | import TerserPlugin from 'terser-webpack-plugin'; 13 | import baseConfig from './webpack.config.base'; 14 | import webpackPaths from './webpack.paths'; 15 | import checkNodeEnv from '../scripts/check-node-env'; 16 | import deleteSourceMaps from '../scripts/delete-source-maps'; 17 | 18 | checkNodeEnv('production'); 19 | deleteSourceMaps(); 20 | 21 | const devtoolsConfig = 22 | process.env.DEBUG_PROD === 'true' 23 | ? { 24 | devtool: 'source-map', 25 | } 26 | : {}; 27 | 28 | export default merge(baseConfig, { 29 | ...devtoolsConfig, 30 | 31 | mode: 'production', 32 | 33 | target: ['web', 'electron-renderer'], 34 | 35 | entry: [ 36 | 'core-js', 37 | 'regenerator-runtime/runtime', 38 | path.join(webpackPaths.srcRendererPath, 'index.tsx'), 39 | ], 40 | 41 | output: { 42 | path: webpackPaths.distRendererPath, 43 | publicPath: './', 44 | filename: 'renderer.js', 45 | library: { 46 | type: 'umd', 47 | }, 48 | }, 49 | 50 | module: { 51 | rules: [ 52 | { 53 | test: /\.s?(a|c)ss$/, 54 | use: [ 55 | MiniCssExtractPlugin.loader, 56 | { 57 | loader: 'css-loader', 58 | options: { 59 | modules: true, 60 | sourceMap: true, 61 | importLoaders: 1, 62 | }, 63 | }, 64 | 'sass-loader', 65 | ], 66 | include: /\.module\.s?(c|a)ss$/, 67 | }, 68 | { 69 | test: /\.s?(a|c)ss$/, 70 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 71 | exclude: /\.module\.s?(c|a)ss$/, 72 | }, 73 | // Fonts 74 | { 75 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 76 | type: 'asset/resource', 77 | }, 78 | // Images 79 | { 80 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 81 | type: 'asset/resource', 82 | }, 83 | ], 84 | }, 85 | 86 | optimization: { 87 | minimize: true, 88 | minimizer: [ 89 | new TerserPlugin({ 90 | parallel: true, 91 | }), 92 | new CssMinimizerPlugin(), 93 | ], 94 | }, 95 | 96 | plugins: [ 97 | /** 98 | * Create global constants which can be configured at compile time. 99 | * 100 | * Useful for allowing different behaviour between development builds and 101 | * release builds 102 | * 103 | * NODE_ENV should be production so that modules do not perform certain 104 | * development checks 105 | */ 106 | new webpack.EnvironmentPlugin({ 107 | NODE_ENV: 'production', 108 | DEBUG_PROD: false, 109 | }), 110 | 111 | new MiniCssExtractPlugin({ 112 | filename: 'style.css', 113 | }), 114 | 115 | new BundleAnalyzerPlugin({ 116 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 117 | }), 118 | 119 | new HtmlWebpackPlugin({ 120 | filename: 'index.html', 121 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 122 | minify: { 123 | collapseWhitespace: true, 124 | removeAttributeQuotes: true, 125 | removeComments: true, 126 | }, 127 | isBrowser: false, 128 | isDevelopment: process.env.NODE_ENV !== 'production', 129 | }), 130 | ], 131 | }); 132 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.erb/img/erb-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold('cd ./release/app && npm install your-package')} 42 | Read more about native dependencies at: 43 | ${chalk.bold( 44 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 45 | )} 46 | `); 47 | process.exit(1); 48 | } 49 | } catch (e) { 50 | console.log('Native dependencies could not be checked'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from 'rimraf'; 2 | import webpackPaths from '../configs/webpack.paths.ts'; 3 | import process from 'process'; 4 | 5 | const args = process.argv.slice(2); 6 | const commandMap = { 7 | dist: webpackPaths.distPath, 8 | release: webpackPaths.releasePath, 9 | dll: webpackPaths.dllPath, 10 | }; 11 | 12 | args.forEach((x) => { 13 | const pathToRemove = commandMap[x]; 14 | if (pathToRemove !== undefined) { 15 | rimraf.sync(pathToRemove); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rimraf from 'rimraf'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | export default function deleteSourceMaps() { 6 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map')); 7 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map')); 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { execSync } from 'child_process'; 3 | import fs from 'fs'; 4 | import { dependencies } from '../../release/app/package.json'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | if ( 8 | Object.keys(dependencies || {}).length > 0 && 9 | fs.existsSync(webpackPaths.appNodeModulesPath) 10 | ) { 11 | const electronRebuildCmd = 12 | '../../node_modules/.bin/@electron-rebuild --parallel --force --types prod,dev,optional --module-dir .'; 13 | const cmd = 14 | process.platform === 'win32' 15 | ? electronRebuildCmd.replace(/\//g, '\\') 16 | : electronRebuildCmd; 17 | execSync(cmd, { 18 | cwd: webpackPaths.appPath, 19 | stdio: 'inherit', 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const srcNodeModulesPath = webpackPaths.srcNodeModulesPath; 5 | const appNodeModulesPath = webpackPaths.appNodeModulesPath 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (!process.env.CI) { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 16 | console.warn('Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set'); 17 | return; 18 | } 19 | 20 | const appName = context.packager.appInfo.productFilename; 21 | 22 | await notarize({ 23 | appBundleId: build.appId, 24 | appPath: `${appOutDir}/${appName}.app`, 25 | appleId: process.env.APPLE_ID, 26 | appleIdPassword: process.env.APPLE_ID_PASS, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 'shared-node-browser': true, node: true, browser: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/eslint-recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | ], 8 | plugins: ['import', 'react'], 9 | rules: { 10 | 'import/no-extraneous-dependencies': 'off', 11 | 'react/react-in-jsx-scope': 'off', 12 | 'no-console': 'off', 13 | 'global-require': 'off', 14 | 'import/no-dynamic-require': 'off', 15 | indent: ['warn', 4], 16 | quotes: ['warn', 'single'], 17 | 'prettier/prettier': 0, 18 | 'object-curly-spacing': ['warn', 'always'], 19 | '@typescript-eslint/ban-types': 'off', 20 | '@typescript-eslint/no-var-requires': 'off', 21 | '@typescript-eslint/no-non-null-assertion': 'off', 22 | 'semi': 'warn' 23 | }, 24 | parserOptions: { 25 | ecmaVersion: 2021, 26 | sourceType: 'module', 27 | project: './tsconfig.json', 28 | tsconfigRootDir: __dirname, 29 | createDefaultProgram: true, 30 | }, 31 | settings: { 32 | 'import/resolver': { 33 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 34 | node: {}, 35 | webpack: { 36 | config: require.resolve( 37 | './.erb/configs/webpack.config.eslint.ts' 38 | ), 39 | }, 40 | }, 41 | 'import/parsers': { 42 | '@typescript-eslint/parser': ['.ts', '.tsx'], 43 | }, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You're having technical issues. 🐞 4 | labels: 'bug' 5 | --- 6 | 7 | 8 | 9 | ## Prerequisites 10 | 11 | ## Expected Behavior 12 | 13 | 14 | 15 | ## Current Behavior 16 | 17 | 18 | 19 | ## Steps to Reproduce 20 | 21 | 22 | 23 | 24 | 1. 25 | 26 | 2. 27 | 28 | 3. 29 | 30 | 4. 31 | 32 | ## Possible Solution (Not obligatory) 33 | 34 | 35 | 36 | ## Context 37 | 38 | 39 | 40 | 41 | 42 | ## Your Environment 43 | 44 | 45 | 46 | - App version : 47 | - Device (Phone, FireTV, AndroidTV) and Android version : 48 | - Link to your project : 49 | 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question. 4 | labels: 'question' 5 | --- 6 | 7 | ## Summary 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to the app. 4 | labels: 'enhancement' 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | requiredHeaders: 2 | - Prerequisites 3 | - Expected Behavior 4 | - Current Behavior 5 | - Possible Solution 6 | - Your Environment 7 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - discussion 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | matrix: 14 | os: [macos-latest] 15 | 16 | steps: 17 | - name: Checkout git repo 18 | uses: actions/checkout@v4 19 | 20 | - name: Install Node and NPM 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 20 24 | 25 | - name: Install dependencies 26 | run: | 27 | npm install 28 | 29 | - name: Publish releases 30 | env: 31 | # These values are used for auto updates signing 32 | # APPLE_ID: ${{ secrets.APPLE_ID }} 33 | # APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }} 34 | # CSC_LINK: ${{ secrets.CSC_LINK }} 35 | # CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} 36 | CSC_IDENTITY_AUTO_DISCOVERY: false 37 | # This is used for uploading release assets to github 38 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 39 | run: | 40 | npm run postinstall 41 | npm run build 42 | npm run release 43 | -------------------------------------------------------------------------------- /.github/workflows/quality-check.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality Check 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | run-quality-check: 11 | runs-on: ubuntu-latest 12 | env: 13 | CI: false 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Install dependencies run Lint and Test 19 | run: | 20 | npm install 21 | npm run lint 22 | npm run build 23 | npm test 24 | 25 | - name: Get all git tags 26 | run: git fetch --all --tags 27 | 28 | - name: Version Check 29 | uses: thebongy/version-check@v1 30 | with: 31 | file: release/app/package.json 32 | tagFormat: v${version} 33 | id: version_check 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | .env 31 | package-lock.json 32 | .vscode 33 | .idea 34 | apks 35 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Anthony Gress 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Toolkit 2 | 3 | ![AndroidToolkit](https://github.com/AnthonyGress/Android-Toolkit/assets/70029654/290f4fd7-083d-4fb4-a343-462f09f59a5a) 4 | 5 | # Install 6 | 7 | ## Mac & Linux 8 | 9 | Copy and paste this into the Terminal App to install. 10 | 11 | ``` 12 | /bin/bash -c "$(curl -sL https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/main/install.sh)" 13 | ``` 14 | --- 15 | 16 | ## Windows 17 | 18 | ## Download Latest: [Release](https://github.com/anthonygress/Android-Toolkit/releases/latest/download/Android-toolkit-setup.exe) 19 |
20 | 21 | Use the latest released installer named `Android-Toolkit-Setup.exe` to get started. 22 | 23 |
24 | 25 | # Usage 26 | ## Android TV / FireTV 27 | 1. Make sure you are on the same WiFi network as your android device. 28 | 2. Find the IP address of your device. 29 | On FireTV: Firestick Settings > My Fire TV > About > Network 30 | 3. Go to Device Settings and turn on ADB debugging and Install Apps from Unknown Sources. 31 | On FireTV: Settings > My Fire TV > Developer Options 32 | 4. Enter IP address into __ADB Connection Tools__ and click "Connect" or press the enter key. 33 |
34 | 35 | ## Phones/Tablets with Android 11 or higher 36 | 37 | ### For wired adb connection, you will need to: 38 | 1. enable developer mode by tapping on the build number in the about settings 39 | 2. enable usb debugging. 40 | 3. plug in your device via usb 41 | 42 | ### For wireless connection you will need to: 43 | 1. enable developer mode 44 | 2. enable usb debugging 45 | 3. wireless debugging. 46 | 4. Under the wireless debugging settings, you will need to tap pair device with pairing code. 47 | 5. Enter the pairing ipAddress:port and the pairing code in Android Toolkit. 48 | 6. Once paired, you will need to back out and use the ADB Connect section to connect to the IP address and port listed under wireless debugging. 49 | 50 | ### To enable file transfer with your connected computer: 51 | 1. Plug your device in via USB 52 | 2. Tap to notification on your phone/tablet that says "charging via USB" 53 | 3. Choose the option for File transfer / Android Auto. 54 | 55 |
56 | 57 | # APK Tools 58 | 59 | ## Install Single APK 60 | 61 | You can install any APK file from your computer directly on to your android device. 62 |

63 | 64 | ## Batch Install 65 | Installs ALL files with .apk extension in the selected directory onto your device. 66 |

67 | 68 | ## Quick Install Apps 69 | One click install for selected popular modded apps. 70 | 71 |
72 | 73 | ## TV Apps 74 | 75 | SmartTube - [https://github.com/yuliskov/SmartTubeNext/blob/master/README.md](https://github.com/yuliskov/SmartTubeNext/blob/master/README.md) 76 | 77 | Launcher Manager (FireTV) - [https://forum.xda-developers.com/t/app-firetv-noroot-launcher-manager-change-launcher-without-root.4176349/](https://forum.xda-developers.com/t/app-firetv-noroot-launcher-manager-change-launcher-without-root.4176349/) 78 | 79 | Wolf Launcher - [https://www.techdoctoruk.com/tutorials/block-android-tv-adverts-with-wolf-launcher/](https://www.techdoctoruk.com/tutorials/block-android-tv-adverts-with-wolf-launcher/) 80 |
81 | 82 | ## Mobile Apps (phone/tables) 83 | > Note: Delete the original play store app before installing 84 | 85 | Revanced - [https://revanced.app/](https://revanced.app/) 86 | 87 | To learn more about what each patch does, open the ReVanced Manager app and select your application, or visit the website for [ReVanced Patches](https://revanced.app/patches) 88 | 89 | The Infinity for Reddit patch is from [https://github.com/KhoalaS/Infinity-For-Reddit](https://github.com/KhoalaS/Infinity-For-Reddit) 90 | 91 | Simply click the button to download and install the app directly on your device. 92 | 93 | For spotify/reddit you may need to login with your username and password if signing in with google doesn't work. Youtube and Youtube Music need MicroG installed to login to your google account. 94 | 95 |
96 | 97 | # ReVanced Tools 98 | 99 | Download each of the apps in the list. Then open ReVanced and patch your youtube app. To revert at any time, simply uninstall the app and use the original youtube app from the play store. 100 | 101 | You can get links to open in the ReVanced app by going into settings > apps > youtube > open by default and unchecking all the options. Then delete data, disable/delete the app, then restart. 102 | 103 | To enable links to open in Youtube ReVanced, go to settings > apps > Youtube ReVanced > open by default and select all the options, then restart. 104 | 105 | > NOTE: On my pixel device it seems any youtube links from google chrome will try to open in the stock youtube app, or take you to the play store to install/enable it. This doesn't seem to happen from the google app. 106 | 107 | Prebuilt apks - [Revanced APKs](https://github.com/revanced-apks/build-apps/releases) 108 | 109 |
110 | 111 | # FireTV Tools 112 | 113 | ## Set Screensaver 114 | Lets you set a custom screensaver app. I have included a modified application in this repo called "Website Daydream" that changes the default wallpaper to the website https://clients3.google.com/cast/chromecast/home 115 | 116 | After installing Website Daydream, install the application called Activity launcher (https://www.f-droid.org/en/packages/de.szalkowski.activitylauncher/). Open it, and just touch the website daydream app in order for it to work. 117 |

118 | 119 | 120 | ## Check Screensaver 121 | Gets the current values for your screensaver and returns them. 122 |

123 | 124 | 125 | ## Debloat 126 | Disables ALL unnecessary Amazon services that are not needed (may break some amazon services) 127 | 128 |
129 | 130 | # System Tools 131 | 132 | Each action is performed on the connected android device with the exception of the Terminal action. This opens the terminal on your local computer in the __platform-tools__ folder where you can directly run ADB commands. 133 | 134 | Ex. `./adb install test.apk` 135 | 136 | For Battery Remaining, just divide the number by 1000 to get your battery status in mAh. 137 | 138 | You can learn more about ADB commands at [https://developer.android.com/tools/adb](https://developer.android.com/tools/adb) 139 | 140 |
141 | 142 | # Debugging 143 | 144 | If your device is showing connected but says offline when you list devices or you cannot perform some actions, simply turn off ADB debugging on your device and re-enable it. Then reconnect using the app and it should work again. 145 | -------------------------------------------------------------------------------- /assets/art.txt: -------------------------------------------------------------------------------- 1 | ______ 2 | / /\ 3 | / /##\ 4 | / /####\ 5 | / /######\ 6 | / /########\ 7 | / /##########\ 8 | / /#####/\#####\ 9 | / /#####/++\#####\ 10 | / /#####/++++\#####\ 11 | / /#####/\+++++\#####\ 12 | / /#####/ \+++++\#####\ 13 | / /#####/ \+++++\#####\ 14 | / /#####/ \+++++\#####\ 15 | / /#####/ \+++++\#####\ 16 | / /#####/__________\+++++\#####\ 17 | / \+++++\#####\ 18 | /__________________________\+++++\####/ 19 | \+++++++++++++++++++++++++++++++++\##/ 20 | \+++++++++++++++++++++++++++++++++\/ 21 | `````````````````````````````````` 22 | _______________________________________________________ 23 | 24 | 25 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | const content: string; 5 | export default content; 6 | } 7 | 8 | declare module '*.png' { 9 | const content: string; 10 | export default content; 11 | } 12 | 13 | declare module '*.jpg' { 14 | const content: string; 15 | export default content; 16 | } 17 | 18 | declare module '*.scss' { 19 | const content: Styles; 20 | export default content; 21 | } 22 | 23 | declare module '*.sass' { 24 | const content: Styles; 25 | export default content; 26 | } 27 | 28 | declare module '*.css' { 29 | const content: Styles; 30 | export default content; 31 | } 32 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icon.png -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icons/180x180.png -------------------------------------------------------------------------------- /assets/icons/192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icons/192x192.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/f45f5cb66d5a2de2b9a72eb7811be8996811c676/assets/icons/logo.png -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | OS="$(uname)" 3 | UNAME_MACHINE="$(/usr/bin/uname -m)" 4 | USER_PLATFORM="$OS $UNAME_MACHINE" 5 | 6 | install_app() { 7 | echo -e "\n---------------------- Installing Application ----------------------" 8 | echo -e "Installing latest version: $LATEST_VERSION on $USER_PLATFORM\n" 9 | 10 | if [[ "$USER_PLATFORM" == "Darwin arm64" ]] 11 | then 12 | curl -sL https://github.com/anthonygress/Android-Toolkit/releases/latest/download/Android-Toolkit-arm64.zip --output ~/Downloads/Android-Toolkit.zip && unzip -qo ~/Downloads/Android-Toolkit.zip -d /Applications 13 | elif [[ "$USER_PLATFORM" == "Darwin x86_64" ]] 14 | then 15 | curl -sL https://github.com/anthonygress/Android-Toolkit/releases/latest/download/Android-Toolkit-x64.zip --output ~/Downloads/Android-Toolkit.zip && unzip -qo ~/Downloads/Android-Toolkit.zip -d /Applications 16 | elif [[ "$OS" == "Linux" ]] 17 | then 18 | curl -sL https://github.com/anthonygress/Android-Toolkit/releases/latest/download/Android-Toolkit.AppImage --output ~/Desktop/Android-Toolkit.AppImage && chmod +x ~/Desktop/Android-Toolkit.AppImage 19 | else 20 | echo "OS not supported - please check the readme for install and support instructions" 21 | exit 1 22 | fi 23 | } 24 | 25 | install_adb() { 26 | echo -e "\n---------------------- Installing ADB ----------------------" 27 | 28 | if [[ "$OS" == "Darwin" ]] 29 | then 30 | curl -sL -o ~/Downloads/platform-tools-latest-darwin.zip https://dl.google.com/android/repository/platform-tools-latest-darwin.zip && unzip -qo ~/Downloads/platform-tools-latest-darwin.zip -d /Applications/"Android Toolkit.app"/Contents/ 31 | elif [[ "$OS" == "Linux" ]] 32 | then 33 | mkdir /usr/bin/Android-Toolkit 34 | curl -sL -o ~/Downloads/platform-tools-latest-darwin.zip https://dl.google.com/android/repository/platform-tools-latest-linux.zip && unzip -qo ~/Downloads/platform-tools-latest-darwin.zip -d /usr/bin/Android-Toolkit 35 | fi 36 | } 37 | 38 | cleanUp(){ 39 | echo -e "\n---------------------- Cleaning Up ----------------------" 40 | rm ~/Downloads/platform-tools-latest-darwin.zip 41 | 42 | if [[ "$OS" == "Darwin" ]] 43 | then 44 | rm ~/Downloads/Android-Toolkit.zip 45 | fi 46 | } 47 | 48 | openApp(){ 49 | echo -e "\n---------------------- Opening App ----------------------" 50 | 51 | if [[ "$OS" == "Darwin" ]] 52 | then 53 | open -a /Applications/"Android Toolkit.app" 54 | elif [[ "$OS" == "Linux" ]] 55 | then 56 | cd ~/Desktop && ./Android-Toolkit.AppImage 57 | fi 58 | echo 59 | } 60 | 61 | #runtime 62 | curl https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/main/assets/art.txt 63 | install_app 64 | sleep 2 65 | install_adb 66 | openApp 67 | cleanUp 68 | 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "android-toolkit", 3 | "description": "Electron application for modifying Android Devices such as mobile phones, chromecast's and fireTV's. Built using Typescript, React, React Router, Webpack, React Fast Refresh for rapid application development", 4 | "scripts": { 5 | "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"", 6 | "build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts", 7 | "clean": "ts-node .erb/scripts/clean.js", 8 | "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts", 9 | "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir src", 10 | "lint": "cross-env NODE_ENV=development eslint . --cache --ext .js,.jsx,.ts,.tsx", 11 | "lint-fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", 12 | "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never", 13 | "package-win": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never --win --x64", 14 | "package-all": "npm run package -- --win --x64 && npm run package -- --linux && npm run package -- --linux --x64 && npm run package", 15 | "postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts", 16 | "start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer", 17 | "dev": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer", 18 | "start:main": "cross-env NODE_ENV=development electron -r ts-node/register/transpile-only ./src/main/main.ts", 19 | "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", 20 | "test": "jest", 21 | "prepare": "husky install", 22 | "release": "npm exec electron-builder -- --publish always --linux --x64 && npm exec electron-builder -- --publish always --win --x64 && npm exec electron-builder -- --publish always --mac --arm64 --linux --arm64" 23 | }, 24 | "lint-staged": { 25 | "*.{js,jsx,ts,tsx}": [ 26 | "cross-env NODE_ENV=development eslint --cache" 27 | ], 28 | "*.json,.{eslintrc,prettierrc}": [ 29 | "prettier --ignore-path .eslintignore --parser json --write" 30 | ], 31 | "*.{css,scss}": [ 32 | "prettier --ignore-path .eslintignore --single-quote --write" 33 | ], 34 | "*.{html,md,yml}": [ 35 | "prettier --ignore-path .eslintignore --single-quote --write" 36 | ] 37 | }, 38 | "build": { 39 | "productName": "Android Toolkit", 40 | "appId": "com.techx.Android-toolkit", 41 | "asar": true, 42 | "asarUnpack": [ 43 | "**\\*.{node,dll}" 44 | ], 45 | "files": [ 46 | "dist", 47 | "node_modules", 48 | "package.json" 49 | ], 50 | "afterSign": ".erb/scripts/notarize.js", 51 | "nsis": { 52 | "differentialPackage": false, 53 | "artifactName": "Android-Toolkit-Setup.${ext}" 54 | }, 55 | "mac": { 56 | "artifactName": "Android-Toolkit-${arch}.${ext}", 57 | "target": { 58 | "target": "zip", 59 | "arch": [ 60 | "arm64", 61 | "x64" 62 | ] 63 | }, 64 | "type": "distribution", 65 | "hardenedRuntime": true, 66 | "entitlements": "assets/entitlements.mac.plist", 67 | "entitlementsInherit": "assets/entitlements.mac.plist", 68 | "gatekeeperAssess": false, 69 | "publish": "github" 70 | }, 71 | "win": { 72 | "target": [ 73 | "nsis" 74 | ], 75 | "publish": "github" 76 | }, 77 | "linux": { 78 | "artifactName": "Android-Toolkit-${arch}.${ext}", 79 | "target": [ 80 | "AppImage" 81 | ], 82 | "category": "Development", 83 | "publish": "github" 84 | }, 85 | "directories": { 86 | "app": "release/app", 87 | "buildResources": "assets", 88 | "output": "release/build" 89 | }, 90 | "extraResources": [ 91 | "./assets/**" 92 | ] 93 | }, 94 | "repository": { 95 | "type": "git", 96 | "url": "git+https://github.com/AnthonyGress/Android-Toolkit.git" 97 | }, 98 | "author": { 99 | "name": "Anthony Gress", 100 | "email": "techx@opayq.com", 101 | "url": "https://anthonygress.dev" 102 | }, 103 | "license": "MIT", 104 | "keywords": [ 105 | "electron", 106 | "boilerplate", 107 | "react", 108 | "typescript", 109 | "ts", 110 | "sass", 111 | "webpack", 112 | "hot", 113 | "reload" 114 | ], 115 | "homepage": "https://anthonygress.dev", 116 | "jest": { 117 | "testURL": "http://localhost/", 118 | "testEnvironment": "jsdom", 119 | "transform": { 120 | "\\.(ts|tsx|js|jsx)$": "ts-jest" 121 | }, 122 | "moduleNameMapper": { 123 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.erb/mocks/fileMock.js", 124 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 125 | }, 126 | "moduleFileExtensions": [ 127 | "js", 128 | "jsx", 129 | "ts", 130 | "tsx", 131 | "json" 132 | ], 133 | "moduleDirectories": [ 134 | "node_modules", 135 | "release/app/node_modules" 136 | ], 137 | "testPathIgnorePatterns": [ 138 | "release/app/dist" 139 | ], 140 | "setupFiles": [ 141 | "./.erb/scripts/check-build-exists.ts" 142 | ] 143 | }, 144 | "devDependencies": { 145 | "@electron/notarize": "^2.1.0", 146 | "@electron/rebuild": "^3.2.13", 147 | "@pmmmwh/react-refresh-webpack-plugin": "0.5.1", 148 | "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", 149 | "@testing-library/jest-dom": "^5.14.1", 150 | "@testing-library/react": "^12.1.2", 151 | "@types/enzyme": "^3.10.10", 152 | "@types/history": "4.7.9", 153 | "@types/jest": "^27.0.2", 154 | "@types/node": "^18.16.19", 155 | "@types/react": "^18.0.14", 156 | "@types/react-dom": "^18.0.5", 157 | "@types/react-router": "^5.1.17", 158 | "@types/react-router-dom": "^5.3.2", 159 | "@types/react-test-renderer": "^17.0.1", 160 | "@types/webpack-env": "^1.16.3", 161 | "@typescript-eslint/eslint-plugin": "^5.28.0", 162 | "browserslist-config-erb": "^0.0.3", 163 | "chalk": "^4.1.2", 164 | "concurrently": "^6.3.0", 165 | "core-js": "^3.31.1", 166 | "cross-env": "^7.0.3", 167 | "css-loader": "^6.7.1", 168 | "css-minimizer-webpack-plugin": "^3.1.1", 169 | "detect-port": "^1.3.0", 170 | "electron": "^25.2.0", 171 | "electron-builder": "^24.4.0", 172 | "electron-devtools-installer": "^3.2.0", 173 | "enzyme": "^3.11.0", 174 | "enzyme-to-json": "^3.6.2", 175 | "eslint": "^8.44.0", 176 | "eslint-plugin-import": "^2.27.5", 177 | "eslint-plugin-jest": "^27.2.2", 178 | "eslint-plugin-jsx-a11y": "^6.5.1", 179 | "eslint-plugin-promise": "^6.1.1", 180 | "eslint-plugin-react": "^7.32.2", 181 | "eslint-plugin-react-hooks": "^4.6.0", 182 | "file-loader": "^6.2.0", 183 | "html-webpack-plugin": "^5.5.0", 184 | "husky": "^7.0.0", 185 | "identity-obj-proxy": "^3.0.0", 186 | "jest": "^27.3.1", 187 | "lint-staged": "^11.2.6", 188 | "mini-css-extract-plugin": "^2.4.3", 189 | "prettier": "^2.4.1", 190 | "react-refresh": "^0.10.0", 191 | "react-refresh-typescript": "^2.0.2", 192 | "react-test-renderer": "^17.0.2", 193 | "rimraf": "^3.0.2", 194 | "sass": "^1.43.4", 195 | "sass-loader": "^12.3.0", 196 | "style-loader": "^3.3.1", 197 | "terser-webpack-plugin": "^5.2.4", 198 | "ts-jest": "^27.0.7", 199 | "ts-loader": "^9.2.6", 200 | "ts-node": "^10.8.1", 201 | "typescript": "^4.7.3", 202 | "url-loader": "^4.1.1", 203 | "webpack": "^5.60.0", 204 | "webpack-bundle-analyzer": "^4.5.0", 205 | "webpack-cli": "^4.9.1", 206 | "webpack-dev-server": "^4.4.0", 207 | "webpack-merge": "^5.8.0" 208 | }, 209 | "dependencies": { 210 | "@emotion/react": "^11.11.0", 211 | "@emotion/styled": "^11.11.0", 212 | "@mui/icons-material": "^5.11.16", 213 | "@mui/material": "^5.13.1", 214 | "axios": "^1.4.0", 215 | "electron-debug": "^3.2.0", 216 | "electron-log": "^4.4.1", 217 | "electron-updater": "^4.6.1", 218 | "history": "4.x.x", 219 | "octokit": "^2.1.0", 220 | "react": "^17.0.2", 221 | "react-dom": "^17.0.2", 222 | "react-router-dom": "^5.3.0", 223 | "regenerator-runtime": "^0.13.9", 224 | "semver": "^7.5.4", 225 | "sweetalert2": "^11.7.12", 226 | "uuid": "^11.0.5" 227 | }, 228 | "browserslist": [], 229 | "prettier": { 230 | "overrides": [ 231 | { 232 | "files": [ 233 | ".prettierrc", 234 | ".eslintrc" 235 | ], 236 | "options": { 237 | "parser": "json" 238 | } 239 | } 240 | ], 241 | "singleQuote": true 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "android-toolkit", 3 | "version": "1.5.25", 4 | "description": "Universal app with gui for simple adb connection", 5 | "main": "./dist/main/main.js", 6 | "author": { 7 | "name": "Anthony Gress", 8 | "email": "techx@opayq.com", 9 | "url": "https://github.com/AnthonyGress" 10 | }, 11 | "scripts": { 12 | "electron-rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 13 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts", 14 | "postinstall": "npm run electron-rebuild && npm run link-modules" 15 | }, 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/main/api/ipcHandler.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { app, ipcMain } from 'electron'; 3 | import { LAUNCHER_MANAGER_URL, POWERSHELL_CMD, REVANCED_YOUTUBE_URL,SMART_TUBE_URL, 4 | REVANCED_SPOTIFY_URL, TERMINAL_CMD,WOLF_LAUNCHER_URL, APK_PATH, ADB_PATH, MICRO_G_URL, REVANCED_MANAGER_URL, YOUTUBE_URL, REDDIT_URL, REVANCED_REDDIT_URL, REVANCED_YTMUSIC_URL, REVANCED_TIKTOK_URL, INFINITY_REDDIT_URL, SPOTIFY_MOD_URL 5 | } from '../constants'; 6 | import { executeCmd, batchInstall } from '../utils'; 7 | import { startUpdate } from '../utils/appUpdater'; 8 | import { downloadFile } from '../utils/downloadFile'; 9 | import { execPromise, spawnShell } from '../utils/executeCmd'; 10 | 11 | export const routeHandler = () => { 12 | // listen for messages from renderer at these routes 13 | 14 | ipcMain.on('shellChannel', (event, args: string) => { 15 | let command = args; 16 | 17 | console.log(command); 18 | 19 | switch (command) { 20 | case 'powershell': 21 | command = POWERSHELL_CMD; 22 | executeCmd(command, event, 'shellResponse'); 23 | break; 24 | 25 | case 'terminal': 26 | command = TERMINAL_CMD; 27 | executeCmd(command, event, 'shellResponse'); 28 | break; 29 | 30 | case 'update': 31 | event.reply('shellResponse', 'starting update'); 32 | startUpdate(event).then(() => { 33 | if (process.platform !== 'win32') 34 | event.reply('shellResponse', 'update complete'); 35 | }); 36 | break; 37 | 38 | default: 39 | executeCmd(command, event, 'shellResponse'); 40 | break; 41 | } 42 | }); 43 | 44 | ipcMain.on('adbChannel', (event, args: string) => { 45 | const command = `${ADB_PATH}${args}`; 46 | console.log(command); 47 | 48 | if (args.includes(',')) { 49 | 50 | // adb pair with pairing closed 51 | const argsArr = args.split(','); 52 | console.log(argsArr); 53 | const pairCommand = argsArr[0]; 54 | const pairingCode = argsArr[1]; 55 | 56 | spawnShell(pairCommand, event, 'adbResponse', pairingCode); 57 | } 58 | 59 | switch (args) { 60 | case 'batchInstall': 61 | console.log('batchInstall'); 62 | batchInstall(event); 63 | break; 64 | 65 | case 'smarttube': 66 | console.log('install smarttube'); 67 | downloadFile(SMART_TUBE_URL, path.join(APK_PATH, 'smartTube.apk')).then(async () => { 68 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}smartTube.apk"`); 69 | event.reply('adbResponse', 'Installed SmartTube'); 70 | }).catch((error) => { 71 | event.reply('adbResponse', error.message); 72 | console.log(error.message); 73 | }); 74 | break; 75 | 76 | case 'spotify-mod': 77 | console.log('install spotify-mod'); 78 | downloadFile(SPOTIFY_MOD_URL, path.join(APK_PATH, 'spotify-mod.apk')).then(async () => { 79 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}spotify-mod.apk"`); 80 | event.reply('adbResponse', 'Installed Spotify Mod'); 81 | }).catch((error) => { 82 | event.reply('adbResponse', error.message); 83 | console.log(error.message); 84 | }); 85 | break; 86 | 87 | case 'infinity-reddit': 88 | console.log('install infinity-reddit'); 89 | downloadFile(INFINITY_REDDIT_URL, path.join(APK_PATH, 'infinity-reddit.apk')).then(async () => { 90 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}infinity-reddit.apk"`); 91 | event.reply('adbResponse', 'Installed Infinity Reddit'); 92 | }).catch((error) => { 93 | event.reply('adbResponse', error.message); 94 | console.log(error.message); 95 | }); 96 | break; 97 | 98 | case 'launcher manager': 99 | console.log('launcher manager'); 100 | downloadFile(LAUNCHER_MANAGER_URL, path.join(APK_PATH, 'launcherManager.apk')).then(async () => { 101 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}launcherManager.apk"`); 102 | event.reply('adbResponse', 'Installed Launcher Manager'); 103 | }).catch((error) => { 104 | event.reply('adbResponse', error.message); 105 | console.log(error.message); 106 | }); 107 | break; 108 | 109 | case 'wolf launcher': 110 | console.log('wolf launcher'); 111 | downloadFile(WOLF_LAUNCHER_URL, path.join(APK_PATH, 'wolfLauncher.apk')).then(async () => { 112 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}wolfLauncher.apk"`); 113 | event.reply('adbResponse', 'Installed Wolf Launcher'); 114 | }).catch((error) => { 115 | event.reply('adbResponse', error.message); 116 | console.log(error.message); 117 | }); 118 | break; 119 | 120 | case 'revanced-youtube': 121 | console.log('install revanced youtube'); 122 | // console.log('url', REVANCED_YOUTUBE_URL); 123 | 124 | downloadFile(MICRO_G_URL, path.join(APK_PATH, 'microg.apk')).then(async () => { 125 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}microg.apk"`); 126 | event.reply('adbResponse', 'Installed microg (for login), please wait for revanced...'); 127 | 128 | downloadFile(REVANCED_YOUTUBE_URL, path.join(APK_PATH, 'revanced-youtube.apk')).then(async () => { 129 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}revanced-youtube.apk"`); 130 | event.reply('adbResponse', 'Installed Youtube Revanced'); 131 | }).catch((error) => { 132 | event.reply('adbResponse', error.message); 133 | console.log(error.message); 134 | }); 135 | 136 | }).catch((error) => { 137 | event.reply('adbResponse', error.message); 138 | console.log(error.message); 139 | }); 140 | 141 | break; 142 | 143 | case 'revanced-ytmusic': 144 | console.log('install ytmusic'); 145 | 146 | downloadFile(MICRO_G_URL, path.join(APK_PATH, 'microg.apk')).then(async () => { 147 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}microg.apk"`); 148 | event.reply('adbResponse', 'Installed microg (for login), please wait for revanced...'); 149 | 150 | downloadFile(REVANCED_YTMUSIC_URL, path.join(APK_PATH, 'revanced-ytmusic.apk')).then(async () => { 151 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}revanced-ytmusic.apk"`); 152 | event.reply('adbResponse', 'Installed Youtube Music Revanced'); 153 | }).catch((error) => { 154 | event.reply('adbResponse', error.message); 155 | console.log(error.message); 156 | }); 157 | 158 | }).catch((error) => { 159 | event.reply('adbResponse', error.message); 160 | console.log(error.message); 161 | }); 162 | 163 | break; 164 | 165 | case 'revanced-reddit': 166 | console.log('install revanced reddit'); 167 | 168 | downloadFile(REVANCED_REDDIT_URL, path.join(APK_PATH, 'revanced-reddit.apk')).then(async () => { 169 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}revanced-reddit.apk"`); 170 | event.reply('adbResponse', 'Installed revanced-reddit'); 171 | 172 | }).catch((error) => { 173 | event.reply('adbResponse', error.message); 174 | console.log(error.message); 175 | }); 176 | 177 | break; 178 | 179 | case 'revanced-tiktok': 180 | console.log('install revanced reddit'); 181 | 182 | downloadFile(REVANCED_TIKTOK_URL, path.join(APK_PATH, 'revanced-tiktok.apk')).then(async () => { 183 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}revanced-tiktok.apk"`); 184 | event.reply('adbResponse', 'Installed revanced-tiktok'); 185 | 186 | }).catch((error) => { 187 | event.reply('adbResponse', error.message); 188 | console.log(error.message); 189 | }); 190 | 191 | break; 192 | 193 | case 'revanced-manager': 194 | console.log('revanced'); 195 | downloadFile(REVANCED_MANAGER_URL, path.join(APK_PATH, 'revanced-manager.apk')).then(async () => { 196 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}revanced-manager.apk"`); 197 | event.reply('adbResponse', 'Installed Revanced Manager'); 198 | }).catch((error) => { 199 | event.reply('adbResponse', error.message); 200 | console.log(error.message); 201 | }); 202 | break; 203 | 204 | case 'youtube': 205 | console.log('youtube'); 206 | 207 | downloadFile(YOUTUBE_URL, path.join(APK_PATH, 'youtube.apk')).then(async () => { 208 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}youtube.apk"`); 209 | event.reply('adbResponse', 'Installed Stock Youtube, use Revanced Manager to apply patches'); 210 | }).catch((error) => { 211 | event.reply('adbResponse', error.message); 212 | console.log(error.message); 213 | }); 214 | 215 | break; 216 | 217 | case 'microg': 218 | console.log('microg'); 219 | 220 | downloadFile(MICRO_G_URL, path.join(APK_PATH, 'microg.apk')).then(async () => { 221 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}microg.apk"`); 222 | event.reply('adbResponse', 'Installed MicroG'); 223 | }).catch((error) => { 224 | event.reply('adbResponse', error.message); 225 | console.log(error.message); 226 | }); 227 | 228 | break; 229 | 230 | case 'reddit': 231 | console.log('reddit'); 232 | 233 | downloadFile(REDDIT_URL, path.join(APK_PATH, 'reddit.apk')).then(async () => { 234 | await execPromise(`${ADB_PATH}adb install -r -d "${APK_PATH}reddit.apk"`); 235 | event.reply('adbResponse', 'Installed Stock Reddit, use Revanced Manager to apply patches'); 236 | }).catch((error) => { 237 | event.reply('adbResponse', error.message); 238 | console.log(error.message); 239 | }); 240 | 241 | break; 242 | 243 | default: 244 | console.log('default'); 245 | executeCmd(command, event, 'adbResponse'); 246 | break; 247 | } 248 | 249 | }); 250 | 251 | ipcMain.on('communicationChannel', async (event, args) => { 252 | console.log('hit coms'); 253 | 254 | try { 255 | if (args.includes('restart')){ 256 | event.reply('messageResponse', 'restarting'); 257 | app.relaunch(); 258 | app.exit(); 259 | } 260 | } catch (error: any) { 261 | console.log(error); 262 | event.reply('messageResponse', error.message); 263 | } 264 | }); 265 | }; 266 | 267 | -------------------------------------------------------------------------------- /src/main/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | 3 | export const POWERSHELL_CMD = 'start powershell -noexit -command "[console]::windowwidth=80; [console]::windowheight=35; [console]::bufferwidth=[console]::windowwidth; cd .\\platform-tools; Get-Content -Raw ..\\resources\\assets\\art.txt; Write-Host "Run ADB commands here" -nonewline; Write-Host "`n";Write-Host "Ex: .\\adb COMMAND"; Write-Host "`n""'; 4 | 5 | export const SMART_TUBE_URL = 'https://github.com/yuliskov/SmartTube/releases/download/25.24s/SmartTube_stable_25.24_armeabi-v7a.apk'; 6 | 7 | export const INFINITY_REDDIT_URL = 'https://github.com/KhoalaS/Infinity-For-Reddit/releases/latest/download/app-release.apk'; 8 | 9 | // export const REVANCED_SPOTIFY_URL = 'https://github.com/revanced-apks/build-apps/releases/download/147/spotify-revanced-v8.8.50.466-all.apk'; 10 | 11 | export const SPOTIFY_MOD_URL = 'https://download1638.mediafire.com/ltt2t112ktjgUyaHZhGUdtnW7-Fp-PlE2gmj_pS1bo01lp69Doip8bI8d_T0y-cB1HBF2NdbKWeCQERKqI9wFvv5uJj6zPGlUEe3ZFPRs_uW1WPwsQlzaYowVlw1UBS4uhJOviRb3lU5qpfA6WEwl8YS635-V2Xs0YArbNSyrbIC/gks82nqggqpk4de/spotify-mod.apk'; 12 | 13 | export const REVANCED_TIKTOK_URL = 'https://github.com/revanced-apks/build-apps/releases/download/147/tiktok-revanced-v30.3.4-all.apk'; 14 | 15 | export const REVANCED_YTMUSIC_URL = 'https://github.com/revanced-apks/build-apps/releases/download/147/music-revanced-v6.10.51-arm64-v8a.apk'; 16 | 17 | export const YOUTUBE_URL = 'https://d.apkpure.com/b/APK/com.google.android.youtube?versionCode=1545084352'; 18 | 19 | export const LAUNCHER_MANAGER_URL = 'https://forum.xda-developers.com/attachments/lm-fos-1-1-8-apk.5862251/'; 20 | 21 | export const WOLF_LAUNCHER_URL = 'https://www.techdoctoruk.com/?sdm_process_download=1&download_id=4471'; 22 | 23 | export const MICRO_G_URL ='https://github.com/ReVanced/GmsCore/releases/download/v0.3.1.4.240913/app.revanced.android.gms-240913008-signed.apk'; 24 | 25 | const BUFF = Buffer.from('aHR0cHM6Ly9kcml2ZS51c2VyY29udGVudC5nb29nbGUuY29tL2Rvd25sb2FkP2lkPTE5U0dZYXVSa2FBSHZlc1J5VGFNUEhHeWpRQU5jWVdWZSZleHBvcnQ9ZG93bmxvYWQmY29uZmlybT15ZXM=', 'base64'); 26 | 27 | export const REVANCED_MANAGER_URL = 'https://github.com/ReVanced/revanced-manager/releases/download/v1.20.1/revanced-manager-v1.20.1.apk'; 28 | 29 | export const REVANCED_YOUTUBE_URL = BUFF.toString('ascii'); 30 | 31 | export const REVANCED_REDDIT_URL = 'https://github.com/revanced-apks/build-apps/releases/download/147/reddit-revanced-v2023.26.0-all.apk'; 32 | 33 | export const REDDIT_URL = 'https://d.apkpure.com/b/APK/com.reddit.frontpage?version=latest'; 34 | 35 | export const IS_WIN = process.platform === 'win32'; 36 | 37 | export const USERNAME= process.env.USERNAME; 38 | export const WINDOWS_RESOURCE_PATH = `C:\\Users\\${USERNAME}\\AppData\\Local\\Programs\\android-toolkit\\resources`; 39 | 40 | 41 | const getAdbPath = () => { 42 | console.log('using dev adb path'); 43 | 44 | let path = ''; 45 | exec('which adb', (error, stdout, stderr) => { 46 | if (error) { 47 | console.error(`exec error: ${error}`); 48 | console.log(stderr); 49 | return; 50 | } 51 | path = stdout.toString(); 52 | }); 53 | return path; 54 | }; 55 | 56 | export const isDevelopment = process.env.NODE_ENV === 'development'; 57 | export let APK_PATH: string; 58 | export let ADB_PATH: string; 59 | export let TERMINAL_CMD: string; 60 | export const USER_OS = process.platform; 61 | 62 | switch (USER_OS) { 63 | case 'darwin': 64 | console.log('MacOS'); 65 | APK_PATH = isDevelopment ? './apks' : '/Applications/Android Toolkit.app/Contents/apks/'; 66 | ADB_PATH = isDevelopment ? getAdbPath() : '/Applications/"Android Toolkit.app"/Contents/platform-tools/'; 67 | TERMINAL_CMD = 'open -a Terminal /Applications/"Android Toolkit.app"/Contents/platform-tools'; 68 | break; 69 | 70 | case 'win32': 71 | console.log('Windows operating system'); 72 | APK_PATH = `C:\\Users\\${USERNAME}\\AppData\\Local\\Programs\\android-toolkit\\apks\\`; 73 | ADB_PATH = `C:\\Users\\${USERNAME}\\AppData\\Local\\Programs\\android-toolkit\\platform-tools\\`; 74 | break; 75 | 76 | case 'linux': 77 | console.log('Linux operating system'); 78 | APK_PATH = isDevelopment ? './apks' : '/usr/bin/Android-Toolkit/apks/'; 79 | ADB_PATH = isDevelopment ? getAdbPath() : '/usr/bin/Android-Toolkit/platform-tools/'; 80 | TERMINAL_CMD = isDevelopment ? 'x-terminal-emulator' : 'x-terminal-emulator -w /usr/bin/Android-Toolkit/platform-tools/'; 81 | break; 82 | 83 | default: 84 | console.log('other operating system'); 85 | break; 86 | } 87 | -------------------------------------------------------------------------------- /src/main/main.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/stable'; 2 | import 'regenerator-runtime/runtime'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import MenuBuilder from './menu'; 6 | import { app, BrowserWindow, shell } from 'electron'; 7 | import { routeHandler } from './api/ipcHandler'; 8 | import { MainWindow } from './types'; 9 | import { resolveHtmlPath, checkForUpdates, execPromise } from './utils'; 10 | import { ADB_PATH, APK_PATH, IS_WIN, WINDOWS_RESOURCE_PATH } from './constants'; 11 | // import AppUpdater from './utils/appUpdater'; 12 | 13 | const isDevelopment = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; 14 | 15 | let mainWindow: MainWindow = null; 16 | 17 | if (isDevelopment) { 18 | require('electron-debug')(); 19 | } 20 | 21 | const setupWinAdb = () => { 22 | const { exec } = require('child_process'); 23 | 24 | if (!fs.existsSync(ADB_PATH)) { 25 | 26 | exec(`curl -L https://dl.google.com/android/repository/platform-tools-latest-windows.zip -o ${WINDOWS_RESOURCE_PATH}\\platform-tools.zip && tar -xf "${path.join(__dirname, '../../../platform-tools.zip')}`, (_error: Error, stdout: string, stderr: Error) => { 27 | if (mainWindow){ 28 | mainWindow.webContents.send('startup', `stdout: ${stdout}`); 29 | mainWindow.webContents.send('startup', `stderr: ${stderr}`); 30 | } 31 | 32 | console.log(`stdout: ${stdout}`); 33 | console.log(`stderr: ${stderr}`); 34 | }); 35 | } 36 | }; 37 | 38 | const initialize = () => { 39 | !fs.existsSync(APK_PATH) && fs.mkdir(APK_PATH, (err) => { 40 | if (err) console.log(err); 41 | }); 42 | 43 | if (IS_WIN) { 44 | setupWinAdb(); 45 | } 46 | 47 | routeHandler(); 48 | }; 49 | 50 | initialize(); 51 | 52 | if (process.env.NODE_ENV === 'production') { 53 | const sourceMapSupport = require('source-map-support'); 54 | sourceMapSupport.install(); 55 | } 56 | 57 | const createWindow = async () => { 58 | // if (isDevelopment) { 59 | // await installExtensions() 60 | // } 61 | 62 | const RESOURCES_PATH = app.isPackaged 63 | ? path.join(process.resourcesPath, 'assets') 64 | : path.join(__dirname, '../../assets'); 65 | 66 | const getAssetPath = (...paths: string[]): string => { 67 | return path.join(RESOURCES_PATH, ...paths); 68 | }; 69 | 70 | mainWindow = new BrowserWindow({ 71 | show: false, 72 | width: 1024, 73 | height: 728, 74 | icon: getAssetPath('icon.png'), 75 | webPreferences: { 76 | preload: path.join(__dirname, 'preload.js'), 77 | }, 78 | }); 79 | 80 | mainWindow.loadURL(resolveHtmlPath('index.html')); 81 | 82 | mainWindow.on('ready-to-show', () => { 83 | if (!mainWindow) { 84 | throw new Error('"mainWindow" is not defined'); 85 | } 86 | if (process.env.START_MINIMIZED) { 87 | mainWindow.minimize(); 88 | } else { 89 | mainWindow.show(); 90 | 91 | // start adb server when app starts 92 | execPromise(`${ADB_PATH}adb start-server`).then(() => { 93 | console.log('started adb server'); 94 | mainWindow?.webContents.send('startup', 'started adb server'); 95 | }); 96 | 97 | checkForUpdates().then((result) => { 98 | const updateText = result ? '\n Update Available' : ''; 99 | 100 | mainWindow?.webContents.send('startup', `Welcome to Android-Toolkit version ${app.getVersion()}${updateText}`); 101 | 102 | }); 103 | } 104 | }); 105 | 106 | mainWindow.on('closed', () => { 107 | mainWindow = null; 108 | }); 109 | 110 | const menuBuilder = new MenuBuilder(mainWindow); 111 | menuBuilder.buildMenu(); 112 | 113 | // Open urls in the user's browser 114 | mainWindow.webContents.setWindowOpenHandler((edata) => { 115 | shell.openExternal(edata.url); 116 | return { action: 'deny' }; 117 | }); 118 | 119 | // disable auto updates 120 | // new AppUpdater(); 121 | }; 122 | 123 | /** 124 | * Add event listeners... 125 | */ 126 | 127 | app.on('window-all-closed', () => { 128 | // kill adb server when app closes 129 | execPromise(`${ADB_PATH}adb kill-server`).then(() => { 130 | mainWindow?.webContents.send('startup', 'killed adb server'); 131 | }); 132 | console.log('killed adb server'); 133 | 134 | // Respect the macOS convention of having the application in memory even 135 | // after all windows have been closed 136 | if (process.platform !== 'darwin') { 137 | app.quit(); 138 | } 139 | }); 140 | 141 | app 142 | .whenReady() 143 | .then(() => { 144 | createWindow(); 145 | app.on('activate', () => { 146 | // On macOS it's common to re-create a window in the app when the 147 | // dock icon is clicked and there are no other windows open. 148 | if (mainWindow === null) createWindow(); 149 | }); 150 | }) 151 | .catch(console.log); 152 | -------------------------------------------------------------------------------- /src/main/menu.ts: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | Menu, 4 | shell, 5 | BrowserWindow, 6 | MenuItemConstructorOptions, 7 | } from 'electron'; 8 | import { platform } from 'os'; 9 | import { execPromise } from './utils'; 10 | 11 | interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { 12 | selector?: string; 13 | submenu?: DarwinMenuItemConstructorOptions[] | Menu; 14 | } 15 | 16 | export default class MenuBuilder { 17 | mainWindow: BrowserWindow; 18 | 19 | constructor(mainWindow: BrowserWindow) { 20 | this.mainWindow = mainWindow; 21 | } 22 | 23 | buildMenu(): Menu { 24 | if ( 25 | process.env.NODE_ENV === 'development' || 26 | process.env.DEBUG_PROD === 'true' 27 | ) { 28 | this.setupDevelopmentEnvironment(); 29 | } 30 | 31 | const template = 32 | process.platform === 'darwin' 33 | ? this.buildDarwinTemplate() 34 | : this.buildDefaultTemplate(); 35 | 36 | const menu = Menu.buildFromTemplate(template); 37 | Menu.setApplicationMenu(menu); 38 | 39 | return menu; 40 | } 41 | 42 | setupDevelopmentEnvironment(): void { 43 | this.mainWindow.webContents.on('context-menu', (_, props) => { 44 | const { x, y } = props; 45 | 46 | Menu.buildFromTemplate([ 47 | { 48 | label: 'Inspect element', 49 | click: () => { 50 | this.mainWindow.webContents.inspectElement(x, y); 51 | }, 52 | }, 53 | ]).popup({ window: this.mainWindow }); 54 | }); 55 | } 56 | 57 | unixUpdate(): any { 58 | const updateMenu = { 59 | label: 'Update Android Toolkit', 60 | click: async () => { 61 | console.log('running update'); 62 | this.mainWindow.webContents.send('startup', 'starting update'); 63 | await execPromise('/bin/bash -c "$(curl -sL https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/main/install.sh)"'); 64 | this.mainWindow.webContents.send('startup', 'update complete'); 65 | } 66 | }; 67 | 68 | if (platform() === 'darwin' || platform() === 'linux'){ 69 | return updateMenu; 70 | } 71 | } 72 | 73 | buildDarwinTemplate(): MenuItemConstructorOptions[] { 74 | const subMenuAbout: DarwinMenuItemConstructorOptions = { 75 | label: 'Android Toolkit', 76 | submenu: [ 77 | { 78 | label: 'About Android Toolkit', 79 | selector: 'orderFrontStandardAboutPanel:', 80 | }, 81 | this.unixUpdate(), 82 | { type: 'separator' }, 83 | { type: 'separator' }, 84 | { 85 | label: 'Hide Android Toolkit', 86 | accelerator: 'Command+H', 87 | selector: 'hide:', 88 | }, 89 | { 90 | label: 'Hide Others', 91 | accelerator: 'Command+Shift+H', 92 | selector: 'hideOtherApplications:', 93 | }, 94 | { label: 'Show All', selector: 'unhideAllApplications:' }, 95 | { type: 'separator' }, 96 | { 97 | label: 'Quit', 98 | accelerator: 'Command+Q', 99 | click: () => { 100 | app.quit(); 101 | }, 102 | }, 103 | ], 104 | }; 105 | const subMenuEdit: DarwinMenuItemConstructorOptions = { 106 | label: 'Edit', 107 | submenu: [ 108 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }, 109 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' }, 110 | { type: 'separator' }, 111 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }, 112 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }, 113 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }, 114 | { 115 | label: 'Select All', 116 | accelerator: 'Command+A', 117 | selector: 'selectAll:', 118 | }, 119 | ], 120 | }; 121 | const subMenuViewDev: MenuItemConstructorOptions = { 122 | label: 'View', 123 | submenu: [ 124 | { 125 | label: 'Reload', 126 | accelerator: 'Command+R', 127 | click: () => { 128 | this.mainWindow.webContents.reload(); 129 | }, 130 | }, 131 | { 132 | label: 'Toggle Full Screen', 133 | accelerator: 'Ctrl+Command+F', 134 | click: () => { 135 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 136 | }, 137 | }, 138 | { 139 | label: 'Toggle Developer Tools', 140 | accelerator: 'Alt+Command+I', 141 | click: () => { 142 | this.mainWindow.webContents.toggleDevTools(); 143 | }, 144 | }, 145 | ], 146 | }; 147 | const subMenuViewProd: MenuItemConstructorOptions = { 148 | label: 'View', 149 | submenu: [ 150 | { 151 | label: 'Toggle Full Screen', 152 | accelerator: 'Ctrl+Command+F', 153 | click: () => { 154 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 155 | }, 156 | }, 157 | { 158 | label: 'Toggle Developer Tools', 159 | accelerator: 'Alt+Command+I', 160 | click: () => { 161 | this.mainWindow.webContents.toggleDevTools(); 162 | }, 163 | }, 164 | ], 165 | }; 166 | const subMenuWindow: DarwinMenuItemConstructorOptions = { 167 | label: 'Window', 168 | submenu: [ 169 | { 170 | label: 'Minimize', 171 | accelerator: 'Command+M', 172 | selector: 'performMiniaturize:', 173 | }, 174 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 175 | { type: 'separator' }, 176 | { label: 'Bring All to Front', selector: 'arrangeInFront:' }, 177 | ], 178 | }; 179 | const subMenuHelp: MenuItemConstructorOptions = { 180 | label: 'Help', 181 | submenu: [ 182 | { 183 | label: 'Learn More', 184 | click() { 185 | shell.openExternal('https://github.com/AnthonyGress/Android-Toolkit/'); 186 | }, 187 | }, 188 | { 189 | label: 'Documentation', 190 | click() { 191 | shell.openExternal( 192 | 'https://github.com/AnthonyGress/Android-Toolkit/#readme' 193 | ); 194 | }, 195 | }, 196 | { 197 | label: 'Search/Report Issues', 198 | click() { 199 | shell.openExternal('https://github.com/AnthonyGress/Android-Toolkit/issues'); 200 | }, 201 | }, 202 | ], 203 | }; 204 | 205 | const subMenuView = 206 | process.env.NODE_ENV === 'development' || 207 | process.env.DEBUG_PROD === 'true' 208 | ? subMenuViewDev 209 | : subMenuViewProd; 210 | 211 | return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]; 212 | } 213 | 214 | buildDefaultTemplate() { 215 | const templateDefault = [ 216 | { 217 | label: '&File', 218 | submenu: [ 219 | { 220 | label: '&Open', 221 | accelerator: 'Ctrl+O', 222 | }, 223 | { 224 | label: '&Close', 225 | accelerator: 'Ctrl+W', 226 | click: () => { 227 | this.mainWindow.close(); 228 | }, 229 | } 230 | ], 231 | }, 232 | { 233 | label: '&View', 234 | submenu: 235 | process.env.NODE_ENV === 'development' || 236 | process.env.DEBUG_PROD === 'true' 237 | ? [ 238 | { 239 | label: '&Reload', 240 | accelerator: 'Ctrl+R', 241 | click: () => { 242 | this.mainWindow.webContents.reload(); 243 | }, 244 | }, 245 | { 246 | label: 'Toggle &Full Screen', 247 | accelerator: 'F11', 248 | click: () => { 249 | this.mainWindow.setFullScreen( 250 | !this.mainWindow.isFullScreen() 251 | ); 252 | }, 253 | }, 254 | { 255 | label: 'Toggle &Developer Tools', 256 | accelerator: 'Alt+Ctrl+I', 257 | click: () => { 258 | this.mainWindow.webContents.toggleDevTools(); 259 | }, 260 | }, 261 | ] 262 | : [ 263 | { 264 | label: 'Toggle &Full Screen', 265 | accelerator: 'F11', 266 | click: () => { 267 | this.mainWindow.setFullScreen( 268 | !this.mainWindow.isFullScreen() 269 | ); 270 | }, 271 | }, 272 | { 273 | label: '&Reload', 274 | accelerator: 'Ctrl+R', 275 | click: () => { 276 | this.mainWindow.webContents.reload(); 277 | }, 278 | }, 279 | { 280 | label: 'Toggle &Developer Tools', 281 | accelerator: 'Alt+Ctrl+I', 282 | click: () => { 283 | this.mainWindow.webContents.toggleDevTools(); 284 | }, 285 | }, 286 | ], 287 | }, 288 | { 289 | label: 'Help', 290 | submenu: [ 291 | { 292 | label: 'Learn More', 293 | click() { 294 | shell.openExternal('https://https://github.com/AnthonyGress/Android-Toolkit/'); 295 | }, 296 | }, 297 | { 298 | label: 'Documentation', 299 | click() { 300 | shell.openExternal( 301 | 'https://github.com/AnthonyGress/Android-Toolkit/#readme' 302 | ); 303 | }, 304 | }, 305 | { 306 | label: 'Search/Report Issues', 307 | click() { 308 | shell.openExternal('https://github.com/AnthonyGress/Android-Toolkit/issues'); 309 | }, 310 | }, 311 | ], 312 | }, 313 | ]; 314 | 315 | return templateDefault; 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/main/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron'); 2 | 3 | // sends message to main 4 | const WINDOW_API = { 5 | shellCommand: (command) => ipcRenderer.send('shellChannel', command), 6 | adbCommand: (command) => ipcRenderer.send('adbChannel', command), 7 | coms: (args) => ipcRenderer.send('communicationChannel', args), 8 | }; 9 | 10 | const windowLoaded = new Promise((resolve) => { 11 | window.onload = resolve; 12 | }); 13 | 14 | ipcRenderer.on('startup', async (event, arg) => { 15 | await windowLoaded; 16 | window.postMessage(arg, '*'); 17 | }); 18 | 19 | // listens for messages from main 20 | ipcRenderer.on('adbResponse', async (event, arg) => { 21 | // console.log(arg); // logs response from adbCommand 22 | await windowLoaded; 23 | window.postMessage(arg, '*'); 24 | }); 25 | 26 | ipcRenderer.on('shellResponse', async (event, arg) => { 27 | // console.log(arg); // logs response from shellCommand 28 | await windowLoaded; 29 | window.postMessage(arg, '*'); 30 | }); 31 | 32 | // window.api 33 | contextBridge.exposeInMainWorld('api', WINDOW_API); 34 | -------------------------------------------------------------------------------- /src/main/types/index.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from 'electron'; 2 | 3 | export type MainWindow = BrowserWindow | null 4 | -------------------------------------------------------------------------------- /src/main/utils/appUpdater.ts: -------------------------------------------------------------------------------- 1 | // import log from 'electron-log'; 2 | // import { autoUpdater } from 'electron-updater'; 3 | import { IpcMainEvent } from 'electron'; 4 | import { spawn } from 'node:child_process'; 5 | import { Octokit } from 'octokit'; 6 | import { downloadFile } from './downloadFile'; 7 | import packageJson from '../../../release/app/package.json'; 8 | import semverCompare from 'semver/functions/compare'; 9 | import fs from 'fs'; 10 | import { USERNAME, USER_OS } from '../constants'; 11 | import { execPromise } from './executeCmd'; 12 | 13 | // Cannot use updater unless codesigning with paid credentials for macOS 14 | // export default class AppUpdater { 15 | // constructor() { 16 | // log.transports.file.level = 'info' 17 | // autoUpdater.logger = log; 18 | // autoUpdater.checkForUpdatesAndNotify(); 19 | // } 20 | // } 21 | 22 | export const checkForUpdates = async () => { 23 | let updateAvailable = false; 24 | const octokit = new Octokit(); 25 | 26 | const res = await octokit.request('GET /repos/{owner}/{repo}/releases/latest', { 27 | owner: 'AnthonyGress', 28 | repo: packageJson.name, 29 | headers: { 30 | 'X-GitHub-Api-Version': '2022-11-28' 31 | } 32 | }); 33 | 34 | const latestVersion = res.data.name; 35 | const runningVersion = packageJson.version; 36 | 37 | if (latestVersion){ 38 | const newVersionAvailable = semverCompare(latestVersion, runningVersion); 39 | 40 | if (newVersionAvailable === 1) { 41 | updateAvailable = true; 42 | } 43 | } 44 | return updateAvailable; 45 | }; 46 | 47 | 48 | export const startUpdate = async (event: IpcMainEvent) => { 49 | if (USER_OS === 'win32') { 50 | updateWindows(event); 51 | } else if (USER_OS === 'darwin' || USER_OS === 'linux') { 52 | await nixUpdate(); 53 | } 54 | }; 55 | 56 | export const updateWindows = (event: IpcMainEvent) => { 57 | 58 | const downloadPathWin = `C:\\Users\\${USERNAME}\\Downloads`; 59 | console.log('running windows update'); 60 | 61 | fs.mkdir(`${downloadPathWin}\\Android-Toolkit-Update\\`, (err) => { 62 | if (err) console.log(err); 63 | }); 64 | 65 | downloadFile(`https://github.com/anthonygress/${packageJson.name}/releases/latest/download/${packageJson.name}-setup.exe`, `${downloadPathWin}\\Android-Toolkit-Update\\Android-Toolkit-Update.exe`).then(() => { 66 | event.reply('shellResponse', 'win update downloaded'); 67 | spawn('explorer', [`${downloadPathWin}\\Android-Toolkit-Update\\`], { detached: true }).unref(); 68 | }); 69 | }; 70 | 71 | 72 | export const nixUpdate = async () => { 73 | let updateCompleted = false; 74 | console.log('running *nix update'); 75 | await execPromise('/bin/bash -c "$(curl -sL https://raw.githubusercontent.com/AnthonyGress/Android-Toolkit/main/install.sh)"'); 76 | updateCompleted = true; 77 | return updateCompleted; 78 | }; 79 | -------------------------------------------------------------------------------- /src/main/utils/batchInstall.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { IpcMainEvent, dialog } from 'electron'; 4 | import { executeCmd } from './executeCmd'; 5 | import { ADB_PATH } from '../constants'; 6 | 7 | export const batchInstall = (event: IpcMainEvent) => { 8 | let files: string[]; 9 | const extension = '.apk'; 10 | const dir = dialog.showOpenDialogSync({ 11 | properties: [ 12 | 'openFile', 13 | 'openDirectory' 14 | ] 15 | }); 16 | 17 | if (dir) { 18 | const folderName = path.win32.basename(dir[0]).split(path.sep)[0]; 19 | 20 | dialog.showMessageBox({ 21 | 'type': 'question', 22 | 'title': 'Confirmation', 23 | 'message': `Are you sure you want to install all apk's in the ${folderName} folder?`, 24 | 'buttons': [ 25 | 'Yes', 26 | 'Cancel' 27 | ] 28 | }) 29 | // Dialog returns a promise so let's handle it correctly 30 | .then((result) => { 31 | // exit, use selected no 32 | if (result.response !== 0) { return; } 33 | 34 | // user confirmed 35 | if (result.response === 0) { 36 | event.reply('adbResponse', 'Starting batch install, please wait...'); 37 | files = fs.readdirSync(dir[0]); 38 | files = files.filter(file => file.endsWith(extension)); 39 | console.log(files); 40 | const total = files.length; 41 | 42 | files.forEach((file) => { 43 | const filePath = path.join(dir[0], file); 44 | 45 | executeCmd(`${ADB_PATH}adb install -r "${filePath}"`, event, 'adbResponse', file, total); 46 | }); 47 | } 48 | 49 | }); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/main/utils/downloadFile.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { createWriteStream } from 'fs'; 3 | 4 | export const downloadFile = async (fileUrl: string, outputLocationPath: string) => { 5 | const writer = createWriteStream(outputLocationPath); 6 | 7 | return axios({ 8 | method: 'get', 9 | url: fileUrl, 10 | responseType: 'stream', 11 | headers: { 'User-Agent':'OpenGApps APKMirrorCrawler/1.0' } 12 | }).then(response => { 13 | return new Promise((resolve, reject) => { 14 | response.data.pipe(writer); 15 | let error: Error | null = null; 16 | writer.on('error', (err: Error) => { 17 | error = err; 18 | writer.close(); 19 | reject(err); 20 | }); 21 | writer.on('close', () => { 22 | if (!error) { 23 | resolve(true); 24 | } 25 | }); 26 | }); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/main/utils/executeCmd.ts: -------------------------------------------------------------------------------- 1 | const { spawn } = require('node:child_process'); 2 | import util from 'node:util'; 3 | 4 | const { exec } = require('child_process'); 5 | export const execPromise = util.promisify(exec); 6 | 7 | let count = 0; 8 | 9 | export const executeCmd = ( 10 | command: string, 11 | event: Electron.IpcMainEvent, 12 | callbackChannel: string, 13 | filename?: string, 14 | total = 1 15 | ) => { 16 | exec(command, { maxBuffer: 1024 * 5000 }, (error: Error, stdout: string, stderr: Error) => { 17 | if (error) { 18 | console.log(`error: ${error.message}`); 19 | const errArr = error.message.split(/\r?\n/); 20 | const formattedError = errArr.filter((e) => e).at(-1); 21 | console.log('###############',errArr); 22 | 23 | 24 | event.reply(callbackChannel, `Error: ${formattedError}`); 25 | if (command.toLocaleLowerCase().includes('install') && filename){ 26 | count ++; 27 | event.reply(callbackChannel, `Failed to install ${filename} ${count}/${total} \n\n ${formattedError}`); 28 | if (count === total) { 29 | //reset count on completion 30 | count = 0; 31 | } 32 | } 33 | return; 34 | } 35 | if (stderr) { 36 | console.log(`stderr: ${stderr}`); 37 | event.reply(callbackChannel, `StdError: ${stderr}`); 38 | return; 39 | } 40 | if (stdout) { 41 | console.log(`${stdout}`); 42 | event.reply(callbackChannel, `${stdout}`); 43 | 44 | if (command.toLocaleLowerCase().includes('install') && filename){ 45 | count ++; 46 | event.reply(callbackChannel, `Successfully installed ${filename} ${count}/${total}`); 47 | if (count === total) { 48 | //reset count on completion 49 | count = 0; 50 | } 51 | } 52 | 53 | } 54 | }); 55 | }; 56 | 57 | export const spawnShell = ( 58 | command: string, 59 | event: Electron.IpcMainEvent, 60 | callbackChannel: string, 61 | input?: string 62 | ) => { 63 | let commandArr = command.split(' '); 64 | const commandBase = commandArr[0]; 65 | commandArr = commandArr.slice(1); 66 | 67 | const child = spawn(commandBase, commandArr); 68 | 69 | child.stdout.on('data', (data: any) => { 70 | child.stdin.write(input); 71 | child.stdin.end(); // EOF 72 | 73 | event.reply(callbackChannel, `${data}`); 74 | }); 75 | 76 | child.stderr.on('data', (data: any) => { 77 | console.error(`stderr: ${data}`); 78 | event.reply(callbackChannel, `${data}`); 79 | }); 80 | 81 | child.on('close', (code: number) => { 82 | console.log(`Child process exited with code ${code}.`); 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /src/main/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { installExtensions } from './installExtensions'; 2 | import { resolveHtmlPath } from './resolveHtmlPath'; 3 | import { execPromise, executeCmd } from './executeCmd'; 4 | import { batchInstall } from './batchInstall'; 5 | import { checkForUpdates } from './appUpdater'; 6 | 7 | export { installExtensions, resolveHtmlPath, executeCmd, execPromise, batchInstall, checkForUpdates }; 8 | -------------------------------------------------------------------------------- /src/main/utils/installExtensions.ts: -------------------------------------------------------------------------------- 1 | export const installExtensions = async () => { 2 | const installer = require('electron-devtools-installer'); 3 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 4 | const extensions = ['REACT_DEVELOPER_TOOLS']; 5 | 6 | return installer 7 | .default( 8 | extensions.map((name) => installer[name]), 9 | forceDownload 10 | ) 11 | .catch(console.log); 12 | }; 13 | -------------------------------------------------------------------------------- /src/main/utils/resolveHtmlPath.ts: -------------------------------------------------------------------------------- 1 | import { URL } from 'url'; 2 | import path from 'path'; 3 | 4 | export let resolveHtmlPath: (htmlFileName: string) => string; 5 | 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | resolveHtmlPath = (htmlFileName: string) => { 9 | const url = new URL(`http://localhost:${port}`); 10 | url.pathname = htmlFileName; 11 | return url.href; 12 | }; 13 | } else { 14 | resolveHtmlPath = (htmlFileName: string) => { 15 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/renderer/App.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.cdnfonts.com/css/earth-orbiter'); 2 | 3 | html { 4 | scroll-behavior: smooth; 5 | } 6 | 7 | body { 8 | position: relative; 9 | color: white; 10 | background: linear-gradient( 11 | 200.96deg, 12 | #fedc2a -29.09%, 13 | #dd5789 51.77%, 14 | #7a2c9e 129.35% 15 | ); 16 | font-family: sans-serif; 17 | background-repeat: no-repeat; 18 | background-attachment: fixed; 19 | } 20 | 21 | main { 22 | width: 100%; 23 | } 24 | 25 | button { 26 | position: relative; 27 | background-color: white; 28 | padding: 10px 20px; 29 | border-radius: 10px; 30 | border: none; 31 | appearance: none; 32 | font-size: 1rem; 33 | box-shadow: 0px 8px 28px -6px rgba(24, 39, 75, 0.12), 34 | 0px 18px 88px -4px rgba(24, 39, 75, 0.14); 35 | transition: all ease-in 0.1s; 36 | cursor: pointer; 37 | opacity: 0.9; 38 | -webkit-user-drag: none; 39 | -webkit-user-select: none; 40 | user-select: none; 41 | z-index: 10; 42 | } 43 | 44 | button:hover { 45 | transform: scale(1.03); 46 | opacity: 1; 47 | } 48 | 49 | li { 50 | list-style: none; 51 | } 52 | 53 | pre { 54 | white-space: pre-wrap; 55 | word-break: normal; 56 | word-wrap: normal; 57 | } 58 | 59 | a { 60 | text-decoration: none; 61 | height: fit-content; 62 | width: fit-content; 63 | opacity: 0.9; 64 | /* margin: 10px; */ 65 | } 66 | 67 | a:hover { 68 | opacity: 1; 69 | text-decoration: none; 70 | } 71 | 72 | .button-group { 73 | display: flex; 74 | flex-direction: row; 75 | justify-content: center; 76 | align-items: center; 77 | margin-top: 1rem; 78 | margin-bottom: 1rem; 79 | } 80 | 81 | .center { 82 | display: flex; 83 | justify-content: center; 84 | align-items: center; 85 | } 86 | 87 | .dollar { 88 | font-size: 0.95rem; 89 | margin-right: 0.25rem; 90 | } 91 | 92 | .dashboard { 93 | align-items: center; 94 | } 95 | 96 | .footer-btns { 97 | position:fixed; 98 | display: flex; 99 | justify-content: space-between; 100 | top: 96vh; 101 | width: 98%; 102 | z-index: 0; 103 | } 104 | 105 | .signature { 106 | font-family: 'Earth Orbiter'; 107 | font-size: 1.2rem; 108 | color: #000; 109 | } 110 | 111 | .splash { 112 | display: flex; 113 | justify-content: center; 114 | align-items: center; 115 | margin-bottom: 2vh; 116 | margin-top: 1vh; 117 | -webkit-user-drag: none; 118 | -webkit-user-select: none; 119 | user-select: none; 120 | 121 | } 122 | 123 | .spin { 124 | animation: App-logo-spin infinite 16s linear; 125 | -webkit-user-drag: none; 126 | -webkit-user-select: none; 127 | user-select: none; 128 | } 129 | 130 | .ip-input { 131 | height: 2rem; 132 | min-width: 30vw; 133 | border-radius: 8px; 134 | border-color: transparent !important; 135 | appearance: none; 136 | font-size: 1rem; 137 | background-color: white; 138 | color: black; 139 | } 140 | 141 | .output-terminal { 142 | background-color: black; 143 | height: 28vh; 144 | display: flex; 145 | justify-content: flex-start; 146 | align-items: flex-start; 147 | border-radius: 8px; 148 | opacity: .95; 149 | z-index: 999 !important; 150 | max-width: 65vw; 151 | flex: 1; 152 | overflow-y: scroll !important; 153 | } 154 | 155 | .output-terminal::-webkit-scrollbar, body::-webkit-scrollbar{ 156 | display: none; 157 | } 158 | 159 | .output-text { 160 | color: rgb(153, 255, 0); 161 | margin-left: 10px; 162 | margin-right: 10px; 163 | max-width: 56vw !important; 164 | overflow-wrap: break-word; 165 | font-family: monospace; 166 | font-weight: 500; 167 | font-size: 0.8rem; 168 | } 169 | 170 | .pairing-code { 171 | height: 2rem; 172 | max-width: 20vw; 173 | border-radius: 8px; 174 | border-color: transparent; 175 | appearance: none; 176 | font-size: 1rem; 177 | background-color: white; 178 | color: black; 179 | } 180 | 181 | .swal2-confirm { 182 | background-color: #cf3886 !important; 183 | outline: none !important; 184 | } 185 | 186 | .transparent-bg { 187 | background-color: transparent !important; 188 | box-shadow: none !important; 189 | } 190 | 191 | .terminal-wrapper { 192 | width: 100%; 193 | position: sticky; 194 | position: -webkit-sticky; 195 | z-index: 999 !important; 196 | top: 0 197 | } 198 | 199 | .vcenter { 200 | display: flex; 201 | flex-direction: column; 202 | align-items: center; 203 | height: 100%; 204 | } 205 | 206 | .visible { 207 | visibility: visible; 208 | opacity: 1; 209 | transition: opacity .75s ease-in-out; 210 | } 211 | 212 | .hidden { 213 | visibility: hidden; 214 | opacity: 0; 215 | transition: visibility 0s .75s, opacity .75s ease-in-out; 216 | } 217 | 218 | .bounce { 219 | animation: bounce 1s linear; 220 | animation-iteration-count: 2; 221 | } 222 | 223 | @keyframes App-logo-spin { 224 | from { 225 | transform: rotate(0deg); 226 | } 227 | to { 228 | transform: rotate(360deg); 229 | } 230 | } 231 | 232 | @keyframes fadein { 233 | from { 234 | opacity: 0; 235 | } 236 | to { 237 | opacity: 1; 238 | } 239 | } 240 | 241 | @keyframes fadeout { 242 | from { 243 | opacity: 1; 244 | } 245 | to { 246 | opacity: 0; 247 | } 248 | } 249 | 250 | @keyframes bounce { 251 | 0%, 20%, 50%, 80%, 100% {transform: translateY(0);} 252 | 40% {transform: translateY(-15px);} 253 | 60% {transform: translateY(-5px);} 254 | } 255 | -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import icon from '../../assets/icons/logo.png'; 3 | import packageJson from '../../release/app/package.json'; 4 | import InfoIcon from '@mui/icons-material/Info'; 5 | import RefreshIcon from '@mui/icons-material/Refresh'; 6 | import Swal from 'sweetalert2'; 7 | import { Box, Typography } from '@mui/material'; 8 | import { useRef, useState } from 'react'; 9 | import { Route, MemoryRouter as Router, Switch } from 'react-router-dom'; 10 | import { AccordionDropdown } from './components/AccordionDropwdown'; 11 | import { ConnectionActions } from './components/ConnectionActions'; 12 | import { TvActions } from './components/TvActions'; 13 | import { ApkActions } from './components/ApkActions'; 14 | import { SystemActions } from './components/SystemActions'; 15 | import { ITerminalContext } from './context/TerminalContext'; 16 | import { TerminalProvider } from './context/TerminalProvider'; 17 | import { useTerminalContext } from './context/useTerminalContext'; 18 | import { UpdateBtn } from './components/UpdateBtn'; 19 | import { RevancedActions } from './components/RevancedActions'; 20 | 21 | declare global { 22 | interface Window { 23 | api?: any; 24 | } 25 | } 26 | 27 | const Main = () => { 28 | const [showInfoPage, setShowInfoPage] = useState(false); 29 | const [updateAvailable, setUpdateAvailable] = useState(false); 30 | const terminal: ITerminalContext = useTerminalContext(); 31 | const outputRef = useRef(null); 32 | 33 | window.addEventListener('message', (event: MessageEvent) => { 34 | if (event.source === window && typeof event.data === 'string') { 35 | let stringData = JSON.stringify(event.data); 36 | stringData = stringData.replace(new RegExp('\\\\n', 'g'), '\n'); 37 | stringData = stringData.replace(new RegExp('\\\\r', 'g'), '\n'); 38 | if (stringData.includes('List of devices')){ 39 | stringData = stringData.replace(new RegExp('\\\\t', 'g'), ' '); 40 | } 41 | 42 | stringData = stringData.slice(1, -1); 43 | terminal.setTerminalOutput(stringData); 44 | 45 | if (event.data.includes('Update Available')){ 46 | setUpdateAvailable(true); 47 | } 48 | 49 | if (event.data.includes('starting update')){ 50 | Swal.fire({ 51 | customClass: { 52 | title: 'swal2-title', 53 | }, 54 | title: 'Updating', 55 | text: 'The update is in progress, please wait...', 56 | icon: 'info', 57 | showConfirmButton: false, 58 | allowOutsideClick: false 59 | }); 60 | } 61 | 62 | if (event.data.includes('update complete')){ 63 | Swal.fire({ 64 | customClass: { 65 | title: 'swal2-title', 66 | }, 67 | title: 'Done!', 68 | text: 'Update successful! Please restart the app.', 69 | icon: 'success', 70 | confirmButtonText: 'Restart', 71 | }).then((result) => { 72 | if (result.isConfirmed) { 73 | window.api.coms('restart'); 74 | } 75 | }); 76 | } 77 | 78 | if (event.data.includes('win update downloaded')){ 79 | Swal.fire({ 80 | customClass: { 81 | title: 'swal2-title', 82 | }, 83 | title: 'Update Downloaded', 84 | text: 'Please run the installer by double clicking on it in your Downloads folder.', 85 | icon: 'success', 86 | confirmButtonText: 'Ok', 87 | }); 88 | } 89 | } 90 | 91 | }); 92 | 93 | return ( 94 | <> 95 | 96 | icon 97 | Android Toolkit 98 | 99 | {updateAvailable && } 100 | 101 | 102 | 103 |
104 |                             $
105 |                             {terminal.terminalOutput}
106 |                         
107 |
108 |
109 |
110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | {`Version: ${packageJson.version}`} 135 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | { 152 | if (!showInfoPage){ 153 | window.scrollTo(0, document.body.scrollHeight); 154 | } 155 | setShowInfoPage(!showInfoPage); 156 | }}/> 157 | location.reload()}/> 158 | 159 | 160 | 161 | ); 162 | }; 163 | 164 | export default function App() { 165 | 166 | return ( 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | ); 175 | } 176 | -------------------------------------------------------------------------------- /src/renderer/api/index.ts: -------------------------------------------------------------------------------- 1 | import { AdbCommand, ShellCommand } from '../types'; 2 | 3 | export const adbCommand: AdbCommand = (command) => { 4 | window.api.adbCommand(command); 5 | }; 6 | 7 | export const shellCommand: ShellCommand = (command) => { 8 | window.api.shellCommand(command); 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/components/AccordionDropwdown.tsx: -------------------------------------------------------------------------------- 1 | import { Accordion, AccordionDetails, AccordionSummary, Box, Typography } from '@mui/material'; 2 | import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; 3 | 4 | interface Props { 5 | title: string, 6 | children?: React.ReactNode, 7 | defaultExpanded?: boolean 8 | } 9 | 10 | export const AccordionDropdown = ({ 11 | title, 12 | children, 13 | defaultExpanded=false 14 | }: Props) => { 15 | return ( 16 | 17 | 18 | } 20 | aria-controls="panel1a-content" 21 | id="panel1a-header" 22 | sx={{ 23 | backgroundColor: 'white', 24 | borderRadius: '8px' 25 | }} 26 | 27 | > 28 | {title} 29 | 30 | 31 | {children} 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /src/renderer/components/ApkActions.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, useRef, useState } from 'react'; 2 | import { Box, Divider, Grid, Typography } from '@mui/material'; 3 | import { FixedWidthBtn } from './FixedWidthBtn'; 4 | import { useTerminalContext } from '../context/useTerminalContext'; 5 | 6 | import ClearIcon from '@mui/icons-material/Clear'; 7 | import { adbCommand } from '../api'; 8 | 9 | export const ApkActions = () => { 10 | const [selectedFile, setSelectedFile] = useState(null); 11 | const terminal = useTerminalContext(); 12 | const fileRef = useRef(); 13 | 14 | const handleChangeFile =(e: ChangeEvent) => { 15 | const target = e.target as HTMLInputElement; 16 | const files = target.files; 17 | 18 | if (files) { 19 | setSelectedFile(files[0]); 20 | } 21 | }; 22 | 23 | const installApk = () => { 24 | if (selectedFile) { 25 | console.log(`Selected file - ${selectedFile.path}`); 26 | terminal?.setTerminalOutput(`Installing ${selectedFile.name}....`); 27 | adbCommand(`adb install -r -d "${selectedFile.path}"`); 28 | } 29 | }; 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | Install Single APK 37 | 38 | 39 | 40 | handleChangeFile(e)} 42 | type="file" 43 | name="files" 44 | accept='.apk' 45 | className="form-control" 46 | ref={fileRef} 47 | style={{ display: 'none' }} 48 | /> 49 | 50 | 51 | fileRef.current.click()} title='Select APK File'/> 52 | 53 | 54 | 55 | {selectedFile ? `${selectedFile.name}` : 'No File Selected'} 56 | 57 | 58 | { 59 | fileRef.current.value = null; 60 | setSelectedFile(null); 61 | }}/> 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Batch Install APKs 75 | 76 | 77 | 78 | 79 | 80 | adbCommand('batchInstall')} title='Select APK Folder'/> 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | Quick Install TV Apps 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Quick Install Mobile Apps 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | ); 131 | }; 132 | -------------------------------------------------------------------------------- /src/renderer/components/ConnectionActions.tsx: -------------------------------------------------------------------------------- 1 | import { FormEvent, useState } from 'react'; 2 | import { FixedWidthBtn } from './FixedWidthBtn'; 3 | import { Autocomplete, Box, createFilterOptions, Divider, FilterOptionsState, InputAdornment, TextField, Typography } from '@mui/material'; 4 | import { adbCommand } from '../api'; 5 | import { styles } from '../theme'; 6 | import { v4 } from 'uuid'; 7 | 8 | export const ConnectionActions = () => { 9 | const [ipAddress, setIpAddress] = useState(''); 10 | const [pairingIp, setPairingIp] = useState(''); 11 | const [pairingCode, setPairingCode] = useState(''); 12 | 13 | const handleChange = (_event: any, newValue: string | null) => setIpAddress(newValue ? newValue : ''); 14 | 15 | const recentlyConnected = localStorage.getItem('recentlyConnected'); 16 | console.log('recentlyConnected', recentlyConnected); 17 | 18 | const recentlyConnectedArr: string[] | undefined = recentlyConnected && JSON.parse(recentlyConnected); 19 | console.log('recentlyConnectedArr', recentlyConnectedArr); 20 | 21 | if (recentlyConnectedArr && recentlyConnectedArr.length > 5) { 22 | for (let i = 0; i < recentlyConnectedArr.length - 5; i++) { 23 | recentlyConnectedArr.pop(); 24 | } 25 | } 26 | 27 | const onSubmitConnect = (e: FormEvent) => { 28 | e.preventDefault(); 29 | 30 | if (recentlyConnectedArr) { 31 | const existingIp = recentlyConnectedArr.find((e: string) => e === ipAddress); 32 | if (!existingIp && ipAddress) { 33 | recentlyConnectedArr.unshift(ipAddress); 34 | localStorage.setItem('recentlyConnected', JSON.stringify(recentlyConnectedArr)); 35 | } 36 | } else { 37 | localStorage.setItem('recentlyConnected', JSON.stringify([ipAddress])); 38 | } 39 | 40 | adbCommand(`adb connect ${ipAddress}`); 41 | }; 42 | 43 | const onSubmitPairing = (e: FormEvent) => { 44 | e.preventDefault(); 45 | console.log(`adb pair ${pairingIp},${pairingCode}`); 46 | 47 | adbCommand(`adb pair ${pairingIp},${pairingCode}`); 48 | }; 49 | 50 | const updateIp = (event: React.ChangeEvent) => { 51 | setIpAddress(event.target.value); 52 | }; 53 | 54 | const updatePairingIp = (event: React.ChangeEvent) => { 55 | setPairingIp(event.target.value); 56 | }; 57 | 58 | const updatePairingCode = ( 59 | event: React.ChangeEvent 60 | ) => { 61 | setPairingCode(event.target.value); 62 | }; 63 | 64 | return ( 65 | <> 66 | 67 | 68 | ADB Connect 69 | 70 | 71 |
72 | 73 | {/* */} 81 | 82 | 83 | 0 ? recentlyConnectedArr : []} 86 | renderOption={(props, option: string) => ( 87 |
  • 88 | {option} 89 |
  • 90 | )} 91 | renderInput={(params) => ( 92 | 102 | )} 103 | onChange={handleChange} 104 | sx={{ width: '95%', borderRadius: '8px', borderColor: 'white', '& .MuiOutlinedInput-root': { 105 | border: 'none', 106 | borderRadius: '8px', 107 | padding: '0' 108 | }, 109 | '& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline': { 110 | border: '1px solid #eee' 111 | } 112 | }} 113 | /> 114 |
    115 | 116 | 117 | 118 | 119 |
    120 |
    121 |
    122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | ADB Pair 136 | 137 |
    138 | 139 | 147 | 148 | 156 | 157 | 158 | 159 | 160 | 161 |
    162 |
    163 | 164 | 165 | ); 166 | }; 167 | -------------------------------------------------------------------------------- /src/renderer/components/FixedWidthBtn.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { useTerminalContext } from '../context/useTerminalContext'; 3 | import { AdbCommand } from '../types'; 4 | 5 | interface Props { 6 | adb?: AdbCommand, 7 | command?: string, 8 | title: string, 9 | customAction?: () => unknown, 10 | disabled?: boolean 11 | } 12 | 13 | export const FixedWidthBtn = ({ adb, command, title, customAction, disabled=false }: Props) => { 14 | const terminal = useTerminalContext(); 15 | 16 | const terminalFeedback = (actionTitle: string) => { 17 | switch (actionTitle) { 18 | case 'Connect': 19 | console.log('Connecting....'); 20 | terminal?.setTerminalOutput('Attempting to Connect....'); 21 | break; 22 | 23 | default: 24 | terminal?.setTerminalOutput(`Running Command for ${title}....`); 25 | break; 26 | } 27 | }; 28 | 29 | return ( 30 | 47 | ); 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /src/renderer/components/RevancedActions.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Grid, Typography } from '@mui/material'; 2 | import { FixedWidthBtn } from './FixedWidthBtn'; 3 | import { adbCommand } from '../api'; 4 | 5 | export const RevancedActions = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | Install Tools 12 | 13 | 14 | 15 | 16 | 17 | adbCommand('microg')} title='Install MicroG'/> 18 | adbCommand('revanced-manager')} title='Install Revanced Manager'/> 19 | adbCommand('youtube')} title='Install Patchable Youtube APK'/> 20 | adbCommand('reddit')} title='Install Patchable Reddit APK'/> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Revanced Guide (Unofficial) 33 | 34 | 35 | 36 | 37 | 38 | window.open('https://www.reddit.com/r/revancedapp/comments/xlcny9/revanced_manager_guide_for_dummies/')} title='Build Revanced Guide'/> 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /src/renderer/components/SystemActions.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Grid } from '@mui/material'; 2 | import { FixedWidthBtn } from './FixedWidthBtn'; 3 | import { adbCommand, shellCommand } from '../api'; 4 | 5 | export const SystemActions = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/renderer/components/TvActions.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Divider, Grid, Typography } from '@mui/material'; 2 | import { FixedWidthBtn } from './FixedWidthBtn'; 3 | import { DEBLOAT_CMDS } from '../constants/debloatCommands'; 4 | import { adbCommand } from '../api'; 5 | import { useTerminalContext } from '../context/useTerminalContext'; 6 | 7 | export const TvActions = () => { 8 | const terminal = useTerminalContext(); 9 | 10 | const screensaverTimeOn = () => { 11 | adbCommand('adb shell settings put system screen_off_timeout 120000'); 12 | terminal?.setTerminalOutput('Set screensaver to start after 2 mins'); 13 | }; 14 | 15 | const screensaverTimeOff = () => { 16 | adbCommand('adb shell settings put secure sleep_timeout 18000000'); 17 | terminal?.setTerminalOutput('Set screensaver to stop after 5 hrs'); 18 | 19 | }; 20 | 21 | // const setScreensaver = () => { 22 | // adbCommand( 23 | // 'adb shell settings put secure screensaver_components uk.co.liamnewmarch.daydream/uk.co.liamnewmarch.daydream.WebsiteDaydreamService ' 24 | // ); 25 | 26 | // setTimeout( 27 | // () => 28 | // adbCommand( 29 | // 'adb shell settings get secure screensaver_components' 30 | // ), 31 | // 2000 32 | // ); 33 | // }; 34 | 35 | // const resetScreensaver = () => { 36 | // adbCommand( 37 | // 'adb shell settings put secure screensaver_components com.amazon.bueller.photos/.daydream.ScreenSaverService' 38 | // ); 39 | // setTimeout( 40 | // () => 41 | // adbCommand( 42 | // 'adb shell settings get secure screensaver_components' 43 | // ), 44 | // 1000 45 | // ); 46 | // }; 47 | 48 | const screensaverDemo = () => { 49 | window.open('https://clients3.google.com/cast/chromecast/home/v/c9541b08'); 50 | }; 51 | 52 | const debloat = () => { 53 | DEBLOAT_CMDS.map((command) => adbCommand(command)); 54 | }; 55 | 56 | return ( 57 | <> 58 | 59 | 60 | Screensaver Tools 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {/* 77 | */} 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | FireOS Debloat Tools 87 | 88 | 89 | 90 | 91 | 92 | 93 | ); 94 | }; 95 | -------------------------------------------------------------------------------- /src/renderer/components/UpdateBtn.tsx: -------------------------------------------------------------------------------- 1 | import ArrowCircleDownOutlinedIcon from '@mui/icons-material/ArrowCircleDownOutlined'; 2 | import { Box, Typography } from '@mui/material'; 3 | import { shellCommand } from '../api'; 4 | 5 | export const UpdateBtn = () => { 6 | 7 | const handleUpdate = () => { 8 | shellCommand('update'); 9 | }; 10 | 11 | return ( 12 | 13 | 14 | Update Available 15 | 16 | ); 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/constants/debloatCommands.ts: -------------------------------------------------------------------------------- 1 | export const DEBLOAT_CMDS = [ 2 | 'adb shell pm disable-user com.amazon.recess', 3 | 'adb shell pm disable-user com.amazon.platform.fdrw', 4 | 'adb shell pm disable-user com.amazon.ssmsys', 5 | 'adb shell pm disable-user com.amazon.whisperplay.contracts', 6 | 'adb shell pm disable-user com.ivona.orchestrator', 7 | 'adb shell pm disable-user com.amazon.device.sync', 8 | 'adb shell pm disable-user com.amazon.device.logmanager', 9 | 'adb shell pm disable-user com.amazon.dpcclient', 10 | 'adb shell pm disable-user com.amazon.ceviche', 11 | 'adb shell pm disable-user com.amazon.alta.h2clientservice', 12 | 'adb shell pm disable-user com.amazon.ftv.screensaver', 13 | 'adb shell pm disable-user com.amazon.ftv.glorialist', 14 | 'adb shell pm disable-user com.amazon.ods.kindleconnect', 15 | 'adb shell pm disable-user com.android.providers.downloads.ui', 16 | 'adb shell pm disable-user com.amazon.sharingservice.android.client.proxy', 17 | 'adb shell pm disable-user com.amazon.android.marketplace', 18 | 'adb shell pm disable-user com.amazon.tifobserver', 19 | 'adb shell pm disable-user com.amazon.aca', 20 | 'adb shell pm disable-user com.amazon.ale', 21 | 'adb shell pm disable-user com.amazon.dcp', 22 | 'adb shell pm disable-user com.amazon.ssm', 23 | 'adb shell pm disable-user com.amazon.tv.csapp', 24 | 'adb shell pm disable-user com.amazon.tahoe', 25 | 'adb shell pm disable-user com.amazon.naatyam', 26 | 'adb shell pm disable-user com.amazon.tv.fw.metrics', 27 | 'adb shell pm disable-user com.amazon.firehomestarter', 28 | 'adb shell pm disable-user com.amazon.securitysyncclient', 29 | 'adb shell pm disable-user com.amazon.device.sale.service', 30 | 'adb shell pm disable-user com.amazon.cardinal', 31 | 'adb shell pm disable-user com.amazon.tcomm.client', 32 | 'adb shell pm disable-user com.amazon.tv.forcedotaupdater.v2', 33 | 'adb shell pm disable-user com.amazon.client.metrics', 34 | 'adb shell pm disable-user amazon.jackson19', 35 | 'adb shell pm disable-user com.android.managedprovisioning', 36 | 'adb shell pm disable-user com.amazon.imdb.tv.android.app', 37 | 'adb shell pm disable-user com.amazon.tmm.tutorial', 38 | 'adb shell pm disable-user com.amazon.device.software.ota', 39 | 'adb shell pm disable-user com.amazon.dcp.contracts.framework.library', 40 | 'adb shell pm disable-user com.amazon.tv.support', 41 | 'adb shell pm disable-user com.amazon.ags.app', 42 | 'adb shell pm disable-user com.amazon.tv.nimh', 43 | 'adb shell pm disable-user com.amazon.sync.provider.ipc', 44 | 'adb shell pm disable-user com.amazon.tv.legal.notices', 45 | 'adb shell pm disable-user android.amazon.perm', 46 | 'adb shell pm disable-user com.amazon.kso.blackbird', 47 | 'adb shell pm disable-user com.amazon.providers.contentsupport', 48 | 'adb shell pm disable-user com.amazon.device.crashmanager', 49 | 'adb shell pm disable-user com.amazon.whisperjoin.middleware.np', 50 | 'adb shell pm disable-user com.amazon.application.compatibility.enforcer', 51 | 'adb shell pm disable-user com.amazon.whisperplay.service.install', 52 | 'adb shell pm disable-user com.ivona.tts.oem', 53 | 'adb shell pm disable-user com.amazon.tv.launcher', 54 | 'adb shell pm disable-user com.amazon.shoptv.client', 55 | 'adb shell pm disable-user com.amazon.device.software.ota.override', 56 | 'adb shell pm disable-user com.amazon.alexashopping', 57 | 'adb shell pm disable-user com.amazon.communication.discovery', 58 | 'adb shell pm disable-user com.amazon.tv.releasenotes', 59 | 'adb shell pm disable-user com.amazon.device.sync.sdk.internal', 60 | 'adb shell pm disable-user com.amazon.hedwig', 61 | 'adb shell pm disable-user com.amazon.application.compatibility.enforcer.sdk.library', 62 | ]; 63 | -------------------------------------------------------------------------------- /src/renderer/context/TerminalContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export interface ITerminalContext { 4 | terminalOutput: string; 5 | setTerminalOutput: React.Dispatch> 6 | } 7 | 8 | export const TerminalContext = createContext(null!); 9 | -------------------------------------------------------------------------------- /src/renderer/context/TerminalProvider.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { TerminalContext } from './TerminalContext'; 3 | 4 | type Props = { 5 | children: React.ReactNode 6 | }; 7 | 8 | export const TerminalProvider: React.FC= ({ children } ) => { 9 | const [terminalOutput, setTerminalOutput] = useState(''); 10 | 11 | const { Provider } = TerminalContext; 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/renderer/context/useTerminalContext.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { TerminalContext } from './TerminalContext'; 3 | 4 | export const useTerminalContext = () => useContext(TerminalContext); 5 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Android Toolkit 6 | 7 | 8 |
    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom'; 2 | import App from './App'; 3 | 4 | render(, document.getElementById('root')); 5 | -------------------------------------------------------------------------------- /src/renderer/theme.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | center: { 3 | display: 'flex', 4 | justifyContent: 'center', 5 | alignItems: 'center' 6 | }, 7 | vcenter : { 8 | display: 'flex', 9 | flexDirection: 'column', 10 | justifyContent: 'center', 11 | alignItems: 'center', 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/renderer/types/index.ts: -------------------------------------------------------------------------------- 1 | export type AdbCommand = (command: string) => void; 2 | export type ShellCommand = (command: string) => void; 3 | 4 | export interface AdbProps { 5 | adbCommand: AdbCommand 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "commonjs", 5 | "lib": [ 6 | "esnext", 7 | "dom" 8 | ], 9 | "declaration": true, 10 | "declarationMap": true, 11 | "jsx": "react-jsx", 12 | "strict": true, 13 | "pretty": true, 14 | "sourceMap": true, 15 | "baseUrl": "./src", 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "moduleResolution": "node", 21 | "esModuleInterop": true, 22 | "allowSyntheticDefaultImports": true, 23 | "resolveJsonModule": true, 24 | "allowJs": true, 25 | "outDir": "release/app/dist" 26 | }, 27 | "exclude": [ 28 | "test", 29 | "release/build", 30 | "release/app/dist", 31 | ".erb/dll", 32 | "node_modules" 33 | ] 34 | } 35 | --------------------------------------------------------------------------------