diff --git a/.gitignore b/.gitignore index 347e252..02da45d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,4 @@ -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Log/OS Files -*.log - -# Android Studio generated files and folders -captures/ -.externalNativeBuild/ -.cxx/ -*.apk -output.json - -# IntelliJ -*.iml .idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml - -# Keystore files -*.jks -*.keystore - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Android Profiling -*.hprof +bun.lockb +node_modules/ +public/css/styles.css \ No newline at end of file diff --git a/README.md b/README.md index 57094db..91d3341 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,50 @@ -# modules-app -An open-source Magisk modules app store +# modules +[](http://unlicense.org/) +[](https://t.me/pontushub) + +An open-source Magisk modules and root/FOSS app store + +This is my first project which uses `bun`. + +Modules does **NOT** host the modules themselves, and only provides downloads to third-party sources. It's database is open source, and is the `apps.json` and `modules.json` files. + +Unless a module is labeled `SelfHost`, I am not hosting the file in question. + +# Contributing +If you would like to contribute, please know I appreciate it very much! + +The backend works on Node.js with Express, EJS, and Tailwind CSS. Each time the code is updated, make sure you run `bun run build:css` to use the latest needed styles. + +You can either contribute code (start with `app.js`) or your time to the `apps.json` database and `modules.json`. JSON formatting is pretty easy to learn. + +Please create pull requests and issues, and be generally respectful and patient. + +# Setting up and self-hosting +1. First, clone the repository: + ```bash + git clone https://github.com/ihatenodejs/modules.git + ``` +2. Next, let's install Bun (if you don't already have it). You can see more about Bun [here](https://bun.sh/). + + **Windows (PowerShell)** + ``` + powershell -c "irm bun.sh/install.ps1 | iex" + ``` + **Linux/macOS:** + ```bash + curl -fsSL https://bun.sh/install | bash + ``` +3. Now, let's install everything with: + ``` + bun install + ``` +4. After that, we'll build the Tailwind CSS: + ``` + bun run build:css + ``` +5. Finally, we start the server on port `3000`: + ``` + bun app.js + ``` + +Your server can now be accessed at http://localhost:3000, and can be used in conjunction with a reverse proxy to get a functional site up. \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..91c2bd9 --- /dev/null +++ b/app.js @@ -0,0 +1,94 @@ +const express = require('express'); +const fs = require('fs'); +const path = require('path'); +const validator = require('validator'); +const helmet = require('helmet'); + +const app = express(); +const PORT = 3000; + +app.set('view engine', 'ejs'); +app.set('views', path.join(__dirname, 'src/views')); +app.use(helmet()); +app.use(express.static(path.join(__dirname, 'public'))); +app.use(helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "https://cdnjs.cloudflare.com"], + styleSrc: ["'self'", "https://cdnjs.cloudflare.com"], + imgSrc: ["'self'", "data:"], + connectSrc: ["'self'"], + fontSrc: ["'self'", "https://cdnjs.cloudflare.com"], + objectSrc: ["'none'"], + upgradeInsecureRequests: [], + }, +})); + +// routes +app.get('/', (req, res) => { + res.render('pages/home', { title: 'modules.' }); +}); + +app.get('/apps', (req, res) => { + fs.readFile('apps.json', (err, data) => { + if (err) throw err; + const apps = JSON.parse(data); + res.render('pages/apps', { title: 'Apps', apps: apps }); + }); +}); + +app.get('/modules', (req, res) => { + fs.readFile('modules.json', (err, data) => { + if (err) throw err; + const modules = JSON.parse(data); + res.render('pages/modules', { title: 'Modules | modules.', modules: modules }); + }); +}); + +app.get('/modules/:id', (req, res) => { + fs.readFile('modules.json', (err, data) => { + if (err) throw err; + const modules = JSON.parse(data); + const module = modules.find(m => m.id === req.params.id); + if (module) { + res.render('templates/module', { module: module }); + } else { + res.status(404).send('Module not found'); + } + }); +}); + +app.get('/apps/:id', (req, res) => { + fs.readFile('apps.json', (err, data) => { + if (err) throw err; + const apps = JSON.parse(data); + const app = apps.find(m => m.id === req.params.id); + if (app) { + res.render('templates/app', { app: app }); + } else { + res.status(404).send('App not found'); + } + }); +}); + +app.get('/download', (req, res) => { + const { name, type, version, arch } = req.query; + if (!validator.isAlphanumeric(name.replace(/\s/g, '')) || !validator.isAlphanumeric(type) || !validator.isAlphanumeric(version.replace(/\./g, '')) || !validator.isAlphanumeric(arch)) { + return res.status(400).send('Invalid input, mister!'); + } + fs.readFile(`${type}s.json`, (err, data) => { + if (err) throw err; + const items = JSON.parse(data); + const item = items.find(i => i.name === name); + if (item && item.downloadLinks && item.downloadLinks[version] && item.downloadLinks[version][arch]) { + const downloadLink = item.downloadLinks[version][arch]; + res.render('templates/download', { name: item.name, type, version, arch, downloadLink }); + } else { + res.status(404).send('Download link not found'); + } + }); +}); + +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`); +}); \ No newline at end of file diff --git a/apps.json b/apps.json new file mode 100644 index 0000000..1cec3c4 --- /dev/null +++ b/apps.json @@ -0,0 +1,101 @@ +[ + { + "id": "1", + "name": "Muzza", + "icon": "/img/apps/muzza.png", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v0.6.3-rc-2": { + "foss": "https://github.com/Maloy-Android/Muzza/releases/download/v0.6.3-rc-2/Muzza-0.6.3-rc-2.final.-foss-release.apk", + "full": "https://github.com/Maloy-Android/Muzza/releases/download/v0.6.3-rc-2/Muzza-0.6.3-rc-2.final.-full-release.apk" + }, + "v0.6.3-rc-1": { + "foss": "https://github.com/Maloy-Android/Muzza/releases/download/v0.6.3-rc1/Muzza-0.6.3-rc1-foss-release.apk", + "full": "https://github.com/Maloy-Android/Muzza/releases/download/v0.6.3-rc1/Muzza-0.6.3-rc1-full-release.apk" + }, + "v0.6.3": { + "foss": "https://github.com/Maloy-Android/Muzza/releases/download/v0.6.3/Muzza-0.6.3-foss-release.apk", + "full": "https://github.com/Maloy-Android/Muzza/releases/download/v0.6.3/Muzza-0.6.3-full-release.apk" + } + }, + "description": "Muzza is a Material 3 YouTube Music client. It features ad-free playback from YouTube/YouTube Music, background playback, search via YouTube Music, cache/download, lyrics, Android Auto, and Discord rich presence support.\n\nIf you're in a region where YouTube Music is not supported, you won't be able to use this app unless you have a proxy or VPN to connect to a YouTube Music supported region.", + "github": "https://github.com/Maloy-Android/Muzza" + }, + { + "id": "2", + "name": "NewPipe", + "icon": "/img/apps/newpipe.png", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v0.27.4": { + "all": "https://github.com/TeamNewPipe/NewPipe/releases/download/v0.27.4/NewPipe_v0.27.4.apk" + }, + "v0.27.3": { + "all": "https://github.com/TeamNewPipe/NewPipe/releases/download/v0.27.3/NewPipe_v0.27.3.apk" + }, + "v0.27.2": { + "all": "https://github.com/TeamNewPipe/NewPipe/releases/download/v0.27.2/NewPipe_v0.27.2.apk" + } + }, + "description": "NewPipe is a 3rd party client that supports many services like Youtube, Peertube, SoundCloud, and Bandcamp.\n\nNewPipe works by fetching the required data from the official API (e.g. PeerTube) of the service you're using. If the official API is restricted (e.g. YouTube) for our purposes, or is proprietary, the app parses the website or uses an internal API instead. This means that you don't need an account on any service to use NewPipe.\n\nAlso, since they are free and open source software, neither the app nor the Extractor use any proprietary libraries or frameworks, such as Google Play Services. This means you can use NewPipe on devices or custom ROMs that do not have Google apps installed.\n\nTHIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN THE NEWPIPE GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.", + "github": "https://github.com/TeamNewPipe/NewPipe" + }, + { + "id": "3", + "name": "FetchIt", + "icon": "/img/apps/fetchit.jpg", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v1.4": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.4/app-release.apk" + }, + "v1.3": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.3/app-release.apk" + }, + "v1.2": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.2/app-release.apk" + }, + "v1.1": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.1/app-release.apk" + }, + "v1.0": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.0/app-release.apk" + } + }, + "description": "NewPipe is a 3rd party client that supports many services like Youtube, Peertube, SoundCloud, and Bandcamp.\n\nNewPipe works by fetching the required data from the official API (e.g. PeerTube) of the service you're using. If the official API is restricted (e.g. YouTube) for our purposes, or is proprietary, the app parses the website or uses an internal API instead. This means that you don't need an account on any service to use NewPipe.\n\nAlso, since they are free and open source software, neither the app nor the Extractor use any proprietary libraries or frameworks, such as Google Play Services. This means you can use NewPipe on devices or custom ROMs that do not have Google apps installed.\n\nTHIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN THE NEWPIPE GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.", + "github": "https://github.com/TeamNewPipe/NewPipe" + }, + { + "id": "4", + "name": "Root Detector", + "icon": "/img/apps/rootdetector.jpg", + "price": "FREE", + "foss": false, + "selfHosted": true, + "downloadLinks": { + "v1.4": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.4/app-release.apk" + }, + "v1.3": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.3/app-release.apk" + }, + "v1.2": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.2/app-release.apk" + }, + "v1.1": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.1/app-release.apk" + }, + "v1.0": { + "all": "https://github.com/tharunbirla/FetchIt/releases/download/v1.0/app-release.apk" + } + }, + "description": "NewPipe is a 3rd party client that supports many services like Youtube, Peertube, SoundCloud, and Bandcamp.\n\nNewPipe works by fetching the required data from the official API (e.g. PeerTube) of the service you're using. If the official API is restricted (e.g. YouTube) for our purposes, or is proprietary, the app parses the website or uses an internal API instead. This means that you don't need an account on any service to use NewPipe.\n\nAlso, since they are free and open source software, neither the app nor the Extractor use any proprietary libraries or frameworks, such as Google Play Services. This means you can use NewPipe on devices or custom ROMs that do not have Google apps installed.\n\nTHIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN THE NEWPIPE GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.", + "github": "https://github.com/TeamNewPipe/NewPipe" + } +] \ No newline at end of file diff --git a/modules.json b/modules.json new file mode 100644 index 0000000..966f1f0 --- /dev/null +++ b/modules.json @@ -0,0 +1,106 @@ +[ + { + "id": "1", + "name": "ReVanced eXtended", + "icon": "/img/modules/rvx.png", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v19.16.39": { + "all": "https://github.com/NoName-exe/revanced-extended/releases/download/126/youtube-revanced-extended-magisk-v19.16.39-all.zip" + }, + "v19.25.39": { + "all": "https://github.com/NoName-exe/revanced-extended/releases/download/117/youtube-revanced-extended-magisk-v19.25.39-all.zip" + } + }, + "description": "ReVanced eXtended is a mod that extends the functionality of ReVanced. It has additional features to the standard ReVanced patches to YouTube. It has a custom icon.\n\nIt features modifications such as SponsorBlock (skipping segments like sponsorships in videos), local YouTube Premium, and more. The app is themed to fit YouTube premium, and is configured through an option in the YouTube settings.\n\nThis option is lighter on battery, as it doesn't use MicroG, like the non-root option. This module will install the YouTube ReVanced eXtended app. You must install the seperate ReVanced eXtended Music module if you would like YouTube Music.\n\nYou can get more information about ReVanced eXtended at it's GitHub page (listed below).", + "github": "https://github.com/NoName-exe/revanced-extended" + }, + { + "id": "2", + "name": "ReVanced eXtended Music", + "icon": "/img/modules/rvx-music.png", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v7.16.53": { + "arm-v7a": "https://github.com/NoName-exe/revanced-extended/releases/download/126/youtube-music-revanced-extended-magisk-v7.16.53-arm-v7a.zip", + "arm64-v8a": "https://github.com/NoName-exe/revanced-extended/releases/download/126/youtube-music-revanced-extended-magisk-v7.16.53-arm64-v8a.zip" + }, + "v7.08.52": { + "arm-v7a": "https://github.com/NoName-exe/revanced-extended/releases/download/117/youtube-music-revanced-extended-magisk-v7.08.52-arm-v7a.zip", + "arm64-v8a": "https://github.com/NoName-exe/revanced-extended/releases/download/117/youtube-music-revanced-extended-magisk-v7.08.52-arm64-v8a.zip" + } + }, + "description": "ReVanced eXtended Music is a mod that extends the functionality of ReVanced Music. It has additional features to the standard ReVanced patches to YouTube Music, with a custom icon.\n\nIt features modifications such as SponsorBlock (skipping segments like non-music segments in music videos), local YouTube Premium, and more. The app is themed to fit YouTube premium, and is configured through an option in the YouTube Music settings.\n\nThis option is lighter on battery, as it doesn't use MicroG, like the non-root option. This module will install the YouTube ReVanced eXtended Music app. You must install the seperate ReVanced eXtended module if you would like regular YouTube.\n\nYou can get more information about ReVanced eXtended at it's GitHub page (listed below).", + "github": "https://github.com/NoName-exe/revanced-extended" + }, + { + "id": "3", + "name": "Tricky Store", + "icon": "/img/modules/ts.png", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v1.2.0": { + "all": "https://github.com/5ec1cff/TrickyStore/releases/download/1.2.0/Tricky-Store-v1.2.0-155-331f6fe-release.zip" + }, + "v1.1.3 ": { + "all": "https://github.com/5ec1cff/TrickyStore/releases/download/1.1.3/Tricky-Store-v1.1.3-128-820c2b2-release.zip" + }, + "v1.1.2 ": { + "all": "https://github.com/5ec1cff/TrickyStore/releases/download/1.1.2/Tricky-Store-v1.1.2-123-94176b9-release.zip" + } + }, + "description": "A trick of keystore. Android 10 or above is required.\n\nThis module is used for modifying the certificate chain generated for Android Key Attestation. In combination with a keybox.xml file, you can spoof Play Integrity requests to return a STRONG hardware-backed attestation status.\n\nYou can get more information about Tricky Store at it's GitHub page (listed below).", + "github": "https://github.com/5ec1cff/TrickyStore" + }, + { + "id": "4", + "name": "LSPosed Mod", + "icon": "/img/modules/lsposed.jpg", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v1.2.0": { + "all": "https://github.com/5ec1cff/TrickyStore/releases/download/1.2.0/Tricky-Store-v1.2.0-155-331f6fe-release.zip" + }, + "v1.1.3 ": { + "all": "https://github.com/5ec1cff/TrickyStore/releases/download/1.1.3/Tricky-Store-v1.1.3-128-820c2b2-release.zip" + }, + "v1.1.2 ": { + "all": "https://github.com/5ec1cff/TrickyStore/releases/download/1.1.2/Tricky-Store-v1.1.2-123-94176b9-release.zip" + } + }, + "description": "LSPosed is a great XPosed Framework, but it has a big problem, only manager can manage scope. LSPosed team don't accept PR for CLI or API Module, the TODO issues are old more one year and never completed, is more important the GUI changed many times but not CLI or API Module. In my fork API Module and CLI are implemented. CLI require root user because must access files readable only by root.\n\nA Riru / Zygisk module trying to provide an ART hooking framework which delivers consistent APIs with the OG Xposed, leveraging LSPlant hooking framework.", + "github": "https://github.com/mywalkb/LSPosed_mod" + }, + { + "id": "5", + "name": "Play Integrity Fix", + "icon": "/img/modules/ts.png", + "price": "FREE", + "foss": true, + "selfHosted": false, + "downloadLinks": { + "v18.1": { + "all": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v18.1/PlayIntegrityFix_v18.1.zip" + }, + "v18.0": { + "all": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v18.0/PlayIntegrityFix_v18.0.zip" + }, + "v17.9": { + "all": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v17.9/PlayIntegrityFix_v17.9.zip" + }, + "v17.8": { + "all": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v17.8/PlayIntegrityFix_v17.8.zip" + } + }, + "description": "This module tries to fix Play Integrity and SafetyNet verdicts to get a valid attestation.\n\nThis module is not made to hide root, nor to avoid detections in other apps. It only serves to pass Device verdict in the Play Integrity tests and certify your device. All issues created to report a non-Google app not working will be closed without notice.\n\nPLEASE SEE GITHUB FOR SETUP INSTRUCTIONS.", + "github": "https://github.com/chiteroman/PlayIntegrityFix" + } +] \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..ce19f6d --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "modules", + "version": "1.0.0", + "description": "An open-source Magisk modules app store ", + "main": "app.js", + "devDependencies": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/ihatenodejs/modules.git" + }, + "author": "ihatenodejs", + "license": "Unlicense", + "bugs": { + "url": "https://github.com/ihatenodejs/modules/issues" + }, + "homepage": "https://github.com/ihatenodejs/modules#readme", + "scripts": { + "build:css": "tailwindcss -i ./src/css/styles.css -o ./public/css/styles.css" + }, + "dependencies": { + "ejs": "^3.1.10", + "express": "^4.21.2", + "helmet": "^8.0.0", + "tailwindcss": "^3.4.16", + "validator": "^13.12.0" + } +} diff --git a/public/img/apps/fetchit.jpg b/public/img/apps/fetchit.jpg new file mode 100644 index 0000000..e90a9f8 Binary files /dev/null and b/public/img/apps/fetchit.jpg differ diff --git a/public/img/apps/muzza.png b/public/img/apps/muzza.png new file mode 100644 index 0000000..28f802b Binary files /dev/null and b/public/img/apps/muzza.png differ diff --git a/public/img/apps/newpipe.png b/public/img/apps/newpipe.png new file mode 100644 index 0000000..5c825a7 Binary files /dev/null and b/public/img/apps/newpipe.png differ diff --git a/public/img/apps/rootdetector.jpg b/public/img/apps/rootdetector.jpg new file mode 100644 index 0000000..c74786d Binary files /dev/null and b/public/img/apps/rootdetector.jpg differ diff --git a/public/img/modules/lsposed.jpg b/public/img/modules/lsposed.jpg new file mode 100644 index 0000000..6c2979d Binary files /dev/null and b/public/img/modules/lsposed.jpg differ diff --git a/public/img/modules/rvx-music.png b/public/img/modules/rvx-music.png new file mode 100644 index 0000000..9bf8b29 Binary files /dev/null and b/public/img/modules/rvx-music.png differ diff --git a/public/img/modules/rvx.png b/public/img/modules/rvx.png new file mode 100644 index 0000000..0b154cb Binary files /dev/null and b/public/img/modules/rvx.png differ diff --git a/public/img/modules/ts.png b/public/img/modules/ts.png new file mode 100644 index 0000000..89fd539 Binary files /dev/null and b/public/img/modules/ts.png differ diff --git a/public/js/desc-pg.js b/public/js/desc-pg.js new file mode 100644 index 0000000..cb90cc3 --- /dev/null +++ b/public/js/desc-pg.js @@ -0,0 +1,22 @@ +const dlBtn = document.getElementById('download-btn'); +const ddMenu = document.getElementById('dropdown-menu'); +const copyBtn = document.getElementById('copy-btn'); +const sourceURL = copyBtn.getAttribute('data-source-url'); + +dlBtn.addEventListener('click', (event) => { + event.stopPropagation(); + ddMenu.classList.toggle('hidden'); +}); + +document.addEventListener('click', () => { + ddMenu.classList.add('hidden'); +}); + +copyBtn.addEventListener('click', (event) => { + event.stopPropagation(); + navigator.clipboard.writeText(sourceURL).then(() => { + alert('Source URL copied to clipboard'); + }).catch((err) => { + console.error('Failed to copy: ', err); + }); +}); \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..b9a2329 --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,6 @@ +const menuBtn = document.getElementById('menu-btn'); +const mobileMenu = document.getElementById('mobile-menu'); + +menuBtn.addEventListener('click', () => { + mobileMenu.classList.toggle('hidden'); +}); \ No newline at end of file diff --git a/src/css/styles.css b/src/css/styles.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/src/css/styles.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/src/views/pages/apps.ejs b/src/views/pages/apps.ejs new file mode 100644 index 0000000..ff14f0f --- /dev/null +++ b/src/views/pages/apps.ejs @@ -0,0 +1,85 @@ + + +
+ + ++ + <% if (app.price === 'FREE') { %> + <%= app.price %> + <% } else { %> + $<%= app.price %> + <% } %> + + <% if (app.foss) { %> + FOSS + <% } %> + <% if (app.selfHosted) { %> + SelfHost + <% } %> +
+ +<%- app.description.replace(/\n/g, '
') %>
Your file is being downloaded from: <%= downloadLink %>
++ + <% if (module.price === 'FREE') { %> + <%= module.price %> + <% } else { %> + $<%= module.price %> + <% } %> + + <% if (module.foss) { %> + FOSS + <% } %> + <% if (module.selfHosted) { %> + SelfHost + <% } %> +
+ +<%- module.description.replace(/\n/g, '
') %>