diff --git a/package.json b/package.json index fadcdf53..50a1a64f 100644 --- a/package.json +++ b/package.json @@ -1,169 +1,171 @@ -{ - "name": "com.dergoogler.mmrl.web", - "description": "", - "config": { - "cname": "mmrl.dergoogler.com", - "application_id": "com.dergoogler.mmrl", - "min_sdk": 26, - "target_sdk": 34, - "version_name": "3.24.33", - "version_code": 32433, - "verified_hosts": [ - [ - "mmrl", - "i" - ], - [ - "localhost", - "i" - ], - [ - "mmrl.dergoogler.com", - "i" - ], - [ - "dergoogler.com", - "i" - ], - [ - "dergoogler.github.io", - "i" - ], - [ - "gr.dergoogler.com", - "i" - ], - [ - "googlers-repo.github.io", - "i" - ], - [ - "(localhost|\\b(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(?::\\d{0,4})?\\b)", - "g" - ] - ] - }, - "main": "index.tsx", - "keywords": [], - "author": "Der_Googler", - "license": "GPL-3.0", - "scripts": { - "start:dev": "webpack-dev-server --open --config webpack.dev.ts", - "start:prod": "npm run licensefix && webpack-dev-server --open --config webpack.prod.ts", - "web:dev": "webpack --config webpack.dev.ts", - "web:prod": "npm run licensefix && webpack --config webpack.prod.ts", - "web:prod-app": "npm run web:prod", - "licensefix": "node licensefix.js", - "deploy": "node deploy.js" - }, - "resolutions": { - "react": "^18.2.0", - "react-dom": "^18.1.0", - "@types/webpack": "^5.28.0" - }, - "dependencies": { - "@babel/runtime": "^7.23.2", - "@babel/standalone": "^7.24.0", - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@giscus/react": "^2.4.0", - "@monaco-editor/react": "^4.6.0", - "@mui/icons-material": "^5.16.5", - "@mui/lab": "^5.0.0-alpha.160", - "@mui/material": "^5.15.2", - "@nyariv/sandboxjs": "^0.8.23", - "@primer/octicons-react": "^19.9.0", - "@zenfs/core": "^0.17.1", - "@zenfs/dom": "^0.2.15", - "ajv": "^8.12.0", - "anser": "^2.1.1", - "axios": "^1.6.2", - "default-composer": "^0.6.0", - "eruda": "^3.0.0", - "escape-carriage": "^1.3.1", - "flatlist-react": "^1.5.14", - "googlers-tools": "^1.2.8", - "highlight.js": "^11.6.0", - "ini": "^4.1.1", - "linkify-it": "^5.0.0", - "localforage": "^1.10.0", - "markdown-to-jsx": "^7.4.0", - "material-icons": "^1.10.8", - "material-ui-confirm": "^3.0.16", - "modfs": "^1.4.2", - "monaco-editor": "^0.48.0", - "monaco-editor-core": "^0.50.0", - "monaco-languageclient": "^6.5.0", - "object-assign": "^4.1.1", - "onsenui": "^2.12.8", - "properties-file": "^3.2.10", - "react": "^18.2.0", - "react-device-detect": "^2.2.3", - "react-disappear": "^1.1.3", - "react-dom": "^18.2.0", - "react-fast-marquee": "^1.6.1", - "react-onsenui": "^1.13.2", - "react-render-tools": "^1.0.1", - "react-syntax-highlighter": "^15.5.0", - "react-transition-group": "^4.4.5", - "react-zoom-pan-pinch": "^3.3.0", - "reflect-metadata": "^0.2.2", - "underscore": "^1.13.6", - "usehooks-ts": "^3.1.0", - "uuid": "^10.0.0", - "yaml": "^2.3.4" - }, - "devDependencies": { - "@babel/core": "^7.24.0", - "@babel/preset-env": "^7.23.6", - "@babel/preset-react": "^7.23.3", - "@octokit/rest": "^21.0.1", - "@types/babel__core": "^7.20.2", - "@types/babel__standalone": "^7.1.7", - "@types/fs-extra": "^11.0.4", - "@types/gh-pages": "^6.1.0", - "@types/ini": "^4.1.0", - "@types/node": "^18.19.50", - "@types/object-assign": "^4.0.30", - "@types/react": "^18.2.67", - "@types/react-dom": "^18.0.2", - "@types/react-onsenui": "^2.9.17", - "@types/react-syntax-highlighter": "^15.5.2", - "@types/uglifyjs-webpack-plugin": "^1.1.2", - "@types/underscore": "^1.11.15", - "@types/webpack": "^5.28.5", - "babel-loader": "^9.1.3", - "buffer": "^6.0.3", - "cache-loader": "^4.1.0", - "commander": "^11.0.0", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^4.0.0", - "dotenv": "^16.4.5", - "file-loader": "^6.2.0", - "filemanager-webpack-plugin": "^8.0.0", - "fs-extra": "^11.2.0", - "gh-pages": "^6.1.1", - "html-webpack-plugin": "^5.6.0", - "image-webpack-loader": "^8.1.0", - "js-yaml-loader": "^1.2.2", - "license-checker": "^25.0.1", - "mini-css-extract-plugin": "^2.9.0", - "postcss-loader": "^7.3.3", - "raw-loader": "^4.0.2", - "sass": "^1.49.8", - "sass-loader": "^13.0.2", - "style-loader": "^3.3.3", - "terser-webpack-plugin": "^5.3.9", - "thread-loader": "^4.0.2", - "ts-loader": "^9.3.0", - "ts-node": "^10.9.2", - "tslib": "^2.4.0", - "typescript": "^5.2.2", - "url-loader": "^4.1.1", - "vscode": "^1.1.37", - "webpack": "^5.94.0", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", - "yaml-loader": "^0.8.0" - } -} +{ + "name": "com.dergoogler.mmrl.web", + "description": "", + "config": { + "cname": "mmrl.dergoogler.com", + "application_id": "com.dergoogler.mmrl", + "min_sdk": 26, + "target_sdk": 34, + "version_name": "3.24.33", + "version_code": 32433, + "verified_hosts": [ + [ + "mmrl", + "i" + ], + [ + "localhost", + "i" + ], + [ + "mmrl.dergoogler.com", + "i" + ], + [ + "dergoogler.com", + "i" + ], + [ + "dergoogler.github.io", + "i" + ], + [ + "gr.dergoogler.com", + "i" + ], + [ + "googlers-repo.github.io", + "i" + ], + [ + "(localhost|\\b(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(?::\\d{0,4})?\\b)", + "g" + ] + ] + }, + "main": "index.tsx", + "keywords": [], + "author": "Der_Googler", + "license": "GPL-3.0", + "scripts": { + "start:dev": "webpack-dev-server --open --config webpack.dev.ts", + "start:prod": "npm run licensefix && webpack-dev-server --open --config webpack.prod.ts", + "web:dev": "webpack --config webpack.dev.ts", + "web:prod": "npm run licensefix && webpack --config webpack.prod.ts", + "web:prod-app": "npm run web:prod", + "licensefix": "node licensefix.js", + "deploy": "node deploy.js" + }, + "resolutions": { + "react": "^18.2.0", + "react-dom": "^18.1.0", + "@types/webpack": "^5.28.0" + }, + "dependencies": { + "@babel/runtime": "^7.23.2", + "@babel/standalone": "^7.24.0", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@giscus/react": "^2.4.0", + "@monaco-editor/react": "^4.6.0", + "@mui/icons-material": "^5.16.5", + "@mui/lab": "^5.0.0-alpha.160", + "@mui/material": "^5.15.2", + "@nyariv/sandboxjs": "^0.8.23", + "@primer/octicons-react": "^19.9.0", + "@zenfs/core": "^0.17.1", + "@zenfs/dom": "^0.2.15", + "ajv": "^8.12.0", + "anser": "^2.1.1", + "axios": "^1.6.2", + "default-composer": "^0.6.0", + "eruda": "^3.0.0", + "escape-carriage": "^1.3.1", + "flatlist-react": "^1.5.14", + "googlers-tools": "^1.2.8", + "highlight.js": "^11.6.0", + "ini": "^4.1.1", + "linkify-it": "^5.0.0", + "localforage": "^1.10.0", + "markdown-to-jsx": "^7.4.0", + "material-icons": "^1.10.8", + "material-ui-confirm": "^3.0.16", + "modfs": "^1.4.2", + "monaco-editor": "^0.48.0", + "monaco-editor-core": "^0.50.0", + "monaco-languageclient": "^6.5.0", + "object-assign": "^4.1.1", + "onsenui": "^2.12.8", + "properties-file": "^3.2.10", + "react": "^18.2.0", + "react-device-detect": "^2.2.3", + "react-disappear": "^1.1.3", + "react-dom": "^18.2.0", + "react-fast-marquee": "^1.6.1", + "react-onsenui": "^1.13.2", + "react-render-tools": "^1.0.1", + "react-syntax-highlighter": "^15.5.0", + "react-transition-group": "^4.4.5", + "react-zoom-pan-pinch": "^3.3.0", + "reflect-metadata": "^0.2.2", + "semver": "^7.6.3", + "underscore": "^1.13.6", + "usehooks-ts": "^3.1.0", + "uuid": "^10.0.0", + "yaml": "^2.3.4" + }, + "devDependencies": { + "@babel/core": "^7.24.0", + "@babel/preset-env": "^7.23.6", + "@babel/preset-react": "^7.23.3", + "@octokit/rest": "^21.0.1", + "@types/babel__core": "^7.20.2", + "@types/babel__standalone": "^7.1.7", + "@types/fs-extra": "^11.0.4", + "@types/gh-pages": "^6.1.0", + "@types/ini": "^4.1.0", + "@types/node": "^18.19.50", + "@types/object-assign": "^4.0.30", + "@types/react": "^18.2.67", + "@types/react-dom": "^18.0.2", + "@types/react-onsenui": "^2.9.17", + "@types/react-syntax-highlighter": "^15.5.2", + "@types/semver": "^7.5.8", + "@types/uglifyjs-webpack-plugin": "^1.1.2", + "@types/underscore": "^1.11.15", + "@types/webpack": "^5.28.5", + "babel-loader": "^9.1.3", + "buffer": "^6.0.3", + "cache-loader": "^4.1.0", + "commander": "^11.0.0", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^4.0.0", + "dotenv": "^16.4.5", + "file-loader": "^6.2.0", + "filemanager-webpack-plugin": "^8.0.0", + "fs-extra": "^11.2.0", + "gh-pages": "^6.1.1", + "html-webpack-plugin": "^5.6.0", + "image-webpack-loader": "^8.1.0", + "js-yaml-loader": "^1.2.2", + "license-checker": "^25.0.1", + "mini-css-extract-plugin": "^2.9.0", + "postcss-loader": "^7.3.3", + "raw-loader": "^4.0.2", + "sass": "^1.49.8", + "sass-loader": "^13.0.2", + "style-loader": "^3.3.3", + "terser-webpack-plugin": "^5.3.9", + "thread-loader": "^4.0.2", + "ts-loader": "^9.3.0", + "ts-node": "^10.9.2", + "tslib": "^2.4.0", + "typescript": "^5.2.2", + "url-loader": "^4.1.1", + "vscode": "^1.1.37", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1", + "yaml-loader": "^0.8.0" + } +} diff --git a/src/activitys/ModuleViewActivity/tabs/OverviewTab.tsx b/src/activitys/ModuleViewActivity/tabs/OverviewTab.tsx index 6c489b35..e75b8c45 100644 --- a/src/activitys/ModuleViewActivity/tabs/OverviewTab.tsx +++ b/src/activitys/ModuleViewActivity/tabs/OverviewTab.tsx @@ -1,289 +1,298 @@ -import { Activities } from "@Activitys/index"; -import AntiFeatureListItem from "@Components/AntiFeatureListItem"; -import { Image } from "@Components/dapi/Image"; -import { useActivity } from "@Hooks/useActivity"; -import { useBlacklist } from "@Hooks/useBlacklist"; -import { useCategories } from "@Hooks/useCategories"; -import { useFetch } from "@Hooks/useFetch"; -import { useFormatDate } from "@Hooks/useFormatDate"; -import { useLowQualityModule } from "@Hooks/useLowQualityModule"; -import { useModuleInfo } from "@Hooks/useModuleInfo"; -import { useRepos } from "@Hooks/useRepos"; -import { useSettings } from "@Hooks/useSettings"; -import { useStrings } from "@Hooks/useStrings"; -import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; -import Alert from "@mui/material/Alert"; -import AlertTitle from "@mui/material/AlertTitle"; -import Box from "@mui/material/Box"; -import Card from "@mui/material/Card"; -import CardContent from "@mui/material/CardContent"; -import Chip from "@mui/material/Chip"; -import IconButton from "@mui/material/IconButton"; -import ImageList from "@mui/material/ImageList"; -import ImageListItem from "@mui/material/ImageListItem"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemButton from "@mui/material/ListItemButton"; -import ListItemText from "@mui/material/ListItemText"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import { Build } from "@Native/Build"; -import { os } from "@Native/Os"; -import React from "react"; - -const colorHandler = (color?: ModuleNoteColors) => { - switch (color) { - case "green": - case "success": - return "success"; - - case "info": - case "blue": - return "info"; - - case "warning": - case "yellow": - return "warning"; - - case "error": - case "red": - return "error"; - - default: - return "info"; - } -}; - -const OverviewTab = () => { - const { strings } = useStrings(); - const { context, extra } = useActivity(); - const { modules } = useRepos(); - const { id, name, description, versions, minApi, note, track } = extra; - - const { icon, screenshots, require, readme: moduleReadme, categories } = useModuleInfo(extra); - - const { filteredCategories } = useCategories(categories); - - const [lowQualityModule] = useSettings("_low_quality_module"); - const isLowQuality = useLowQualityModule(extra, !lowQualityModule); - const latestVersion = React.useMemo(() => versions[versions.length - 1], [versions]); - const formatLastUpdate = useFormatDate(latestVersion.timestamp); - - const blacklistedModules = useBlacklist(); - const findHardCodedAntifeature = React.useMemo(() => { - return [...(track.antifeatures || []), ...(blacklistedModules.find((mod) => mod.id === id)?.antifeatures || [])]; - }, [id, track.antifeatures]); - - const [readme] = useFetch(moduleReadme, { type: "text" }); - - return ( - <> - - {note && ( - - {note.title && {note.title}} - {note.message} - - )} - - {isLowQuality && ( - - {strings("low_quality_module")} - {strings("low_quality_module_warn")} - - )} - - {minApi && minApi > os.sdk && ( - - {strings("module_require_android_ver", { andro_ver: Build.parseVersion(minApi) })} - - )} - - - - - - {strings("about_this_module")} - - {readme && ( - { - context.pushPage({ - component: Activities.Description, - key: "DescriptonActivity", - extra: { - desc: readme, - name: name, - logo: icon, - }, - }); - }} - sx={{ ml: 0.5 }} - > - - - )} - - - - {description} - - - {strings("updated_on")} - - {formatLastUpdate} - - - {filteredCategories.length !== 0 && ( - - {filteredCategories.map((category) => ( - - ))} - - )} - - - - {findHardCodedAntifeature && findHardCodedAntifeature.length !== 0 && ( - - - - {strings("antifeatures")} - - - - {typeof findHardCodedAntifeature === "string" ? ( - - ) : ( - Array.isArray(findHardCodedAntifeature) && findHardCodedAntifeature.map((anti) => ) - )} - - - - )} - - {require && require.length !== 0 && ( - - - - {"Dependencies"} - - - - - - {require.map((req) => { - const findRequire = React.useMemo(() => modules.find((module) => module.id === req), [modules]); - - if (findRequire) { - return ( - { - context.pushPage({ - component: Activities.ModuleView, - key: "ModuleViewActivity", - extra: findRequire, - }); - }} - > - - - ); - } else { - return ( - - - - ); - } - })} - - - - )} - - {screenshots && screenshots.length !== 0 && ( - - - - {strings("images")} - - - - - {screenshots.map((image, i) => ( - ({ - ml: 1, - mr: 1, - })} - > - - - ))} - - - )} - - - ); -}; - -export { OverviewTab }; +import { Activities } from "@Activitys/index"; +import AntiFeatureListItem from "@Components/AntiFeatureListItem"; +import { Image } from "@Components/dapi/Image"; +import { useActivity } from "@Hooks/useActivity"; +import { useBlacklist } from "@Hooks/useBlacklist"; +import { useCategories } from "@Hooks/useCategories"; +import { useFetch } from "@Hooks/useFetch"; +import { useFormatDate } from "@Hooks/useFormatDate"; +import { useLowQualityModule } from "@Hooks/useLowQualityModule"; +import { useModuleInfo } from "@Hooks/useModuleInfo"; +import { useRepos } from "@Hooks/useRepos"; +import { useSettings } from "@Hooks/useSettings"; +import { useStrings } from "@Hooks/useStrings"; +import { useSupportedRoot } from "@Hooks/useSupportedRoot"; +import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; +import Alert from "@mui/material/Alert"; +import AlertTitle from "@mui/material/AlertTitle"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Chip from "@mui/material/Chip"; +import IconButton from "@mui/material/IconButton"; +import ImageList from "@mui/material/ImageList"; +import ImageListItem from "@mui/material/ImageListItem"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemText from "@mui/material/ListItemText"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { Build } from "@Native/Build"; +import { os } from "@Native/Os"; +import { Shell } from "@Native/Shell"; +import React from "react"; + +const colorHandler = (color?: ModuleNoteColors) => { + switch (color) { + case "green": + case "success": + return "success"; + + case "info": + case "blue": + return "info"; + + case "warning": + case "yellow": + return "warning"; + + case "error": + case "red": + return "error"; + + default: + return "info"; + } +}; + +const OverviewTab = () => { + const { strings } = useStrings(); + const { context, extra } = useActivity(); + const { modules } = useRepos(); + const { id, name, description, versions, minApi, note, track } = extra; + + const { icon, screenshots, require, readme: moduleReadme, categories, root } = useModuleInfo(extra); + const [isModuleSupported, currentRootVersion] = useSupportedRoot(root, []); + + const { filteredCategories } = useCategories(categories); + + const [lowQualityModule] = useSettings("_low_quality_module"); + const isLowQuality = useLowQualityModule(extra, !lowQualityModule); + const latestVersion = React.useMemo(() => versions[versions.length - 1], [versions]); + const formatLastUpdate = useFormatDate(latestVersion.timestamp); + + const blacklistedModules = useBlacklist(); + const findHardCodedAntifeature = React.useMemo(() => { + return [...(track.antifeatures || []), ...(blacklistedModules.find((mod) => mod.id === id)?.antifeatures || [])]; + }, [id, track.antifeatures]); + + const [readme] = useFetch(moduleReadme, { type: "text" }); + + return ( + <> + + {note && ( + + {note.title && {note.title}} + {note.message} + + )} + + {isLowQuality && ( + + {strings("low_quality_module")} + {strings("low_quality_module_warn")} + + )} + + {minApi && minApi > os.sdk && ( + + {strings("module_require_android_ver", { andro_ver: Build.parseVersion(minApi) })} + + )} + + {!isModuleSupported && ( + + {strings("unsupported_root", { manager: Shell.getRootManagerV2(), version: currentRootVersion })} + + )} + + + + + + {strings("about_this_module")} + + {readme && ( + { + context.pushPage({ + component: Activities.Description, + key: "DescriptonActivity", + extra: { + desc: readme, + name: name, + logo: icon, + }, + }); + }} + sx={{ ml: 0.5 }} + > + + + )} + + + + {description} + + + {strings("updated_on")} + + {formatLastUpdate} + + + {filteredCategories.length !== 0 && ( + + {filteredCategories.map((category) => ( + + ))} + + )} + + + + {findHardCodedAntifeature && findHardCodedAntifeature.length !== 0 && ( + + + + {strings("antifeatures")} + + + + {typeof findHardCodedAntifeature === "string" ? ( + + ) : ( + Array.isArray(findHardCodedAntifeature) && findHardCodedAntifeature.map((anti) => ) + )} + + + + )} + + {require && require.length !== 0 && ( + + + + {"Dependencies"} + + + + + + {require.map((req) => { + const findRequire = React.useMemo(() => modules.find((module) => module.id === req), [modules]); + + if (findRequire) { + return ( + { + context.pushPage({ + component: Activities.ModuleView, + key: "ModuleViewActivity", + extra: findRequire, + }); + }} + > + + + ); + } else { + return ( + + + + ); + } + })} + + + + )} + + {screenshots && screenshots.length !== 0 && ( + + + + {strings("images")} + + + + + {screenshots.map((image, i) => ( + ({ + ml: 1, + mr: 1, + })} + > + + + ))} + + + )} + + + ); +}; + +export { OverviewTab }; diff --git a/src/components/module/ExploreModule.tsx b/src/components/module/ExploreModule.tsx index f4d45883..a4e2a81b 100644 --- a/src/components/module/ExploreModule.tsx +++ b/src/components/module/ExploreModule.tsx @@ -1,143 +1,147 @@ -import { Activities } from "@Activitys/index"; -import { AntifeatureButton } from "@Components/AntifeaturesButton"; -import { Image } from "@Components/dapi/Image"; -import { VerifiedIcon } from "@Components/icons/VerifiedIcon"; -import { useActivity } from "@Hooks/useActivity"; -import { useBlacklist } from "@Hooks/useBlacklist"; -import { useFormatDate } from "@Hooks/useFormatDate"; -import { useModFS } from "@Hooks/useModFS"; -import { useModuleInfo } from "@Hooks/useModuleInfo"; -import { useStrings } from "@Hooks/useStrings"; -import { useTheme } from "@Hooks/useTheme"; -import { CalendarMonth, PersonOutline, Source, Tag } from "@mui/icons-material"; -import Card from "@mui/material/Card"; -import Chip from "@mui/material/Chip"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import { SuFile } from "@Native/SuFile"; -import React from "react"; - -interface Props { - module: Module; -} - -const ExploreModule = React.memo((props) => { - const { id, name, author, description, track, timestamp, version, versions, versionCode, features, __mmrl_repo_source } = props.module; - const { cover, verified } = useModuleInfo(props.module); - - const { context } = useActivity(); - const { strings } = useStrings(); - const { theme } = useTheme(); - const { modFS } = useModFS(); - - const formatLastUpdate = useFormatDate(timestamp ? timestamp : versions[versions.length - 1].timestamp); - - const blacklistedModules = useBlacklist(); - const findHardCodedAntifeature = React.useMemo(() => { - return [...(track.antifeatures || []), ...(blacklistedModules.find((mod) => mod.id === id)?.antifeatures || [])]; - }, [id, track.antifeatures]); - - const handleOpenModule = () => { - context.pushPage({ - component: Activities.ModuleView, - key: "ModuleViewActivity", - extra: props.module, - }); - }; - - return ( - - {cover && ( - {name} - )} - - - - - - {name} - - - - - {author} - - - - - - - {SuFile.exist(modFS("PROPS", { MODID: id })) && ( - - )} - - {features && Object.keys(features).length !== 0 && ( - - )} - - {findHardCodedAntifeature && findHardCodedAntifeature.length !== 0 && ( - - )} - - - - {description} - - - - - - - {strings("last_updated", { date: formatLastUpdate })} - - - - {__mmrl_repo_source && __mmrl_repo_source.join(", ")} - - - - {versionCode} - - - - - ); -}); - -export default ExploreModule; +import { Activities } from "@Activitys/index"; +import { AntifeatureButton } from "@Components/AntifeaturesButton"; +import { Image } from "@Components/dapi/Image"; +import { VerifiedIcon } from "@Components/icons/VerifiedIcon"; +import { useActivity } from "@Hooks/useActivity"; +import { useBlacklist } from "@Hooks/useBlacklist"; +import { useFormatDate } from "@Hooks/useFormatDate"; +import { useModFS } from "@Hooks/useModFS"; +import { useModuleInfo } from "@Hooks/useModuleInfo"; +import { useStrings } from "@Hooks/useStrings"; +import { useSupportedRoot } from "@Hooks/useSupportedRoot"; +import { useTheme } from "@Hooks/useTheme"; +import { CalendarMonth, PersonOutline, Source, Tag } from "@mui/icons-material"; +import Card from "@mui/material/Card"; +import Chip from "@mui/material/Chip"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { SuFile } from "@Native/SuFile"; +import React from "react"; + +interface Props { + module: Module; +} + +const ExploreModule = React.memo((props) => { + const { id, name, author, description, track, timestamp, version, versions, versionCode, features, __mmrl_repo_source } = props.module; + const { cover, verified, root } = useModuleInfo(props.module); + const [isModuleSupported] = useSupportedRoot(root, []); + + const { context } = useActivity(); + const { strings } = useStrings(); + const { theme } = useTheme(); + const { modFS } = useModFS(); + + const formatLastUpdate = useFormatDate(timestamp ? timestamp : versions[versions.length - 1].timestamp); + + const blacklistedModules = useBlacklist(); + const findHardCodedAntifeature = React.useMemo(() => { + return [...(track.antifeatures || []), ...(blacklistedModules.find((mod) => mod.id === id)?.antifeatures || [])]; + }, [id, track.antifeatures]); + + const handleOpenModule = () => { + context.pushPage({ + component: Activities.ModuleView, + key: "ModuleViewActivity", + extra: props.module, + }); + }; + + return ( + + {cover && ( + {name} + )} + + + + + + {name} + + + + + {author} + + + + + + + {SuFile.exist(modFS("PROPS", { MODID: id })) && ( + + )} + + {features && Object.keys(features).length !== 0 && ( + + )} + + {!isModuleSupported && } + + {findHardCodedAntifeature && findHardCodedAntifeature.length !== 0 && ( + + )} + + + + {description} + + + + + + + {strings("last_updated", { date: formatLastUpdate })} + + + + {__mmrl_repo_source && __mmrl_repo_source.join(", ")} + + + + {versionCode} + + + + + ); +}); + +export default ExploreModule; diff --git a/src/hooks/useModuleInfo.ts b/src/hooks/useModuleInfo.ts index 20b765b2..7e063ebd 100644 --- a/src/hooks/useModuleInfo.ts +++ b/src/hooks/useModuleInfo.ts @@ -1,44 +1,46 @@ -import React from "react"; - -type PickedModule = - | "timestamp" - | "verified" - | "license" - | "homepage" - | "support" - | "donate" - | "cover" - | "icon" - | "require" - | "screenshots" - | "categories" - | "readme" - | "size"; - -type ModuleInfo = Pick & { latestVersion: Version }; - -/** - * Used to handle undefined properties - */ -export const useModuleInfo = (extra: Module): ModuleInfo => { - const { track } = extra; - - const latestVersion = React.useMemo(() => extra.versions[extra.versions.length - 1], [extra.versions]); - - return { - timestamp: extra.timestamp || latestVersion.timestamp, - verified: extra.verified, - license: extra.license || track.license, - homepage: extra.homepage || track.homepage, - support: extra.support || track.support, - donate: extra.donate || track.donate, - cover: extra.cover || track.cover, - icon: extra.icon || track.icon, - require: extra.require || track.require, - screenshots: extra.screenshots || track.screenshots, - categories: extra.categories || track.categories, - readme: extra.readme || track.readme, - latestVersion: latestVersion, - size: extra.size || latestVersion.size, - }; -}; +import React from "react"; + +type PickedModule = + | "timestamp" + | "verified" + | "license" + | "homepage" + | "support" + | "donate" + | "cover" + | "icon" + | "require" + | "screenshots" + | "categories" + | "readme" + | "size" + | "root"; + +type ModuleInfo = Pick & { latestVersion: Version }; + +/** + * Used to handle undefined properties + */ +export const useModuleInfo = (extra: Module): ModuleInfo => { + const { track } = extra; + + const latestVersion = React.useMemo(() => extra.versions[extra.versions.length - 1], [extra.versions]); + + return { + timestamp: extra.timestamp || latestVersion.timestamp, + verified: extra.verified, + license: extra.license || track.license, + homepage: extra.homepage || track.homepage, + support: extra.support || track.support, + donate: extra.donate || track.donate, + cover: extra.cover || track.cover, + icon: extra.icon || track.icon, + require: extra.require || track.require, + screenshots: extra.screenshots || track.screenshots, + categories: extra.categories || track.categories, + readme: extra.readme || track.readme, + latestVersion: latestVersion, + size: extra.size || latestVersion.size, + root: extra.root, + }; +}; diff --git a/src/hooks/useSupportedRoot.ts b/src/hooks/useSupportedRoot.ts new file mode 100644 index 00000000..3534db81 --- /dev/null +++ b/src/hooks/useSupportedRoot.ts @@ -0,0 +1,31 @@ +import { Shell } from "@Native/Shell"; +import React from "react"; +import { valid, satisfies } from "semver"; + +const currentRootVersion = Shell.VERSION_NAME(); +const rootManager = Shell.getRootManagerV2().toLowerCase(); + +const useSupportedRoot = (roots: Module["root"], deps: React.DependencyList): [boolean, string] => { + const cleanSemver = React.useCallback((version: string) => { + return version.replace(/:.*$/, ""); + }, deps); + + const isSupported = React.useMemo(() => { + if (!roots) { + return true; + } + + if (roots[rootManager]) { + const cleanedVer = cleanSemver(currentRootVersion); + if (valid(cleanedVer)) { + return satisfies(cleanedVer, roots[rootManager]); + } + } + + return true; + }, deps); + + return [isSupported, currentRootVersion]; +}; + +export { useSupportedRoot }; diff --git a/src/locales/en.ts b/src/locales/en.ts index 2f1641aa..935a06bd 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -1,172 +1,174 @@ -export const en = { - continue: "Continue", - caution: "Caution", - latest: "Latest", - security: "Security", - changelog: "Changelog", - new: "New", - search: "Search", - updates: "Updates", - versions: "Versions", - licenses: "Licenses", - license: "License", - search_modules: "Search modules", - settings: "Settings", - repository: "Repository", - repositories: "Repositories", - appearance: "Appearance", - accent_color: "Accent color", - language: "Language", - dark_theme: "Dark theme", - bottom_navigation_text: "Bottom navigation", - bottom_navigation_subtext: "Moves tabs to the bottom of screen.", - not_supported_in_web_version: "Not supported in web version", - source_code: "Source code", - acknowledgements: "Acknowledgements", - issues: "Issues", - download: "Download", - install: "Install", - update: "Update", - explore: "Explore", - installed: "Installed", - remove: "Remove", - restore: "Restore", - module_enabled_LOG: "{name} has been enabled", - module_disabled_LOG: "{name} has been disabled", - add: "Add", - cancel: "Cancel", - confirm_repo_delete: "Are you sure to remove {name} repository?", - submit_module: "Submit a module", - donate: "Donate", - support: "Support", - website: "Website", - no_root: "No Root", - failed: "Failed", - no_root_message: "Please make sure that you have at least one root manager, otherwise you can't use MMRL.", - open_magisk: "Open Magisk", - development: "Development", - enabled: "Enabled", - comments: "Comments", - configureable: "Configureable", - change_boot: "Changes boot", - need_ramdisk: "Needs Ramdisk", - add_repository: "Add Repository", - add_repository_description: "Add your repository or an repository from some else.", - explore_repositories: "Explore Repositories", - overview: "Overview", - about_this_module: "About this module", - about: "About", - updated_on: "Updated on", - requirements: "Requirements", - access: "Access", - minimum: "Minimum", - recommended: "Recommended", - source: "Source", - require_sdk: "Module requires {sdk}", - unsupported: "Unsupported", - images: "Images", - unset: "Unset", - yes: "Yes", - no: "No", - operating_sys: "Operating System", - verified_module: "Verified module", - verified_module_desc: - "This module has undergone verification and has been confirmed as a trusted module developed by a reputable developer.", - update_json: "Uses own update.json", - update_json_desc: "This module utilizes its own update.json for updating and installation purposes.", - shading: "Shading", - shading_title: "Apply custom shading", - shading_desc: "Use with care, if to dark you may not able to see the UI anymore.", - module: "Module", - swipeable_tabs: "Swipeable tabs", - swipeable_tabs_subtitle: "Make all tabs swipeable. This make break user experience", - low_quality_module: "Low quality module", - low_quality_modules: "Low quality modules", - low_quality_modules_subtitle: "Shows a alert below the module if it has a low quality", - low_quality_module_warn: - "This Magisk module is missing crucial properties, such as id, version, versionCode, author, etc., which may affect its functionality and origin.", - invaild_modules: "Invaild modules", - invaild_modules_subtitle: "Show invaild modules", - // no translate - modconf: "ModConf", - modconf_playground: "ModConf Playground", - // no translate - modfs: "ModFS", - enable_install: "Enable install", - enable_install_subtitle: "Since 1.8.5 the MMRL Install Tools are required", - scroll_to_bottom: "Scroll to bottom", - scroll_to_bottom_subtitle: "Automatically scroll to bottom within the terminal", - scroll_behavior: "Scroll behavior", - terminal: "Terminal", - eruda_console: "Eruda console", - eruda_console_subtitle: "Useful for development and bug hunting", - share_device_infos: "Share device information's", - share_device_infos_subtilte: "Device specs, configured ModConf, app infos and installed modules", - storage: "Storage", - clear_repos: "Clear repositories", - patch_settings: "Patch settings", - patch_settings_subtitle: "Add missing settings keys", - sticky_search_bar: "Disable sticky search bar", - dm_update_json_fetch_warn: "{id} has empty „updateJson“ property or the link isn't valid", - - // Anti-Features - antifeature: "Anti-Feature", - antifeatures: "Anti-Features", - - // Anchor link confirm - anchor_confirm_title: "Leaving MMRL", - anchor_confirm_desc: "This link is taking you the following website {codeblock}", - link_protection_title: "Link protection", - link_protection_desc: "Prevent link that are accidentally clicked with a extra confirm dialog. This setting also affects ModConf.", - - // modconf - compile_error: "Compile error!", - - unverified_host: "Unverified host!", - unverified_host_text: - "You're accessing MMRL from {url} which isn't a verified host. Only use MMRL from it's origial source and not from thrid-party sources.", - unverified_host_text_help: "Noticed any issues or you know that the accessed host safe for use is? Then report it under our {issues}", - - // terminal activity - reboot_device: "Reboot device?", - reboot_device_desc: "Are you sure to reboot your device?", - word_wrap_title: "Word wrap", - numberic_lines_title: "Numberic lines", - numberic_lines_desc: "Adds a number to every line in the installer", - - privacy_privacy: "Privacy Policy", - hoc_with_require_new_version: "This config requires MMRL above {versionCode} (versionCode){br}Check the latest {url}", - - print_errors: "Print errors", - print_errors_desc: "Prints terminal and MMRL Install Tools error to the console", - - blacklisted_modules: "Blacklisted modules", - - install_module: "Install {name}?", - install_module_dialog_desc: "Are you sure that you what to install {name}?", - - exit_app: "Exit app?", - exit_app_desc: "Are you sure that you want to leave the app?", - - module_require_android_ver: "This module requires {andro_ver}. It may doesn't work for your device.", - - we_hit_a_brick: "We hit a brick!", - open_settings: "Open settings", - open_logcat: "Open Logcat", - try_again: "Try again", - - file_downloaded: "File has been downloaded to {path}", - - features: "Features", - feature: "Feature", - verified: "Verified", - last_updated: "Last updated: {date}", - telegram_channel: "Telegram channel", - install_queue: "Install queue", - install_queue_notice: "The queue will be deleted when you close the app", - install_queue_empty: "Your queue is empty. Add some modules!", - alr_add_queue: "This module has been already added to your queue", - add_t_queue: "Module has been added to your queue", - start_mod_ini_queue: "Start install queue?", - start_mod_ini_queue_desc: "Are you sure that you want to start this queue?", -}; +export const en = { + continue: "Continue", + caution: "Caution", + latest: "Latest", + security: "Security", + changelog: "Changelog", + new: "New", + search: "Search", + updates: "Updates", + versions: "Versions", + licenses: "Licenses", + license: "License", + search_modules: "Search modules", + settings: "Settings", + repository: "Repository", + repositories: "Repositories", + appearance: "Appearance", + accent_color: "Accent color", + language: "Language", + dark_theme: "Dark theme", + bottom_navigation_text: "Bottom navigation", + bottom_navigation_subtext: "Moves tabs to the bottom of screen.", + not_supported_in_web_version: "Not supported in web version", + source_code: "Source code", + acknowledgements: "Acknowledgements", + issues: "Issues", + download: "Download", + install: "Install", + update: "Update", + explore: "Explore", + installed: "Installed", + remove: "Remove", + restore: "Restore", + module_enabled_LOG: "{name} has been enabled", + module_disabled_LOG: "{name} has been disabled", + add: "Add", + cancel: "Cancel", + confirm_repo_delete: "Are you sure to remove {name} repository?", + submit_module: "Submit a module", + donate: "Donate", + support: "Support", + website: "Website", + no_root: "No Root", + failed: "Failed", + no_root_message: "Please make sure that you have at least one root manager, otherwise you can't use MMRL.", + open_magisk: "Open Magisk", + development: "Development", + enabled: "Enabled", + comments: "Comments", + configureable: "Configureable", + change_boot: "Changes boot", + need_ramdisk: "Needs Ramdisk", + add_repository: "Add Repository", + add_repository_description: "Add your repository or an repository from some else.", + explore_repositories: "Explore Repositories", + overview: "Overview", + about_this_module: "About this module", + about: "About", + updated_on: "Updated on", + requirements: "Requirements", + access: "Access", + minimum: "Minimum", + recommended: "Recommended", + source: "Source", + require_sdk: "Module requires {sdk}", + unsupported: "Unsupported", + images: "Images", + unset: "Unset", + yes: "Yes", + no: "No", + operating_sys: "Operating System", + verified_module: "Verified module", + verified_module_desc: + "This module has undergone verification and has been confirmed as a trusted module developed by a reputable developer.", + update_json: "Uses own update.json", + update_json_desc: "This module utilizes its own update.json for updating and installation purposes.", + shading: "Shading", + shading_title: "Apply custom shading", + shading_desc: "Use with care, if to dark you may not able to see the UI anymore.", + module: "Module", + swipeable_tabs: "Swipeable tabs", + swipeable_tabs_subtitle: "Make all tabs swipeable. This make break user experience", + low_quality_module: "Low quality module", + low_quality_modules: "Low quality modules", + low_quality_modules_subtitle: "Shows a alert below the module if it has a low quality", + low_quality_module_warn: + "This Magisk module is missing crucial properties, such as id, version, versionCode, author, etc., which may affect its functionality and origin.", + invaild_modules: "Invaild modules", + invaild_modules_subtitle: "Show invaild modules", + // no translate + modconf: "ModConf", + modconf_playground: "ModConf Playground", + // no translate + modfs: "ModFS", + enable_install: "Enable install", + enable_install_subtitle: "Since 1.8.5 the MMRL Install Tools are required", + scroll_to_bottom: "Scroll to bottom", + scroll_to_bottom_subtitle: "Automatically scroll to bottom within the terminal", + scroll_behavior: "Scroll behavior", + terminal: "Terminal", + eruda_console: "Eruda console", + eruda_console_subtitle: "Useful for development and bug hunting", + share_device_infos: "Share device information's", + share_device_infos_subtilte: "Device specs, configured ModConf, app infos and installed modules", + storage: "Storage", + clear_repos: "Clear repositories", + patch_settings: "Patch settings", + patch_settings_subtitle: "Add missing settings keys", + sticky_search_bar: "Disable sticky search bar", + dm_update_json_fetch_warn: "{id} has empty „updateJson“ property or the link isn't valid", + + // Anti-Features + antifeature: "Anti-Feature", + antifeatures: "Anti-Features", + + // Anchor link confirm + anchor_confirm_title: "Leaving MMRL", + anchor_confirm_desc: "This link is taking you the following website {codeblock}", + link_protection_title: "Link protection", + link_protection_desc: "Prevent link that are accidentally clicked with a extra confirm dialog. This setting also affects ModConf.", + + // modconf + compile_error: "Compile error!", + + unverified_host: "Unverified host!", + unverified_host_text: + "You're accessing MMRL from {url} which isn't a verified host. Only use MMRL from it's origial source and not from thrid-party sources.", + unverified_host_text_help: "Noticed any issues or you know that the accessed host safe for use is? Then report it under our {issues}", + + // terminal activity + reboot_device: "Reboot device?", + reboot_device_desc: "Are you sure to reboot your device?", + word_wrap_title: "Word wrap", + numberic_lines_title: "Numberic lines", + numberic_lines_desc: "Adds a number to every line in the installer", + + privacy_privacy: "Privacy Policy", + hoc_with_require_new_version: "This config requires MMRL above {versionCode} (versionCode){br}Check the latest {url}", + + print_errors: "Print errors", + print_errors_desc: "Prints terminal and MMRL Install Tools error to the console", + + blacklisted_modules: "Blacklisted modules", + + install_module: "Install {name}?", + install_module_dialog_desc: "Are you sure that you what to install {name}?", + + exit_app: "Exit app?", + exit_app_desc: "Are you sure that you want to leave the app?", + + module_require_android_ver: "This module requires {andro_ver}. It may doesn't work for your device.", + + we_hit_a_brick: "We hit a brick!", + open_settings: "Open settings", + open_logcat: "Open Logcat", + try_again: "Try again", + + file_downloaded: "File has been downloaded to {path}", + + features: "Features", + feature: "Feature", + verified: "Verified", + last_updated: "Last updated: {date}", + telegram_channel: "Telegram channel", + install_queue: "Install queue", + install_queue_notice: "The queue will be deleted when you close the app", + install_queue_empty: "Your queue is empty. Add some modules!", + alr_add_queue: "This module has been already added to your queue", + add_t_queue: "Module has been added to your queue", + start_mod_ini_queue: "Start install queue?", + start_mod_ini_queue_desc: "Are you sure that you want to start this queue?", + unsupported_root: + "This module doesn't supports your current root version! Detected {manager} with version {version}. Install at your own risk!", +}; diff --git a/src/native/Shell.ts b/src/native/Shell.ts index 96b6cc77..87899104 100644 --- a/src/native/Shell.ts +++ b/src/native/Shell.ts @@ -1,249 +1,270 @@ -import { Native } from "./Native"; -import { SuFile } from "./SuFile"; - -interface NativeShell { - /** - * Executes an command without result - */ - exec(command: string): void; - /** - * Executes an command with result - */ - result(command: string): string; - isSuccess(command: string): boolean; - getCode(command: string): number; - /** - * Checks if the app has been granted root privileges - * @deprecated Use `Shell.isSuAvailable()` instead - */ - isAppGrantedRoot(): boolean; - /** - * Checks if the app has been granted root privileges - */ - isSuAvailable(): boolean; - isKernelSU(): boolean; - isMagiskSU(): boolean; - isAPatchSU(): boolean; - getenv(key: string): string; - setenv(key: string, value: string, override: number): void; - pw_uid(): number; - pw_gid(): number; - pw_name(): string; -} - -interface NativeShellV2 extends NativeShell { - v2(command: string): { - exec(): void; - result(): string; - isSuccess(): boolean; - getCode(): number; - }; -} - -type RootManager = "Magisk" | "KernelSU" | "APatchSU" | "Unknown"; - -/** - * Run Shell commands native on Android - */ -class Shell extends Native { - /** - * Successful module install exit code - */ - public static readonly M_INS_SUCCESS: number = 0; - /** - * Failed module install exit code - */ - public static readonly M_INS_FAILURE: number = 1; - /** - * Failed file download exit code - */ - public static readonly M_DWL_FAILURE: number = 2; - /** - * File creation exit code - */ - public static readonly FILE_CRA_ERRO: number = 3; - /** - * Internal terminal error exit code - */ - public static readonly TERM_INTR_ERR: number = 500; - - private _command: Array; - // @ts-ignore - Won't get even called - private _shell: ReturnType; - - public constructor(command: string | Array) { - super(window.__shell__); - - if (!Array.isArray(command)) { - this._command = [command]; - } else { - this._command = command; - } - - if (this.isAndroid) { - this._shell = this.interface.v2.bind(this.interface)(JSON.stringify(this._command)); - } - } - - public exec(): void { - if (this.isAndroid) { - this._shell.exec(); - } - } - - public result(): string { - if (this.isAndroid) { - return this._shell.result(); - } else { - return ""; - } - } - - public isSuccess(): boolean { - if (this.isAndroid) { - return this._shell.isSuccess(); - } else { - return false; - } - } - - public getCode(): number { - if (this.isAndroid) { - return this._shell.getCode(); - } else { - return 1; - } - } - - /** - * Compatibility method to ensure support without beaking changes - * @param command - * @returns - */ - public static cmd(command: string | Array): Shell { - return new Shell(command); - } - - /** - * Checks if the app has been granted root privileges - */ - public static isSuAvailable(): boolean { - if (this.isAndroid) { - return window.__shell__.isSuAvailable(); - } - - return false; - } - - /** - * Get current installed Superuser version code - */ - public static VERSION_CODE(): number { - if (this.isAndroid) { - return parseInt(this.cmd("su -V").result()); - } else { - return 0; - } - } - - public static VERSION_NAME(): string { - if (this.isAndroid) { - return this.cmd("su -v").result(); - } else { - return "0:SU"; - } - } - - public static getRootManager(): RootManager { - const rootManagers: [boolean, RootManager][] = [ - [this.isMagiskSU(), "Magisk"], - [this.isKernelSU(), "KernelSU"], - [this.isAPatchSU(), "APatchSU"], - ]; - - for (const [check, name] of rootManagers) { - if (check) { - return name; - } - } - - return "Unknown"; - } - - /** - * Use regex for better detection - * @param searcher - * @returns - */ - private static _mountDetect(searcher: { [Symbol.search](string: string): number }): boolean { - const proc = new SuFile("/proc/self/mounts"); - - if (proc.exist()) { - return proc.read().search(searcher) !== -1; - } else { - return false; - } - } - - /** - * Determine if MMRL runs with KernelSU - */ - public static isKernelSU(): boolean { - // `proc.exist()` is always `false` on browsers - return this._mountDetect(/\b(KSU|KernelSU)\b/i); - } - - /** - * Determine if MMRL runs with Magisk - */ - public static isMagiskSU(): boolean { - // `proc.exist()` is always `false` on browsers - return this._mountDetect(/\b(magisk|core\/mirror|core\/img)\b/i); - } - - /** - * Determine if MMRL runs with APatch - */ - public static isAPatchSU(): boolean { - // `proc.exist()` is always `false` on browsers - return this._mountDetect(/\b(APD|APatch)\b/i); - } - - /** - * Returns the current user id - * @returns {strign} User ID - */ - public static pw_uid(): string { - if (this.isAndroid) { - return this.cmd("id -u").result(); - } else { - return "Unknown"; - } - } - - /** - * Returns the current group id - * @returns {string} Group ID - */ - public static pw_gid(): string { - if (this.isAndroid) { - return this.cmd("id -g").result(); - } else { - return "Unknown"; - } - } - - /** - * Returns the current user name - * @returns {string} User name - */ - public static pw_name(): string { - if (this.isAndroid) { - return this.cmd("id -un").result(); - } else { - return "Unknown"; - } - } -} - -export { Shell }; +import { Native } from "./Native"; +import { SuFile } from "./SuFile"; + +interface NativeShell { + /** + * Executes an command without result + */ + exec(command: string): void; + /** + * Executes an command with result + */ + result(command: string): string; + isSuccess(command: string): boolean; + getCode(command: string): number; + /** + * Checks if the app has been granted root privileges + * @deprecated Use `Shell.isSuAvailable()` instead + */ + isAppGrantedRoot(): boolean; + /** + * Checks if the app has been granted root privileges + */ + isSuAvailable(): boolean; + isKernelSU(): boolean; + isMagiskSU(): boolean; + isAPatchSU(): boolean; + getenv(key: string): string; + setenv(key: string, value: string, override: number): void; + pw_uid(): number; + pw_gid(): number; + pw_name(): string; +} + +interface NativeShellV2 extends NativeShell { + v2(command: string): { + exec(): void; + result(): string; + isSuccess(): boolean; + getCode(): number; + }; +} + +export type RootManager = "Magisk" | "KernelSU" | "APatchSU" | "Unknown"; +export type RootManagerV2 = "Magisk" | "KernelSU" | "APatch" | "Unknown"; + +/** + * Run Shell commands native on Android + */ +class Shell extends Native { + /** + * Successful module install exit code + */ + public static readonly M_INS_SUCCESS: number = 0; + /** + * Failed module install exit code + */ + public static readonly M_INS_FAILURE: number = 1; + /** + * Failed file download exit code + */ + public static readonly M_DWL_FAILURE: number = 2; + /** + * File creation exit code + */ + public static readonly FILE_CRA_ERRO: number = 3; + /** + * Internal terminal error exit code + */ + public static readonly TERM_INTR_ERR: number = 500; + + private _command: Array; + // @ts-ignore - Won't get even called + private _shell: ReturnType; + + public constructor(command: string | Array) { + super(window.__shell__); + + if (!Array.isArray(command)) { + this._command = [command]; + } else { + this._command = command; + } + + if (this.isAndroid) { + this._shell = this.interface.v2.bind(this.interface)(JSON.stringify(this._command)); + } + } + + public exec(): void { + if (this.isAndroid) { + this._shell.exec(); + } + } + + public result(): string { + if (this.isAndroid) { + return this._shell.result(); + } else { + return ""; + } + } + + public isSuccess(): boolean { + if (this.isAndroid) { + return this._shell.isSuccess(); + } else { + return false; + } + } + + public getCode(): number { + if (this.isAndroid) { + return this._shell.getCode(); + } else { + return 1; + } + } + + /** + * Compatibility method to ensure support without beaking changes + * @param command + * @returns + */ + public static cmd(command: string | Array): Shell { + return new Shell(command); + } + + /** + * Checks if the app has been granted root privileges + */ + public static isSuAvailable(): boolean { + if (this.isAndroid) { + return window.__shell__.isSuAvailable(); + } + + return false; + } + + /** + * Get current installed Superuser version code + */ + public static VERSION_CODE(): number { + if (this.isAndroid) { + return parseInt(this.cmd("su -V").result()); + } else { + return 0; + } + } + + public static VERSION_NAME(): string { + if (this.isAndroid) { + return this.cmd("su -v").result(); + } else { + return "0:SU"; + } + } + + /** + * @deprecated + * @returns + */ + public static getRootManager(): RootManager { + const rootManagers: [boolean, RootManager][] = [ + [this.isMagiskSU(), "Magisk"], + [this.isKernelSU(), "KernelSU"], + [this.isAPatchSU(), "APatchSU"], + ]; + + for (const [check, name] of rootManagers) { + if (check) { + return name; + } + } + + return "Unknown"; + } + + public static getRootManagerV2(): RootManagerV2 { + const rootManagers: [boolean, RootManagerV2][] = [ + [this.isMagiskSU(), "Magisk"], + [this.isKernelSU(), "KernelSU"], + [this.isAPatchSU(), "APatch"], + ]; + + for (const [check, name] of rootManagers) { + if (check) { + return name; + } + } + + return "Unknown"; + } + + /** + * Use regex for better detection + * @param searcher + * @returns + */ + private static _mountDetect(searcher: { [Symbol.search](string: string): number }): boolean { + const proc = new SuFile("/proc/self/mounts"); + + if (proc.exist()) { + return proc.read().search(searcher) !== -1; + } else { + return false; + } + } + + /** + * Determine if MMRL runs with KernelSU + */ + public static isKernelSU(): boolean { + // `proc.exist()` is always `false` on browsers + return this._mountDetect(/\b(KSU|KernelSU)\b/i); + } + + /** + * Determine if MMRL runs with Magisk + */ + public static isMagiskSU(): boolean { + // `proc.exist()` is always `false` on browsers + return this._mountDetect(/\b(magisk|core\/mirror|core\/img)\b/i); + } + + /** + * Determine if MMRL runs with APatch + */ + public static isAPatchSU(): boolean { + // `proc.exist()` is always `false` on browsers + return this._mountDetect(/\b(APD|APatch)\b/i); + } + + /** + * Returns the current user id + * @returns {strign} User ID + */ + public static pw_uid(): string { + if (this.isAndroid) { + return this.cmd("id -u").result(); + } else { + return "Unknown"; + } + } + + /** + * Returns the current group id + * @returns {string} Group ID + */ + public static pw_gid(): string { + if (this.isAndroid) { + return this.cmd("id -g").result(); + } else { + return "Unknown"; + } + } + + /** + * Returns the current user name + * @returns {string} User name + */ + public static pw_name(): string { + if (this.isAndroid) { + return this.cmd("id -un").result(); + } else { + return "Unknown"; + } + } +} + +export { Shell }; diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts index 8d9503a1..9b2c5dce 100644 --- a/src/typings/global.d.ts +++ b/src/typings/global.d.ts @@ -1,342 +1,346 @@ -import { AlertColor } from "@mui/material/Alert"; -import { AvailableStrs, strs } from "./../locales/declaration"; -import { Theme } from "@mui/material"; -import { en_antifeatures } from "locales/antifeatures/en"; - -export {}; - -declare module "*.d.ts" { - const value: string; - export default value; -} - -declare global { - type arr = Array; - type list = Array; - type str = string; - type Str = String; - type int = number; - type Int = Number; - type Void = void; - type Any = any; - type bool = boolean; - - type HTMLAttributes = React.DetailedHTMLProps & P, E>; - type AnchorHTMLAttributes = React.DetailedHTMLProps & P, E>; - - type VersionType = `${string}.${string}.${string}`; - - namespace JSX { - interface IntrinsicElements { - "mmrl-anchor": React.DetailedHTMLProps & { page?: string }, HTMLAnchorElement>; - - // Onsen Elements - "ons-toolbar-button": HTMLAttributes; - "ons-toolbar": HTMLAttributes; - "ons-page": HTMLAttributes; - "ons-splitter": HTMLAttributes; - "ons-splitter-content": HTMLAttributes; - "ons-splitter-side": HTMLAttributes; - "ons-navigator": HTMLAttributes; - "ons-tabbar": HTMLAttributes; - "ons-tab": HTMLAttributes; - "ons-gesture-detector": HTMLAttributes; - "ons-bottom-toolbar": HTMLAttributes; - "ons-fab": HTMLAttributes; - "ons-carousel": HTMLAttributes; - "ons-carousel-item": HTMLAttributes; - } - } - - interface NativeStorage extends Storage { - getItem(key: string, def?: string): string; - } - - /** - * Native window properties for Android - */ - interface AndroidWindow { - /** - * This is an Android only window object - */ - readonly __sufile__: I; - /** - * This is an Android only window object - */ - readonly __suzip__: I; - /** - * This is an Android only window object - */ - readonly __environment__: I; - - /** - * This is an Android only window object - */ - readonly __shell__: I; - /** - * This is an Android only window object - */ - readonly __buildconfig__: I; - /** - * This is an Android only window object - */ - readonly __os__: I; - /** - * TODO - */ - readonly __view__: I; - readonly __log__: I; - readonly __properties__: I; - /** - * `localStorage` like object to make support better with `useLocalStorage`. - * - * - This interface is not configurable - */ - readonly __nativeStorage__: NativeStorage; - - readonly __terminal__: I; - readonly __chooser__: I; - readonly __download__: I; - } - - export type MMRLTheme = Theme & { - palette?: { - primary?: { - header?: string; - }; - menuoutline?: string; - text?: { - link?: string; - }; - }; - }; - - interface AndroidNavigator {} - - interface Navigator extends AndroidNavigator { - app: { - exitApp: () => void; - }; - } - - interface Window extends AndroidWindow { - localStorage: NativeStorage; - } - - const Toast: { - LENGTH_LONG: "long"; - LENGTH_SHORT: "short"; - }; - - const WEB_BUILD_DATE: number; - - const __webpack__mode__: "production" | "development"; - - export interface RepoConfig { - name: string; - website?: string; - support?: string; - donate?: string; - submission?: any; - base_url: string; - max_num?: number; - enable_log?: boolean; - log_dir?: string; - } - - export interface Repo { - name: string; - website: string; - support: string; - donate: string; - submission: any; - metadata: Metadata; - modules: Module[]; - } - - export interface Metadata { - version: number; - timestamp: number; - } - - export interface BaseModule { - id: string; - name: string; - version: string; - versionCode: number; - author: string; - description: string; - } - - export type ModuleNoteColors = "success" | "green" | "info" | "blue" | "warning" | "yellow" | "error" | "red"; - export interface ModuleNote { - title?: string; - message?: string; - color?: ModuleNoteColors; - } - - export type ModuleFeaturesList = - | "service" - | "post_fs_data" - | "resetprop" - | "sepolicy" - | "zygisk" - | "webroot" - | "post_mount" - | "boot_completed" - | "modconf" - | "apks"; - - export type ModuleFeatures = Partial>; - - export interface Module extends BaseModule { - updateJson?: string; - added: number; - timestamp?: number; - size?: number; - track: Track; - versions: Version[]; - - features: ModuleFeatures; - - minApi?: number; - maxApi?: number; - - license?: string; - homepage?: string; - support?: string; - donate?: string; - cover?: string; - icon?: string; - require?: string[]; - screenshots?: string[]; - category?: string; - categories?: string[]; - stars?: number; - readme?: string; - note?: ModuleNote; - - /** - * Non-user definable - */ - verified: boolean; - - /** - * Local modules only - */ - __mmrl__local__module__?: boolean; - __mmrl_repo_source?: string[]; - } - - export interface BaseTrack { - type: string; - added: number; - source: string; - } - - export interface Track extends BaseTrack { - verified: boolean; - antifeatures?: string | string[]; - - /** - * @deprecated - */ - license: string; - /** - * @deprecated - */ - homepage: string; - /** - * @deprecated - */ - support: string; - /** - * @deprecated - */ - donate: string; - /** - * @deprecated - */ - cover?: string; - /** - * @deprecated - */ - icon?: string; - /** - * @deprecated - */ - require?: string[]; - /** - * @deprecated - */ - screenshots?: string[]; - /** - * @deprecated - */ - category?: string; - /** - * @deprecated - */ - categories?: string[]; - /** - * @deprecated - */ - readme?: string; - - /** - * Not Supported - */ - stars?: number; - } - - export interface Version { - timestamp: number; - version: string; - versionCode: number; - zipUrl: string; - size?: number; - changelog: string; - } - - export interface UpdateJson { - version: string; - versionCode: number; - zipUrl: string; - changelog: string; - } - - export interface LicenseSPX { - isDeprecatedLicenseId: boolean; - isFsfLibre: boolean; - licenseText: string; - standardLicenseTemplate: string; - name: string; - licenseId: string; - crossRef: CrossRef[]; - seeAlso: string[]; - isOsiApproved: boolean; - licenseTextHtml: string; - } - - export interface CrossRef { - match: string; - url: string; - isValid: boolean; - isLive: boolean; - timestamp: string; - isWayBackLink: boolean; - order: number; - } - - // OnsenUI Types - /** - * @extends {Event} - */ - export interface DeviceBackButtonEvent extends Event { - /** - * Runs the handler for the immediate parent that supports device back button. - * @returns {void} - */ - callParentHandler: () => void; - } -} +import { AlertColor } from "@mui/material/Alert"; +import { AvailableStrs, strs } from "./../locales/declaration"; +import { Theme } from "@mui/material"; +import { en_antifeatures } from "locales/antifeatures/en"; +import { RootManagerV2 } from "@Native/Shell"; + +export {}; + +declare module "*.d.ts" { + const value: string; + export default value; +} + +declare global { + type arr = Array; + type list = Array; + type str = string; + type Str = String; + type int = number; + type Int = Number; + type Void = void; + type Any = any; + type bool = boolean; + + type HTMLAttributes = React.DetailedHTMLProps & P, E>; + type AnchorHTMLAttributes = React.DetailedHTMLProps & P, E>; + + type VersionType = `${string}.${string}.${string}`; + + namespace JSX { + interface IntrinsicElements { + "mmrl-anchor": React.DetailedHTMLProps & { page?: string }, HTMLAnchorElement>; + + // Onsen Elements + "ons-toolbar-button": HTMLAttributes; + "ons-toolbar": HTMLAttributes; + "ons-page": HTMLAttributes; + "ons-splitter": HTMLAttributes; + "ons-splitter-content": HTMLAttributes; + "ons-splitter-side": HTMLAttributes; + "ons-navigator": HTMLAttributes; + "ons-tabbar": HTMLAttributes; + "ons-tab": HTMLAttributes; + "ons-gesture-detector": HTMLAttributes; + "ons-bottom-toolbar": HTMLAttributes; + "ons-fab": HTMLAttributes; + "ons-carousel": HTMLAttributes; + "ons-carousel-item": HTMLAttributes; + } + } + + interface NativeStorage extends Storage { + getItem(key: string, def?: string): string; + } + + /** + * Native window properties for Android + */ + interface AndroidWindow { + /** + * This is an Android only window object + */ + readonly __sufile__: I; + /** + * This is an Android only window object + */ + readonly __suzip__: I; + /** + * This is an Android only window object + */ + readonly __environment__: I; + + /** + * This is an Android only window object + */ + readonly __shell__: I; + /** + * This is an Android only window object + */ + readonly __buildconfig__: I; + /** + * This is an Android only window object + */ + readonly __os__: I; + /** + * TODO + */ + readonly __view__: I; + readonly __log__: I; + readonly __properties__: I; + /** + * `localStorage` like object to make support better with `useLocalStorage`. + * + * - This interface is not configurable + */ + readonly __nativeStorage__: NativeStorage; + + readonly __terminal__: I; + readonly __chooser__: I; + readonly __download__: I; + } + + export type MMRLTheme = Theme & { + palette?: { + primary?: { + header?: string; + }; + menuoutline?: string; + text?: { + link?: string; + }; + }; + }; + + interface AndroidNavigator {} + + interface Navigator extends AndroidNavigator { + app: { + exitApp: () => void; + }; + } + + interface Window extends AndroidWindow { + localStorage: NativeStorage; + } + + const Toast: { + LENGTH_LONG: "long"; + LENGTH_SHORT: "short"; + }; + + const WEB_BUILD_DATE: number; + + const __webpack__mode__: "production" | "development"; + + export interface RepoConfig { + name: string; + website?: string; + support?: string; + donate?: string; + submission?: any; + base_url: string; + max_num?: number; + enable_log?: boolean; + log_dir?: string; + } + + export interface Repo { + name: string; + website: string; + support: string; + donate: string; + submission: any; + metadata: Metadata; + modules: Module[]; + } + + export interface Metadata { + version: number; + timestamp: number; + } + + export interface BaseModule { + id: string; + name: string; + version: string; + versionCode: number; + author: string; + description: string; + } + + export type ModuleNoteColors = "success" | "green" | "info" | "blue" | "warning" | "yellow" | "error" | "red"; + export interface ModuleNote { + title?: string; + message?: string; + color?: ModuleNoteColors; + } + + export type ModuleFeaturesList = + | "service" + | "post_fs_data" + | "resetprop" + | "sepolicy" + | "zygisk" + | "webroot" + | "post_mount" + | "boot_completed" + | "modconf" + | "apks"; + + export type ModuleFeatures = Partial>; + + export type RootSoluctions = Partial, "unknown">, string>>; + + export interface Module extends BaseModule { + updateJson?: string; + added: number; + timestamp?: number; + size?: number; + track: Track; + versions: Version[]; + + features: ModuleFeatures; + + minApi?: number; + maxApi?: number; + + license?: string; + homepage?: string; + support?: string; + donate?: string; + cover?: string; + icon?: string; + require?: string[]; + screenshots?: string[]; + category?: string; + categories?: string[]; + stars?: number; + readme?: string; + note?: ModuleNote; + root?: RootSoluctions; + + /** + * Non-user definable + */ + verified: boolean; + + /** + * Local modules only + */ + __mmrl__local__module__?: boolean; + __mmrl_repo_source?: string[]; + } + + export interface BaseTrack { + type: string; + added: number; + source: string; + } + + export interface Track extends BaseTrack { + verified: boolean; + antifeatures?: string | string[]; + + /** + * @deprecated + */ + license: string; + /** + * @deprecated + */ + homepage: string; + /** + * @deprecated + */ + support: string; + /** + * @deprecated + */ + donate: string; + /** + * @deprecated + */ + cover?: string; + /** + * @deprecated + */ + icon?: string; + /** + * @deprecated + */ + require?: string[]; + /** + * @deprecated + */ + screenshots?: string[]; + /** + * @deprecated + */ + category?: string; + /** + * @deprecated + */ + categories?: string[]; + /** + * @deprecated + */ + readme?: string; + + /** + * Not Supported + */ + stars?: number; + } + + export interface Version { + timestamp: number; + version: string; + versionCode: number; + zipUrl: string; + size?: number; + changelog: string; + } + + export interface UpdateJson { + version: string; + versionCode: number; + zipUrl: string; + changelog: string; + } + + export interface LicenseSPX { + isDeprecatedLicenseId: boolean; + isFsfLibre: boolean; + licenseText: string; + standardLicenseTemplate: string; + name: string; + licenseId: string; + crossRef: CrossRef[]; + seeAlso: string[]; + isOsiApproved: boolean; + licenseTextHtml: string; + } + + export interface CrossRef { + match: string; + url: string; + isValid: boolean; + isLive: boolean; + timestamp: string; + isWayBackLink: boolean; + order: number; + } + + // OnsenUI Types + /** + * @extends {Event} + */ + export interface DeviceBackButtonEvent extends Event { + /** + * Runs the handler for the immediate parent that supports device back button. + * @returns {void} + */ + callParentHandler: () => void; + } +}