├── .github └── workflows │ └── release.yml ├── .gitignore ├── README.md ├── app_brook ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── moe │ └── matsuri │ └── plugin │ └── brook │ └── BinaryProvider.kt ├── app_hysteria ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── moe │ │ └── matsuri │ │ └── exe │ │ └── hysteria │ │ └── BinaryProvider.kt │ └── res │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── mipmap-xxxhdpi │ └── ic_launcher.png ├── app_juicity ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── moe │ └── matsuri │ └── plugin │ └── singbox │ └── BinaryProvider.kt ├── app_naive ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── moe │ │ └── matsuri │ │ └── exe │ │ └── naive │ │ └── BinaryProvider.kt │ └── res │ ├── drawable │ └── ic_launcher_foreground.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ └── ic_launcher_background.xml ├── app_singbox ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── moe │ │ └── matsuri │ │ └── plugin │ │ └── singbox │ │ └── BinaryProvider.kt │ └── res │ └── mipmap-xxxhdpi │ └── ic_launcher.png ├── app_trojan-go ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── moe │ │ └── matsuri │ │ └── exe │ │ └── trojan_go │ │ └── BinaryProvider.kt │ └── res │ ├── drawable │ └── ic_launcher_foreground.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ └── ic_launcher_background.xml ├── app_tuic ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── moe │ └── matsuri │ └── exe │ └── tuic │ └── BinaryProvider.kt ├── app_tuic5 ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── moe │ └── matsuri │ └── exe │ └── tuic │ └── BinaryProvider.kt ├── app_xray ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── moe │ │ └── matsuri │ │ └── plugin │ │ └── xray │ │ └── BinaryProvider.kt │ └── res │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── mipmap-xxxhdpi │ └── ic_launcher.png ├── build.gradle ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── BuildSrc.kt ├── common ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── io │ └── nekohasekai │ └── sagernet │ └── plugin │ ├── NativePluginProvider.kt │ ├── PathProvider.kt │ └── PluginContract.kt ├── download.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── js ├── .gitignore ├── common │ ├── common.js │ ├── translate.js │ └── util.js ├── make.sh ├── package-lock.json ├── package.json ├── plugin.html ├── plugin_brook │ ├── brook.js │ └── main.js ├── plugin_juicity │ ├── juicity.js │ └── main.js ├── plugin_singbox │ ├── main.js │ ├── shadowtls.js │ └── wireguard.js ├── plugin_xray │ ├── main.js │ ├── ss2022.js │ └── vless.js └── webpack.config.js ├── make.sh ├── release.keystore ├── requirement.sh └── settings.gradle /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Build 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | tag: 6 | description: 'Release Tag' 7 | required: true 8 | plugin: 9 | description: 'Plugin to build' 10 | required: true 11 | publish: 12 | description: 'Publish: If want ignore' 13 | required: false 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | - name: Gradle cache 22 | uses: actions/cache@v2 23 | with: 24 | path: ~/.gradle 25 | key: gradle-${{ hashFiles('**/*.gradle.kts') }} 26 | - name: Release Build 27 | run: | 28 | export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}" 29 | bash requirement.sh 30 | dl=1 bash make.sh ${{ github.event.inputs.plugin }} 31 | APK=$(find . -name '*arm64-v8a*.apk') 32 | APK=$(dirname $APK) 33 | echo "APK=$APK" >> $GITHUB_ENV 34 | - uses: actions/upload-artifact@v2 35 | with: 36 | name: APKs 37 | path: ${{ env.APK }} 38 | publish: 39 | name: Publish Release 40 | if: github.event.inputs.publish != 'y' 41 | runs-on: ubuntu-latest 42 | needs: build 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v2 46 | - name: Donwload Artifacts 47 | uses: actions/download-artifact@v2 48 | with: 49 | name: APKs 50 | path: artifacts 51 | - name: Release 52 | run: | 53 | wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz 54 | tar -xvf ghr.tar.gz 55 | mv ghr*linux_amd64/ghr . 56 | mkdir apks 57 | find artifacts -name "*.apk" -exec cp {} apks \; 58 | ./ghr -delete -t "${{ github.token }}" -n "${{ github.event.inputs.tag }}" "${{ github.event.inputs.tag }}" apks 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .vscode 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | 13 | /app_*/build 14 | /app_*/libs 15 | /app_*/html 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Matsuri Plugins 2 | 3 | ## To find plugin download 4 | 5 | https://matsuridayo.github.io/m-plugin/ 6 | 7 | https://github.com/MatsuriDayo/plugins/releases 8 | 9 | ## To build a plugin 10 | 11 | TODO 12 | -------------------------------------------------------------------------------- /app_brook/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.plugin.brook" 11 | versionCode = 2 12 | versionName = "v20220707-1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app_brook/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 31 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app_brook/src/main/java/moe/matsuri/plugin/brook/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.plugin.brook 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("moe.matsuri.plugin.brook", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libbrook.so" 16 | 17 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 18 | "/moe.matsuri.plugin.brook" -> ParcelFileDescriptor.open( 19 | File(getExecutable()), 20 | ParcelFileDescriptor.MODE_READ_ONLY 21 | ) 22 | else -> throw FileNotFoundException() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app_hysteria/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.exe.hysteria" 11 | versionCode = 8 12 | versionName = "1.3.5-1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app_hysteria/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | 15 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 40 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app_hysteria/src/main/java/moe/matsuri/exe/hysteria/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.exe.hysteria 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("hysteria-plugin", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libhysteria.so" 16 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 17 | "/hysteria-plugin" -> ParcelFileDescriptor.open( 18 | File(getExecutable()), 19 | ParcelFileDescriptor.MODE_READ_ONLY 20 | ) 21 | else -> throw FileNotFoundException() 22 | } 23 | } -------------------------------------------------------------------------------- /app_hysteria/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_hysteria/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_hysteria/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_hysteria/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_hysteria/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_hysteria/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_hysteria/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_hysteria/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_hysteria/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_hysteria/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_juicity/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.plugin.juicity" 11 | versionCode = 6 12 | versionName = "v0.4.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app_juicity/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 31 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app_juicity/src/main/java/moe/matsuri/plugin/singbox/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.plugin.juicity 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("moe.matsuri.plugin.juicity", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libjuicity.so" 16 | 17 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 18 | "/moe.matsuri.plugin.juicity" -> ParcelFileDescriptor.open( 19 | File(getExecutable()), 20 | ParcelFileDescriptor.MODE_READ_ONLY 21 | ) 22 | else -> throw FileNotFoundException() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app_naive/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /html 4 | -------------------------------------------------------------------------------- /app_naive/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.exe.naive" 11 | versionCode = 8 12 | versionName = "116.0.5845.92-2" 13 | splits.abi { 14 | reset() 15 | include("arm64-v8a", "armeabi-v7a") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app_naive/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app_naive/src/main/java/moe/matsuri/exe/naive/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.exe.naive 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("naive-plugin", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libnaive.so" 16 | 17 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 18 | "/naive-plugin" -> ParcelFileDescriptor.open( 19 | File(getExecutable()), 20 | ParcelFileDescriptor.MODE_READ_ONLY 21 | ) 22 | else -> throw FileNotFoundException() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app_naive/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 15 | 17 | 19 | 21 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_naive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_naive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_naive/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #E91E63 4 | -------------------------------------------------------------------------------- /app_singbox/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.plugin.singbox" 11 | versionCode = 18 12 | versionName = "v1.3.4" 13 | splits.abi { 14 | reset() 15 | include("arm64-v8a", "armeabi-v7a") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app_singbox/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app_singbox/src/main/java/moe/matsuri/plugin/singbox/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.plugin.singbox 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("moe.matsuri.plugin.singbox", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libsingbox.so" 16 | 17 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 18 | "/moe.matsuri.plugin.singbox" -> ParcelFileDescriptor.open( 19 | File(getExecutable()), 20 | ParcelFileDescriptor.MODE_READ_ONLY 21 | ) 22 | else -> throw FileNotFoundException() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app_singbox/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_singbox/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_trojan-go/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /html 4 | -------------------------------------------------------------------------------- /app_trojan-go/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.exe.trojan_go" 11 | versionCode = 1 12 | versionName = "0.10.15" 13 | splits.abi { 14 | reset() 15 | include("arm64-v8a", "armeabi-v7a") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app_trojan-go/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app_trojan-go/src/main/java/moe/matsuri/exe/trojan_go/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.exe.trojan_go 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("trojan-go-plugin", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libtrojan-go.so" 16 | 17 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 18 | "/trojan-go-plugin" -> ParcelFileDescriptor.open( 19 | File(getExecutable()), 20 | ParcelFileDescriptor.MODE_READ_ONLY 21 | ) 22 | else -> throw FileNotFoundException() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 15 | 17 | 19 | 21 | 23 | 25 | 26 | 27 | 29 | 31 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_trojan-go/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app_trojan-go/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #E91E63 4 | -------------------------------------------------------------------------------- /app_tuic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.exe.tuic" 11 | versionCode = 1 12 | versionName = "0.8.5-2" 13 | splits.abi { 14 | reset() 15 | include("arm64-v8a", "x86_64") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app_tuic/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | 15 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 39 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app_tuic/src/main/java/moe/matsuri/exe/tuic/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.exe.tuic 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("tuic-plugin", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libtuic.so" 16 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 17 | "/tuic-plugin" -> ParcelFileDescriptor.open( 18 | File(getExecutable()), 19 | ParcelFileDescriptor.MODE_READ_ONLY 20 | ) 21 | else -> throw FileNotFoundException() 22 | } 23 | } -------------------------------------------------------------------------------- /app_tuic5/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.exe.tuic5" 11 | versionCode = 2 12 | versionName = "1.0.0-3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app_tuic5/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | 15 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 39 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app_tuic5/src/main/java/moe/matsuri/exe/tuic/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.exe.tuic5 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("tuic-v5-plugin", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libtuic.so" 16 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 17 | "/tuic-v5-plugin" -> ParcelFileDescriptor.open( 18 | File(getExecutable()), 19 | ParcelFileDescriptor.MODE_READ_ONLY 20 | ) 21 | else -> throw FileNotFoundException() 22 | } 23 | } -------------------------------------------------------------------------------- /app_xray/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | setupAll() 7 | 8 | android { 9 | defaultConfig { 10 | applicationId = "moe.matsuri.plugin.xray" 11 | versionCode = 20 12 | versionName = "v1.8.6" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app_xray/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app_xray/src/main/java/moe/matsuri/plugin/xray/BinaryProvider.kt: -------------------------------------------------------------------------------- 1 | package moe.matsuri.plugin.xray 2 | 3 | import android.net.Uri 4 | import android.os.ParcelFileDescriptor 5 | import io.nekohasekai.sagernet.plugin.NativePluginProvider 6 | import io.nekohasekai.sagernet.plugin.PathProvider 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | class BinaryProvider : NativePluginProvider() { 11 | override fun populateFiles(provider: PathProvider) { 12 | provider.addPath("moe.matsuri.plugin.xray", 0b111101101) 13 | } 14 | 15 | override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libxray.so" 16 | 17 | override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) { 18 | "/moe.matsuri.plugin.xray" -> ParcelFileDescriptor.open( 19 | File(getExecutable()), 20 | ParcelFileDescriptor.MODE_READ_ONLY 21 | ) 22 | else -> throw FileNotFoundException() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app_xray/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_xray/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_xray/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_xray/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_xray/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_xray/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_xray/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_xray/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app_xray/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/app_xray/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:7.3.1' 9 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | task clean(type: Delete) { 17 | delete rootProject.buildDir 18 | } -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-gradle-plugin` 3 | `kotlin-dsl` 4 | } 5 | 6 | repositories { 7 | google() 8 | mavenCentral() 9 | gradlePluginPortal() 10 | maven(url = "https://jitpack.io") 11 | } 12 | 13 | dependencies { 14 | implementation(kotlin("gradle-plugin", "1.6.10")) 15 | 16 | implementation("com.android.tools.build:gradle:7.3.1") 17 | implementation("com.android.tools.build:gradle-api:7.3.1") 18 | } 19 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/BuildSrc.kt: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.BaseExtension 2 | import com.android.build.gradle.internal.api.BaseVariantOutputImpl 3 | import org.gradle.api.JavaVersion 4 | import org.gradle.api.Project 5 | import org.gradle.api.plugins.ExtensionAware 6 | import org.gradle.kotlin.dsl.getByName 7 | import org.gradle.util.GUtil.loadProperties 8 | import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions 9 | import com.android.build.gradle.AbstractAppExtension 10 | import java.io.File 11 | import java.security.MessageDigest 12 | 13 | private val Project.android get() = extensions.getByName("android") 14 | private lateinit var flavor: String 15 | 16 | private val javaVersion = JavaVersion.VERSION_1_8 17 | 18 | fun Project.requireFlavor(): String { 19 | if (::flavor.isInitialized) return flavor 20 | if (gradle.startParameter.taskNames.isNotEmpty()) { 21 | val taskName = gradle.startParameter.taskNames[0] 22 | when { 23 | taskName.contains("assemble") -> { 24 | flavor = taskName.substringAfter("assemble") 25 | return flavor 26 | } 27 | taskName.contains("install") -> { 28 | flavor = taskName.substringAfter("install") 29 | return flavor 30 | } 31 | taskName.contains("publish") -> { 32 | flavor = taskName.substringAfter("publish").substringBefore("Bundle") 33 | return flavor 34 | } 35 | } 36 | } 37 | 38 | flavor = "" 39 | return flavor 40 | } 41 | 42 | fun Project.requireLocalProperties(): java.util.Properties? { 43 | if (project.rootProject.file("local.properties").exists()) { 44 | return loadProperties(rootProject.file("local.properties")) 45 | } 46 | return null 47 | } 48 | 49 | fun Project.setupCommon() { 50 | dependencies.apply { 51 | add("implementation", project(":common")) 52 | } 53 | 54 | android.apply { 55 | compileSdkVersion(33) 56 | 57 | defaultConfig.apply { 58 | minSdk = 21 59 | targetSdk = 33 60 | } 61 | 62 | compileOptions { 63 | sourceCompatibility = javaVersion 64 | targetCompatibility = javaVersion 65 | } 66 | 67 | (android as ExtensionAware).extensions.getByName("kotlinOptions").apply { 68 | jvmTarget = javaVersion.toString() 69 | } 70 | 71 | lintOptions { 72 | isShowAll = true 73 | isCheckAllWarnings = true 74 | isCheckReleaseBuilds = false 75 | isWarningsAsErrors = true 76 | textOutput = project.file("build/lint.txt") 77 | htmlOutput = project.file("build/lint.html") 78 | } 79 | 80 | // 81 | 82 | sourceSets.getByName("main") { 83 | jniLibs.srcDir("libs") 84 | assets.srcDir("html") 85 | } 86 | 87 | splits.abi { 88 | if (requireFlavor().startsWith("Fdroid")) { 89 | isEnable = false 90 | } else { 91 | isEnable = true 92 | isUniversalApk = false 93 | } 94 | } 95 | 96 | (this as? AbstractAppExtension)?.apply { 97 | applicationVariants.all { 98 | outputs.all { 99 | this as BaseVariantOutputImpl 100 | outputFileName = 101 | outputFileName.replace(project.name, "${project.name}-plugin-$versionName") 102 | .replace("-release", "") 103 | .replace("-oss", "") 104 | .replace("app_", "") 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | fun Project.setupRelease() { 112 | val lp = requireLocalProperties()!! 113 | val keystorePwd = lp.getProperty("KEYSTORE_PASS") ?: System.getenv("KEYSTORE_PASS") 114 | val alias = lp.getProperty("ALIAS_NAME") ?: System.getenv("ALIAS_NAME") 115 | val pwd = lp.getProperty("ALIAS_PASS") ?: System.getenv("ALIAS_PASS") 116 | 117 | android.apply { 118 | if (keystorePwd != null) { 119 | signingConfigs { 120 | create("release") { 121 | storeFile = rootProject.file("release.keystore") 122 | storePassword = keystorePwd 123 | keyAlias = alias 124 | keyPassword = pwd 125 | } 126 | } 127 | } 128 | 129 | buildTypes { 130 | getByName("release") { 131 | proguardFiles( 132 | getDefaultProguardFile("proguard-android-optimize.txt"), 133 | file("proguard-rules.pro") 134 | ) 135 | isMinifyEnabled = true 136 | } 137 | 138 | val key = signingConfigs.findByName("release") 139 | if (key != null) { 140 | getByName("release").signingConfig = key 141 | } 142 | } 143 | } 144 | } 145 | 146 | fun Project.setupAll() { 147 | setupCommon() 148 | setupRelease() 149 | } -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /html 4 | -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.konan.properties.loadProperties 2 | 3 | plugins { 4 | id("com.android.library") 5 | id("kotlin-android") 6 | } 7 | 8 | android { 9 | compileSdk = 33 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | targetSdk = 33 14 | } 15 | 16 | compileOptions { 17 | sourceCompatibility = JavaVersion.VERSION_1_8 18 | targetCompatibility = JavaVersion.VERSION_1_8 19 | } 20 | kotlinOptions { 21 | jvmTarget = "1.8" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /common/src/main/java/io/nekohasekai/sagernet/plugin/NativePluginProvider.kt: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * * 3 | * Copyright (C) 2021 by nekohasekai * 4 | * * 5 | * This program is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * This program is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with this program. If not, see . * 17 | * * 18 | ******************************************************************************/ 19 | 20 | package io.nekohasekai.sagernet.plugin 21 | 22 | import android.content.ContentProvider 23 | import android.content.ContentValues 24 | import android.database.Cursor 25 | import android.database.MatrixCursor 26 | import android.net.Uri 27 | import android.os.Bundle 28 | import android.os.ParcelFileDescriptor 29 | 30 | abstract class NativePluginProvider : ContentProvider() { 31 | override fun getType(uri: Uri): String? = "application/x-elf" 32 | 33 | override fun onCreate(): Boolean = true 34 | 35 | /** 36 | * Provide all files needed for native plugin. 37 | * 38 | * @param provider A helper object to use to add files. 39 | */ 40 | protected abstract fun populateFiles(provider: PathProvider) 41 | 42 | override fun query( 43 | uri: Uri, 44 | projection: Array?, 45 | selection: String?, 46 | selectionArgs: Array?, 47 | sortOrder: String?, 48 | ): Cursor? { 49 | check(selection == null && selectionArgs == null && sortOrder == null) 50 | val result = MatrixCursor(projection) 51 | populateFiles(PathProvider(uri, result)) 52 | return result 53 | } 54 | 55 | /** 56 | * Returns executable entry absolute path. 57 | * This is used for fast mode initialization where ss-local launches your native binary at the path given directly. 58 | * In order for this to work, plugin app is encouraged to have the following in its AndroidManifest.xml: 59 | * - android:installLocation="internalOnly" for 60 | * - android:extractNativeLibs="true" for 61 | * 62 | * Default behavior is throwing UnsupportedOperationException. If you don't wish to use this feature, use the 63 | * default behavior. 64 | * 65 | * @return Absolute path for executable entry. 66 | */ 67 | open fun getExecutable(): String = throw UnsupportedOperationException() 68 | 69 | abstract fun openFile(uri: Uri): ParcelFileDescriptor 70 | override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor { 71 | check(mode == "r") 72 | return openFile(uri) 73 | } 74 | 75 | override fun call(method: String, arg: String?, extras: Bundle?): Bundle? = when (method) { 76 | PluginContract.METHOD_GET_EXECUTABLE -> { 77 | Bundle().apply { 78 | putString(PluginContract.EXTRA_ENTRY, getExecutable()) 79 | } 80 | } 81 | else -> super.call(method, arg, extras) 82 | } 83 | 84 | // Methods that should not be used 85 | override fun insert(uri: Uri, values: ContentValues?): Uri? = 86 | throw UnsupportedOperationException() 87 | 88 | override fun update( 89 | uri: Uri, 90 | values: ContentValues?, 91 | selection: String?, 92 | selectionArgs: Array?, 93 | ): Int = 94 | throw UnsupportedOperationException() 95 | 96 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 97 | throw UnsupportedOperationException() 98 | } 99 | -------------------------------------------------------------------------------- /common/src/main/java/io/nekohasekai/sagernet/plugin/PathProvider.kt: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * * 3 | * Copyright (C) 2021 by nekohasekai * 4 | * * 5 | * This program is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * This program is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with this program. If not, see . * 17 | * * 18 | ******************************************************************************/ 19 | 20 | package io.nekohasekai.sagernet.plugin 21 | 22 | import android.database.MatrixCursor 23 | import android.net.Uri 24 | import java.io.File 25 | 26 | /** 27 | * Helper class to provide relative paths of files to copy. 28 | */ 29 | class PathProvider internal constructor(baseUri: Uri, private val cursor: MatrixCursor) { 30 | private val basePath = baseUri.path?.trim('/') ?: "" 31 | 32 | fun addPath(path: String, mode: Int = 0b110100100): PathProvider { 33 | val trimmed = path.trim('/') 34 | if (trimmed.startsWith(basePath)) cursor.newRow() 35 | .add(PluginContract.COLUMN_PATH, trimmed) 36 | .add(PluginContract.COLUMN_MODE, mode) 37 | return this 38 | } 39 | fun addTo(file: File, to: String = "", mode: Int = 0b110100100): PathProvider { 40 | var sub = to + file.name 41 | if (basePath.startsWith(sub)) if (file.isDirectory) { 42 | sub += '/' 43 | file.listFiles()!!.forEach { addTo(it, sub, mode) } 44 | } else addPath(sub, mode) 45 | return this 46 | } 47 | fun addAt(file: File, at: String = "", mode: Int = 0b110100100): PathProvider { 48 | if (basePath.startsWith(at)) { 49 | if (file.isDirectory) file.listFiles()!!.forEach { addTo(it, at, mode) } else addPath(at, mode) 50 | } 51 | return this 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /common/src/main/java/io/nekohasekai/sagernet/plugin/PluginContract.kt: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * * 3 | * Copyright (C) 2021 by nekohasekai * 4 | * * 5 | * This program is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * This program is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with this program. If not, see . * 17 | * * 18 | ******************************************************************************/ 19 | 20 | package io.nekohasekai.sagernet.plugin 21 | 22 | object PluginContract { 23 | 24 | const val ACTION_NATIVE_PLUGIN = "io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN" 25 | const val EXTRA_ENTRY = "io.nekohasekai.sagernet.plugin.EXTRA_ENTRY" 26 | const val METADATA_KEY_ID = "io.nekohasekai.sagernet.plugin.id" 27 | const val METADATA_KEY_EXECUTABLE_PATH = "io.nekohasekai.sagernet.plguin.executable_path" 28 | const val METHOD_GET_EXECUTABLE = "sagernet:getExecutable" 29 | 30 | const val COLUMN_PATH = "path" 31 | const val COLUMN_MODE = "mode" 32 | const val SCHEME = "plugin" 33 | const val AUTHORITY = "io.nekohasekai.sagernet" 34 | } 35 | -------------------------------------------------------------------------------- /download.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | mkdir_libs() { 4 | rm -rf "$1" 5 | mkdir "$1" 6 | cd "$1" 7 | mkdir arm64-v8a armeabi-v7a x86 x86_64 8 | } 9 | 10 | unzip_xray() { 11 | rm -rf tmp 12 | unzip -d tmp tmp.zip 13 | mv tmp/xray "$1"/libxray.so 14 | rm -rf tmp tmp.zip 15 | } 16 | 17 | unzip_singbox() { 18 | rm -rf tmp 19 | mkdir tmp 20 | tar -zxvf tmp.tar.gz -C tmp 21 | mv tmp/*/sing-box "$1"/libsingbox.so 22 | rm -rf tmp tmp.tar.gz 23 | } 24 | 25 | unzip_juicity() { 26 | rm -rf tmp 27 | unzip -d tmp tmp.zip 28 | mv tmp/juicity-client "$1"/libjuicity.so 29 | rm -rf tmp tmp.zip 30 | } 31 | 32 | unzip_naive() { 33 | rm -rf tmp 34 | mkdir tmp 35 | tar -xvf naiveproxy.tar.xz -C tmp 36 | mv tmp/*/naive "$1"/libnaive.so 37 | rm -rf tmp naiveproxy.tar.xz 38 | } 39 | 40 | download_xray() { 41 | VERSION="v1.8.6" 42 | mkdir_libs "app_xray/libs" 43 | 44 | dl_and_chmod arm64-v8a/libxray.so "https://github.com/maskedeken/Xray-core/releases/download/$VERSION/xray-android-arm64" 45 | dl_and_chmod armeabi-v7a/libxray.so "https://github.com/maskedeken/Xray-core/releases/download/$VERSION/xray-android-arm" 46 | dl_and_chmod x86_64/libxray.so "https://github.com/maskedeken/Xray-core/releases/download/$VERSION/xray-android-x64" 47 | dl_and_chmod x86/libxray.so "https://github.com/maskedeken/Xray-core/releases/download/$VERSION/xray-android-x86" 48 | } 49 | 50 | download_singbox() { 51 | VERSION="1.3.4" 52 | mkdir_libs "app_singbox/libs" 53 | dl_and_chmod arm64-v8a/libsingbox.so "https://github.com/maskedeken/sing-box/releases/download/v$VERSION/sing-box-android-arm64" 54 | dl_and_chmod armeabi-v7a/libsingbox.so "https://github.com/maskedeken/sing-box/releases/download/v$VERSION/sing-box-android-arm" 55 | } 56 | 57 | download_naive() { 58 | VERSION="116.0.5845.92-2" 59 | mkdir_libs "app_naive/libs" 60 | 61 | curl -Lso naiveproxy.tar.xz "https://github.com/klzgrad/naiveproxy/releases/download/v$VERSION/naiveproxy-v$VERSION-android-arm64.tar.xz" 62 | unzip_naive arm64-v8a 63 | curl -Lso naiveproxy.tar.xz "https://github.com/klzgrad/naiveproxy/releases/download/v$VERSION/naiveproxy-v$VERSION-android-arm.tar.xz" 64 | unzip_naive armeabi-v7a 65 | } 66 | 67 | download_trojan-go() { 68 | VERSION="0.10.15" 69 | mkdir_libs "app_trojan-go/libs" 70 | dl_and_chmod arm64-v8a/libtrojan-go.so "https://github.com/maskedeken/trojan-go/releases/download/v$VERSION/trojan-go-android-arm64" 71 | dl_and_chmod armeabi-v7a/libtrojan-go.so "https://github.com/maskedeken/trojan-go/releases/download/v$VERSION/trojan-go-android-arm" 72 | } 73 | 74 | dl_and_chmod() { 75 | curl -fLso "$1" "$2" && chmod +x "$1" 76 | } 77 | 78 | download_brook() { 79 | VERSION="v20220707" 80 | mkdir_libs "app_brook/libs" 81 | 82 | dl_and_chmod arm64-v8a/libbrook.so "https://github.com/txthinking/brook/releases/download/$VERSION/brook_linux_arm64" 83 | dl_and_chmod armeabi-v7a/libbrook.so "https://github.com/txthinking/brook/releases/download/$VERSION/brook_linux_arm7" 84 | dl_and_chmod x86/libbrook.so "https://github.com/txthinking/brook/releases/download/$VERSION/brook_linux_386" 85 | dl_and_chmod x86_64/libbrook.so "https://github.com/txthinking/brook/releases/download/$VERSION/brook_linux_amd64" 86 | } 87 | 88 | download_hysteria() { 89 | VERSION="v1.3.5-1" 90 | mkdir_libs "app_hysteria/libs" 91 | 92 | dl_and_chmod arm64-v8a/libhysteria.so "https://github.com/MatsuriDayo/hysteria/releases/download/$VERSION/hysteria-linux-arm64" 93 | dl_and_chmod armeabi-v7a/libhysteria.so "https://github.com/MatsuriDayo/hysteria/releases/download/$VERSION/hysteria-linux-arm" 94 | dl_and_chmod x86/libhysteria.so "https://github.com/MatsuriDayo/hysteria/releases/download/$VERSION/hysteria-linux-386" 95 | dl_and_chmod x86_64/libhysteria.so "https://github.com/MatsuriDayo/hysteria/releases/download/$VERSION/hysteria-linux-amd64" 96 | } 97 | 98 | download_tuic() { 99 | mkdir_libs "app_tuic/libs" 100 | 101 | dl_and_chmod arm64-v8a/libtuic.so "https://github.com/MatsuriDayo/tuic/releases/download/rel/tuic-client-0.8.5-2-aarch64-android" 102 | dl_and_chmod x86_64/libtuic.so "https://github.com/MatsuriDayo/tuic/releases/download/rel/tuic-client-0.8.5-2-x86_64-android" 103 | } 104 | 105 | download_tuic5() { 106 | VERSION="1.0.0-3" 107 | mkdir_libs "app_tuic5/libs" 108 | 109 | dl_and_chmod arm64-v8a/libtuic.so "https://github.com/MatsuriDayo/tuic/releases/download/rel/tuic-client-"$VERSION"-aarch64-linux-android" 110 | dl_and_chmod armeabi-v7a/libtuic.so "https://github.com/MatsuriDayo/tuic/releases/download/rel/tuic-client-"$VERSION"-armv7-linux-androideabi" 111 | dl_and_chmod x86/libtuic.so "https://github.com/MatsuriDayo/tuic/releases/download/rel/tuic-client-"$VERSION"-i686-linux-android" 112 | dl_and_chmod x86_64/libtuic.so "https://github.com/MatsuriDayo/tuic/releases/download/rel/tuic-client-"$VERSION"-x86_64-linux-android" 113 | } 114 | 115 | download_juicity() { 116 | VERSION="v0.4.3" 117 | mkdir_libs "app_juicity/libs" 118 | 119 | dl_and_chmod arm64-v8a/libjuicity.so "https://github.com/maskedeken/juicity/releases/download/"$VERSION"/juicity-client-android-arm64" 120 | dl_and_chmod armeabi-v7a/libjuicity.so "https://github.com/maskedeken/juicity/releases/download/"$VERSION"/juicity-client-android-arm" 121 | dl_and_chmod x86/libjuicity.so "https://github.com/maskedeken/juicity/releases/download/"$VERSION"/juicity-client-android-x86" 122 | dl_and_chmod x86_64/libjuicity.so "https://github.com/maskedeken/juicity/releases/download/"$VERSION"/juicity-client-android-x64" 123 | } 124 | 125 | download_"$1" 126 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 27 22:42:44 HKT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test*js 3 | test*html 4 | dist 5 | -------------------------------------------------------------------------------- /js/common/common.js: -------------------------------------------------------------------------------- 1 | export class commomClass { 2 | constructor() { 3 | this.typeMap = {} 4 | } 5 | 6 | getKV(key) { 7 | let jsonStr = neko.getKV(this.typeMap[key], key) 8 | let v = JSON.parse(jsonStr) 9 | return v.v 10 | } 11 | setKV(key, obj) { 12 | let v = { "v": obj } 13 | let jsonStr = JSON.stringify(v) 14 | neko.setKV(this.typeMap[key], key, jsonStr) 15 | } 16 | 17 | _setType(k, type) { 18 | if (type == 'string') { 19 | this.typeMap[k] = 4 20 | } else if (type == 'boolean') { 21 | this.typeMap[k] = 0 22 | } else if (type == 'number') { //TODO int 23 | this.typeMap[k] = 2 24 | // 但是preference大多是不用int的 25 | } 26 | } 27 | 28 | _applyTranslateToPreferenceScreenConfig(sb, TR) { 29 | sb.forEach((category) => { 30 | if (category["title"] == null) { 31 | category["title"] = TR(category["key"]) 32 | } 33 | category.preferences.forEach((preference) => { 34 | if (preference["title"] == null) { 35 | preference["title"] = TR(preference["key"]) 36 | } 37 | }) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /js/common/translate.js: -------------------------------------------------------------------------------- 1 | export var LANG = "" 2 | 3 | export function LANG_TR() { 4 | if (LANG.startsWith("zh-")) return "zh_CN" 5 | return LANG 6 | } 7 | 8 | export function TR(key) { 9 | let transalte_item = translates[key] 10 | if (transalte_item == null) return key // key not exist 11 | 12 | let transalte_current_lang = transalte_item[LANG_TR()] 13 | if (transalte_current_lang == null) { 14 | let transalte_default_lang = transalte_item[""] 15 | if (transalte_default_lang == null) return key 16 | return transalte_default_lang 17 | } else { 18 | return transalte_current_lang 19 | } 20 | } 21 | 22 | Object.prototype.global_debug_setlang = function (lang) { LANG = lang } 23 | 24 | // Translates 25 | 26 | export var translates = { 27 | // v2ray common 28 | serverSettings: { 29 | "zh_CN": "服务器设置", 30 | "": "Server Settings", 31 | }, 32 | serverAddress: { 33 | "zh_CN": "服务器", 34 | "": "Server Address", 35 | }, 36 | serverPort: { 37 | "zh_CN": "服务器端口", 38 | "": "Server Port", 39 | }, 40 | serverUserId: { 41 | "zh_CN": "用户 ID", 42 | "": "UUID", 43 | }, 44 | serverEncryption: { 45 | "zh_CN": "加密", 46 | "": "Encryption", 47 | }, 48 | serverMethod: { 49 | "zh_CN": "加密方式", 50 | "": "Encryption method", 51 | }, 52 | serverPassword: { 53 | "zh_CN": "密码", 54 | "": "Password", 55 | }, 56 | serverNetwork: { 57 | "zh_CN": "传输协议", 58 | "": "Network", 59 | }, 60 | serverHeader: { 61 | "zh_CN": "伪装类型", 62 | "": "Header type", 63 | }, 64 | serverSecurity: { 65 | "zh_CN": "传输层加密", 66 | "": "Transport layer encryption", 67 | }, 68 | serverHost_ws: { 69 | "zh_CN": "WebSocket 主机", 70 | "": "WebSocket Host", 71 | }, 72 | serverHost_http: { 73 | "zh_CN": "HTTP 主机", 74 | "": "HTTP Host", 75 | }, 76 | serverPath: { 77 | "zh_CN": "路径", 78 | "": "Path", 79 | }, 80 | serverPath_ws: { 81 | "zh_CN": "WebSocket 路径", 82 | "": "WebSocket Path", 83 | }, 84 | serverPath_http: { 85 | "zh_CN": "HTTP 路径", 86 | "": "HTTP Path", 87 | }, 88 | serverPath_mkcp: { 89 | "zh_CN": "mKCP 混淆密码", 90 | "": "mKCP Seed", 91 | }, 92 | serverPath_quic: { 93 | "zh_CN": "QUIC 密钥", 94 | "": "QUIC Key", 95 | }, 96 | serverPath_grpc: { 97 | "zh_CN": "gRPC 服务名称", 98 | "": "gRPC ServiceName", 99 | }, 100 | serverQuicSecurity: { 101 | "zh_CN": "QUIC 加密方式", 102 | "": "QUIC Security", 103 | }, 104 | grpcMultiMode: { 105 | "": "gRPC Multi Mode" 106 | }, 107 | grpcMultiMode_summary: { 108 | "zh_CN": "这是一个 实验性 选项,可能不会被长期保留,也不保证跨版本兼容。此模式在 测试环境中 能够带来约 20% 的性能提升,实际效果因传输速率不同而不同。", 109 | "": "This is an experimental option", 110 | }, 111 | 112 | // tls 113 | serverSecurityCategory: { 114 | "zh_CN": "安全设置", 115 | "": "Security Settings", 116 | }, 117 | serverSNI: { 118 | "zh_CN": "服务器名称指示", 119 | "": "SNI", 120 | }, 121 | utlsEnabled: { 122 | "zh_CN": "uTLS 开关", 123 | "": "uTLS Enabled", 124 | }, 125 | utlsFingerprint: { 126 | "zh_CN": "uTLS 指纹", 127 | "": "uTLS Fingerprint", 128 | }, 129 | serverALPN: { 130 | "zh_CN": "应用层协议协商", 131 | "": "ALPN", 132 | }, 133 | serverCertificates: { 134 | "zh_CN": "证书(链)", 135 | "": "Certificate (chain)", 136 | }, 137 | serverFlow: { 138 | "zh_CN": "流控", 139 | "": "Flow Control", 140 | }, 141 | serverFlowVision: { 142 | "zh_CN": "流控", 143 | "": "Flow Control", 144 | }, 145 | serverAllowInsecure: { 146 | "zh_CN": "允许不安全的连接", 147 | "": "Allow insecure", 148 | }, 149 | serverAllowInsecure_summary: { 150 | "zh_CN": "禁用证书检查. 启用后该配置安全性相当于明文", 151 | "": "Disable certificate checking. When enabled, this configuration is as secure as plaintext.", 152 | }, 153 | insecure_cleartext: { 154 | "zh_CN": "该配置 (不安全) 能够被检测识别,传输的内容对审查者完全可见,并且无法抵抗中间人篡改通讯内容.", 155 | "": "The configuration (insecure) can be detected and identified, the transmission is fully visible to the censor and is not resistant to man-in-the-middle tampering with the content of the communication.", 156 | }, 157 | serverUoT: { 158 | "": "UDP over TCP", 159 | }, 160 | publicKey: { 161 | "": "PublicKey", 162 | }, 163 | shortId: { 164 | "": "ShortId", 165 | }, 166 | spiderX: { 167 | "": "SpiderX", 168 | }, 169 | reducedIvHeadEntropy: { 170 | "": "ReducedIvHeadEntropy", 171 | }, 172 | reducedIvHeadEntropy_summary: { 173 | "zh_CN": "这是 GFWReport 针对 GFW 上观察到的类似随机流的协议阻塞行为提出的对策。 这个选项将 IV 的前 6 个字节重新映射为可打印的字符,启用后,特权网络路径上的任何人都有可能识别该协议。", 174 | "": "This is GFWReport's proposal for a countermeasure for the random stream like protocol blocking behaviour observed on GFW. This option remap the first 6 bytes of IV to printable characters, it's possible for anyone on the privileged network path to identify the protocol when enabled.", 175 | }, 176 | 177 | // Brook 178 | serverProtocol: { 179 | "": "Protocol", 180 | "zh_CN": "协议", 181 | }, 182 | withoutBrookProtocol: { 183 | "": "Without Brook Protocol" 184 | }, 185 | withoutBrookProtocol_summary: { 186 | "zh_CN": "不使用 Brook 协议", 187 | "": "Don't use Brook protocol", 188 | }, 189 | udpovertcp: { 190 | "": "UDP over TCP", 191 | }, 192 | 193 | // shadowTLS 194 | shadowTlsServerName: { 195 | "zh_CN": "ShadowTLS 伪装域名", 196 | "": "ShadowTLS Server Name", 197 | }, 198 | shadowTlsVersion: { 199 | "zh_CN": "ShadowTLS 版本", 200 | "": "ShadowTLS Version", 201 | }, 202 | shadowTlsServerPassword: { 203 | "zh_CN": "ShadowTLS 密码", 204 | "": "ShadowTLS Password", 205 | }, 206 | 207 | // WireGurad 208 | wireguardLocalAddress: { 209 | "zh_CN": "本地地址", 210 | "": "Local Address", 211 | }, 212 | wireguardPrivateKey: { 213 | "zh_CN": "私钥", 214 | "": "Private Key", 215 | }, 216 | wireguardCertificates: { 217 | "zh_CN": "节点公钥", 218 | "": "Peer Public Key", 219 | }, 220 | wireguardPeerPreSharedKey: { 221 | "zh_CN": "节点预共享密钥", 222 | "": "Peer Pre-Shared Key", 223 | }, 224 | wireguardMTU: { 225 | "": "MTU", 226 | }, 227 | wireguardDNS: { 228 | "": "DNS", 229 | }, 230 | } 231 | -------------------------------------------------------------------------------- /js/common/util.js: -------------------------------------------------------------------------------- 1 | import { Base64 } from 'js-base64' 2 | 3 | //fuck Chrome, force use core-js URL 4 | const configurator = require('core-js/configurator'); 5 | configurator({ 6 | usePolyfill: ['URL'], // polyfills will be used anyway 7 | }); 8 | require('core-js/actual/url') 9 | 10 | class utilClass { 11 | newURL(protocol) { 12 | return new URL(protocol + "://donotexist/") 13 | } 14 | 15 | tryParseURL(str) { 16 | try { 17 | return new URL(str) 18 | } catch (error) { 19 | return error.toString() 20 | } 21 | } 22 | 23 | isBlank(str) { 24 | return (!str || /^\s*$/.test(str)); 25 | } 26 | 27 | isNotBlank(str) { 28 | return !this.isBlank(str); 29 | } 30 | 31 | isPureIp(str) { 32 | return this.isIpv4(str) || this.isIpv6(str); 33 | } 34 | 35 | isIpv4(str) { 36 | const regexExp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/; 37 | return regexExp.test(str); 38 | } 39 | 40 | isIpv6(str) { 41 | const regexExp = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/gi; 42 | return regexExp.test(str); 43 | } 44 | 45 | wrapUri(addr, port) { 46 | if (this.isIpv6(addr)) { 47 | return "[" + addr + "]" + ":" + port 48 | } else { 49 | return addr + ":" + port 50 | } 51 | } 52 | 53 | unwrapIpv6(addr) { 54 | if (addr.startsWith("[") && addr.endsWith("]")) { 55 | return addr.replace("[", "").replace("]", "") 56 | } 57 | return addr 58 | } 59 | 60 | addSplash(str) { 61 | if (str.startsWith("/")) { 62 | return str 63 | } else { 64 | return "/" + str 65 | } 66 | } 67 | 68 | ifNotNull(any, callback) { 69 | if (any != null) callback(any) 70 | } 71 | 72 | decodeB64Str(b64Str) { 73 | try { 74 | let jsonStr = Base64.decode(b64Str) 75 | return JSON.parse(jsonStr) 76 | } catch (error) { 77 | return {} 78 | } 79 | } 80 | 81 | encodeB64Str(obj) { 82 | return Base64.encode(JSON.stringify(obj), true) 83 | } 84 | 85 | } 86 | 87 | export const util = new utilClass(); 88 | try { 89 | window.util = util 90 | } catch (error) { 91 | 92 | } 93 | 94 | // add utils to prototype 95 | 96 | Object.prototype.global_export = function (name, f) { 97 | Object.prototype[name] = f 98 | } 99 | 100 | String.prototype.contains = function (s) { return this.indexOf(s) >= 0 } 101 | String.prototype.isBlank = function () { return util.isBlank(this) } 102 | String.prototype.isNotBlank = function () { return util.isNotBlank(this) } 103 | String.prototype.isIpv4 = function () { return util.isIpv4(this) } 104 | String.prototype.isIpv6 = function () { return util.isIpv6(this) } 105 | String.prototype.isPureIp = function () { return util.isPureIp(this) } 106 | String.prototype.substringBefore = function (s) { 107 | if (!this.contains(s)) return this 108 | return this.substring(0, this.indexOf(s)) 109 | } 110 | String.prototype.substringAfter = function (s) { 111 | if (!this.contains(s)) return this 112 | return this.substring(this.indexOf(s) + s.length) 113 | } 114 | String.prototype.toInt = function () { return parseInt(this) } 115 | String.prototype.firstLine = function () { return this.split("\n")[0] } 116 | String.prototype.lines = function () { return this.split("\n") } 117 | -------------------------------------------------------------------------------- /js/make.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | # moe.matsur.exe.* -> no js build 4 | [ $1 == "hysteria" ] && exit 5 | [ $1 == "tuic" ] && exit 6 | [ $1 == "naive" ] && exit 7 | [ $1 == "trojan-go" ] && exit 8 | [ $1 == "tuic5" ] && exit 9 | 10 | HTML=../app_$1/html 11 | SRC=./plugin_$1 12 | 13 | webpack --entry "$SRC"/main.js 14 | 15 | rm -rf "$HTML" 16 | mkdir -p "$HTML" 17 | cp plugin.html "$HTML" 18 | cp dist/p.js "$HTML" 19 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "test.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.16.12", 13 | "@babel/preset-env": "^7.16.11", 14 | "babel-loader": "^8.2.3", 15 | "webpack-cli": "^4.9.2" 16 | }, 17 | "dependencies": { 18 | "core-js": "^3.20.3", 19 | "js-base64": "^3.7.2" 20 | }, 21 | "browserslist": [ 22 | "chrome > 30" 23 | ] 24 | } -------------------------------------------------------------------------------- /js/plugin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Webpack App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /js/plugin_brook/brook.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js"; 2 | import { commomClass } from "../common/common.js" 3 | import { TR } from "../common/translate.js" 4 | 5 | class brookClass { 6 | constructor() { 7 | this.sharedStorage = {} 8 | this.defaultSharedStorage = {} 9 | this.common = new commomClass() 10 | } 11 | 12 | _initDefaultSharedStorage() { 13 | // start of default keys 14 | this.defaultSharedStorage.jsVersion = 1 15 | this.defaultSharedStorage.name = "" 16 | this.defaultSharedStorage.serverAddress = "127.0.0.1" 17 | this.defaultSharedStorage.serverPort = "1080" 18 | // end of default keys 19 | this.defaultSharedStorage.serverProtocol = "default" 20 | this.defaultSharedStorage.serverPassword = "" 21 | this.defaultSharedStorage.serverPath = "" 22 | this.defaultSharedStorage.serverAllowInsecure = false 23 | this.defaultSharedStorage.withoutBrookProtocol = false 24 | this.defaultSharedStorage.udpovertcp = false 25 | 26 | 27 | for (var k in this.defaultSharedStorage) { 28 | let v = this.defaultSharedStorage[k] 29 | this.common._setType(k, typeof v) 30 | 31 | if (!this.sharedStorage.hasOwnProperty(k)) { 32 | this.sharedStorage[k] = v 33 | } 34 | } 35 | 36 | } 37 | 38 | _onSharedStorageUpdated() { 39 | // not null 40 | for (var k in this.sharedStorage) { 41 | if (this.sharedStorage[k] == null) { 42 | this.sharedStorage[k] = "" 43 | } 44 | } 45 | this._setShareLink() 46 | } 47 | 48 | _setShareLink() { 49 | var builder = util.newURL("brook") 50 | 51 | var serverString = util.wrapUri(this.sharedStorage.serverAddress, this.sharedStorage.serverPort) 52 | var wsPath = this.sharedStorage.serverPath 53 | 54 | if (this.sharedStorage.serverProtocol.startsWith("ws")) { 55 | if (wsPath.isNotBlank() && wsPath != "/") { 56 | if (!wsPath.startsWith("/")) wsPath = "/" + wsPath 57 | serverString += wsPath 58 | } 59 | } 60 | 61 | switch (this.sharedStorage.serverProtocol) { 62 | case "ws": { 63 | builder.host = "wsserver" 64 | builder.searchParams.set("wsserver", serverString) 65 | break 66 | } 67 | case "wss": { 68 | builder.host = "wssserver" 69 | builder.searchParams.set("wssserver", serverString) 70 | break 71 | } 72 | default: { 73 | builder.host = "server" 74 | builder.searchParams.set("server", serverString) 75 | } 76 | } 77 | 78 | if (this.sharedStorage.serverPassword.isNotBlank()) { 79 | builder.searchParams.set("password", this.sharedStorage.serverPassword) 80 | } 81 | if (this.sharedStorage.name.isNotBlank()) { 82 | builder.searchParams.set("remarks", this.sharedStorage.name) 83 | } 84 | 85 | this.sharedStorage.shareLink = builder.toString() 86 | } 87 | 88 | // UI Interface 89 | 90 | requirePreferenceScreenConfig() { 91 | let sb = [ 92 | { 93 | "title": TR("serverSettings"), 94 | "preferences": [ 95 | { 96 | "type": "EditTextPreference", 97 | "key": "serverAddress", 98 | "icon": "ic_hardware_router", 99 | }, 100 | { 101 | "type": "EditTextPreference", 102 | "key": "serverPort", 103 | "icon": "ic_maps_directions_boat", 104 | "EditTextPreferenceModifiers": "Port", 105 | }, 106 | { 107 | "type": "SimpleMenuPreference", 108 | "key": "serverProtocol", 109 | "icon": "ic_baseline_compare_arrows_24", 110 | "entries": { 111 | "default": "DEFAULT", 112 | "ws": "WS", 113 | "wss": "WSS", 114 | } 115 | }, 116 | { 117 | "type": "EditTextPreference", 118 | "key": "serverPassword", 119 | "icon": "ic_settings_password", 120 | "summaryProvider": "PasswordSummaryProvider", 121 | }, 122 | { 123 | "type": "EditTextPreference", 124 | "key": "serverPath", 125 | "title": TR("serverPath_ws"), 126 | "icon": "ic_baseline_format_align_left_24", 127 | }, 128 | { 129 | "type": "SwitchPreference", 130 | "key": "serverAllowInsecure", 131 | "icon": "ic_baseline_warning_24", 132 | "summary": TR("serverAllowInsecure_summary") 133 | }, 134 | { 135 | "type": "SwitchPreference", 136 | "key": "withoutBrookProtocol", 137 | "icon": "baseline_public_24", 138 | "summary": TR("withoutBrookProtocol_summary") 139 | }, 140 | { 141 | "type": "SwitchPreference", 142 | "key": "udpovertcp", 143 | "icon": "baseline_wrap_text_24", 144 | }, 145 | ] 146 | }, 147 | ] 148 | this.common._applyTranslateToPreferenceScreenConfig(sb, TR) 149 | return JSON.stringify(sb) 150 | } 151 | 152 | // 开启设置界面时调用 153 | setSharedStorage(b64Str) { 154 | this.sharedStorage = util.decodeB64Str(b64Str) 155 | this._initDefaultSharedStorage() 156 | } 157 | 158 | // 开启设置界面时调用 159 | requireSetProfileCache() { 160 | for (var k in this.defaultSharedStorage) { 161 | this.common.setKV(k, this.sharedStorage[k]) 162 | } 163 | } 164 | 165 | // 设置界面创建后调用 166 | onPreferenceCreated() { 167 | let this2 = this 168 | 169 | function listenOnPreferenceChangedNow(key) { 170 | neko.listenOnPreferenceChanged(key) 171 | this2._onPreferenceChanged(key, this2.sharedStorage[key]) 172 | } 173 | 174 | listenOnPreferenceChangedNow("serverProtocol") 175 | } 176 | 177 | // 保存时调用(混合编辑后的值) 178 | sharedStorageFromProfileCache() { 179 | for (var k in this.defaultSharedStorage) { 180 | this.sharedStorage[k] = this.common.getKV(k) 181 | } 182 | this._onSharedStorageUpdated() 183 | return JSON.stringify(this.sharedStorage) 184 | } 185 | 186 | onPreferenceChanged(b64Str) { 187 | let args = util.decodeB64Str(b64Str) 188 | this._onPreferenceChanged(args.key, args.newValue) 189 | } 190 | 191 | _onPreferenceChanged(key, newValue) { 192 | if (key == "serverProtocol") { 193 | if (newValue == "wss") { 194 | neko.setPreferenceVisibility("serverAllowInsecure", true) 195 | } else { 196 | neko.setPreferenceVisibility("serverAllowInsecure", false) 197 | } 198 | if (newValue == "default") { 199 | neko.setPreferenceVisibility("serverPath", false) 200 | neko.setPreferenceVisibility("withoutBrookProtocol", false) 201 | neko.setPreferenceVisibility("udpovertcp", true) 202 | } else { 203 | neko.setPreferenceVisibility("serverPath", true) 204 | neko.setPreferenceVisibility("withoutBrookProtocol", true) 205 | neko.setPreferenceVisibility("udpovertcp", false) 206 | } 207 | } 208 | } 209 | 210 | // Interface 211 | 212 | parseShareLink(b64Str) { 213 | let args = util.decodeB64Str(b64Str) 214 | 215 | this.sharedStorage = {} 216 | this._initDefaultSharedStorage() 217 | 218 | var link = util.tryParseURL(args.shareLink) 219 | if (typeof link == "string") return link 220 | 221 | this.sharedStorage.name = link.searchParams.get("remarks") 222 | 223 | switch (link.host) { 224 | case "server": { 225 | this.sharedStorage.serverProtocol = "default" 226 | 227 | var server = link.searchParams.get("server") 228 | if (server == null) return "Invalid brook server url (Missing server parameter)" 229 | 230 | this.sharedStorage.serverAddress = server.substringBefore(":") 231 | this.sharedStorage.serverPort = server.substringAfter(":").toInt() 232 | var password = link.searchParams.get("password") 233 | if (password == null) ("Invalid brook server url (Missing password parameter)") 234 | 235 | this.sharedStorage.serverPassword = password 236 | break 237 | } 238 | case "wsserver": { 239 | this.sharedStorage.serverProtocol = "ws" 240 | 241 | var wsserver = link.searchParams.get("wsserver") 242 | if (wsserver == null) return "Invalid brook wsserver url (Missing wsserver parameter)" 243 | 244 | wsserver = wsserver.substringAfter("://") 245 | 246 | if (wsserver.contains("/")) { 247 | this.sharedStorage.serverPath = "/" + wsserver.substringAfter("/") 248 | wsserver = wsserver.substringBefore("/") 249 | } 250 | 251 | this.sharedStorage.serverAddress = wsserver.substringBefore(":") 252 | this.sharedStorage.serverPort = wsserver.substringAfter(":").toInt() 253 | 254 | var password = link.searchParams.get("password") 255 | if (password == null) ("Invalid brook wsserver url (Missing password parameter)") 256 | 257 | this.sharedStorage.serverPassword = password 258 | break 259 | } 260 | case "wssserver": { 261 | this.sharedStorage.serverProtocol = "wss" 262 | 263 | var wsserver = link.searchParams.get("wssserver") 264 | if (wsserver == null) return "Invalid brook wssserver url (Missing wssserver parameter)" 265 | 266 | wsserver = wsserver.substringAfter("://") 267 | 268 | if (wsserver.contains("/")) { 269 | this.sharedStorage.serverPath = "/" + wsserver.substringAfter("/") 270 | wsserver = wsserver.substringBefore("/") 271 | } 272 | 273 | this.sharedStorage.serverAddress = wsserver.substringBefore(":") 274 | this.sharedStorage.serverPort = wsserver.substringAfter(":").toInt() 275 | 276 | var password = link.searchParams.get("password") 277 | if (password == null) ("Invalid brook wssserver url (Missing password parameter)") 278 | 279 | this.sharedStorage.serverPassword = password 280 | break 281 | } 282 | } 283 | 284 | this._onSharedStorageUpdated() 285 | return JSON.stringify(this.sharedStorage) 286 | } 287 | 288 | buildAllConfig(b64Str) { 289 | try { 290 | let args = util.decodeB64Str(b64Str) 291 | let cs = util.decodeB64Str(args.sharedStorage) 292 | 293 | console.log(args, cs) 294 | 295 | let v = {} 296 | v.nekoCommands = ["%exe%"] 297 | 298 | switch (cs.serverProtocol) { 299 | case "ws": { 300 | v.nekoCommands.push("wsclient") 301 | v.nekoCommands.push("--wsserver") 302 | break 303 | } 304 | case "wss": { 305 | v.nekoCommands.push("wssclient") 306 | v.nekoCommands.push("--wssserver") 307 | break 308 | } 309 | default: { 310 | v.nekoCommands.push("client") 311 | v.nekoCommands.push("--server") 312 | } 313 | } 314 | 315 | let internalUri = util.wrapUri(args.finalAddress, args.finalPort) 316 | 317 | switch (cs.serverProtocol) { 318 | case "ws": { 319 | internalUri = "ws://" + util.wrapUri(cs.serverAddress, args.finalPort) 320 | break 321 | } 322 | case "wss": { 323 | internalUri = "wss://" + util.wrapUri(cs.serverAddress, args.finalPort) 324 | break 325 | } 326 | } 327 | 328 | if (cs.serverPath.isNotBlank()) { 329 | if (!cs.serverPath.startsWith("/")) { 330 | internalUri += "/" 331 | } 332 | internalUri += cs.serverPath 333 | } 334 | 335 | v.nekoCommands.push(internalUri) 336 | 337 | if (cs.serverProtocol.startsWith("ws")) { 338 | if (cs.withoutBrookProtocol) { 339 | v.nekoCommands.push("--withoutBrookProtocol") 340 | } 341 | if (cs.serverProtocol == "wss" && cs.serverAllowInsecure) { 342 | v.nekoCommands.push("--insecure") 343 | } 344 | // Mapping 345 | v.nekoCommands.push("--address") 346 | v.nekoCommands.push(util.wrapUri(args.finalAddress, args.finalPort)) 347 | } else { 348 | if (cs.udpovertcp) { 349 | v.nekoCommands.push("--udpovertcp") 350 | } 351 | } 352 | 353 | if (cs.serverPassword.isNotBlank()) { 354 | v.nekoCommands.push("--password") 355 | v.nekoCommands.push(cs.serverPassword) 356 | } 357 | 358 | v.nekoCommands.push("--socks5") 359 | v.nekoCommands.push("127.0.0.1:" + args.port) 360 | 361 | return JSON.stringify(v) 362 | } catch (error) { 363 | neko.logError(error.toString()) 364 | } 365 | } 366 | } 367 | 368 | export const brook = new brookClass() 369 | -------------------------------------------------------------------------------- /js/plugin_brook/main.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js" 2 | import { brook } from "./brook.js" 3 | import { LANG } from "../common/translate.js" 4 | 5 | // Init 6 | 7 | export function nekoInit(b64Str) { 8 | let args = util.decodeB64Str(b64Str) 9 | 10 | //TODO 11 | console.log(args) 12 | 13 | LANG = args.lang 14 | 15 | let plgConfig = { 16 | "ok": true, 17 | "reason": "", 18 | "minVersion": 1, 19 | "protocols": [ 20 | { 21 | "protocolId": "brook", 22 | "links": ["brook://"], 23 | "haveStandardLink": true, 24 | "canShare": true, 25 | "canMux": false, 26 | "canMapping": true, 27 | "canTCPing": true, 28 | "canICMPing": true, 29 | "needBypassRootUid": false, 30 | } 31 | ] 32 | } 33 | return JSON.stringify(plgConfig) 34 | } 35 | 36 | export function nekoProtocol(protocolId) { 37 | if (protocolId == "brook") { 38 | return brook 39 | } 40 | } 41 | 42 | // export interface to browser 43 | global_export("nekoInit", nekoInit) 44 | global_export("nekoProtocol", nekoProtocol) 45 | -------------------------------------------------------------------------------- /js/plugin_juicity/juicity.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js"; 2 | import { commomClass } from "../common/common.js"; 3 | import { TR } from "../common/translate.js"; 4 | 5 | class juicityClass { 6 | constructor() { 7 | this.sharedStorage = {}; 8 | this.defaultSharedStorage = {}; 9 | this.common = new commomClass(); 10 | } 11 | 12 | _initDefaultSharedStorage() { 13 | // start of default keys 14 | this.defaultSharedStorage.jsVersion = 1; 15 | this.defaultSharedStorage.name = ""; 16 | this.defaultSharedStorage.serverAddress = "127.0.0.1"; 17 | this.defaultSharedStorage.serverPort = "1080"; 18 | // end of default keys 19 | this.defaultSharedStorage.uuid = "00000000-0000-0000-0000-000000000000"; 20 | this.defaultSharedStorage.password = ""; 21 | this.defaultSharedStorage.sni = ""; 22 | this.defaultSharedStorage.allowInsecure = false; 23 | this.defaultSharedStorage.congestionControl = "bbr"; 24 | 25 | for (var k in this.defaultSharedStorage) { 26 | let v = this.defaultSharedStorage[k]; 27 | this.common._setType(k, typeof v); 28 | 29 | if (!this.sharedStorage.hasOwnProperty(k)) { 30 | this.sharedStorage[k] = v; 31 | } 32 | } 33 | } 34 | 35 | _onSharedStorageUpdated() { 36 | // not null 37 | for (var k in this.sharedStorage) { 38 | if (this.sharedStorage[k] == null) { 39 | this.sharedStorage[k] = ""; 40 | } 41 | } 42 | this._setShareLink(); 43 | } 44 | 45 | // 生成并存储 share link 46 | _setShareLink() { 47 | var builder = util.newURL("juicity") 48 | if (this.sharedStorage.name.isNotBlank()) builder.hash = "#" + encodeURIComponent(this.sharedStorage.name) 49 | builder.host = util.wrapUri(this.sharedStorage.serverAddress, this.sharedStorage.serverPort) 50 | builder.username = this.sharedStorage.uuid 51 | builder.password = this.sharedStorage.password 52 | if (this.sharedStorage.congestionControl.isNotBlank()) { 53 | builder.searchParams.set("congestion_control", this.sharedStorage.congestionControl) 54 | } 55 | if (this.sharedStorage.sni.isNotBlank()) { 56 | builder.searchParams.set("sni", this.sharedStorage.sni) 57 | } 58 | if (this.sharedStorage.allowInsecure) { 59 | builder.searchParams.set("allow_insecure", "1") 60 | } 61 | this.sharedStorage.shareLink = builder.toString() 62 | } 63 | 64 | // UI Interface 65 | 66 | requirePreferenceScreenConfig() { 67 | let sb = [ 68 | { 69 | title: TR("serverSettings"), 70 | preferences: [ 71 | { 72 | type: "EditTextPreference", 73 | key: "serverAddress", 74 | icon: "ic_hardware_router", 75 | }, 76 | { 77 | type: "EditTextPreference", 78 | key: "serverPort", 79 | icon: "ic_maps_directions_boat", 80 | EditTextPreferenceModifiers: "Port", 81 | }, 82 | // 83 | { 84 | "type": "EditTextPreference", 85 | "key": "uuid", 86 | "icon": "ic_baseline_person_24", 87 | }, 88 | { 89 | "type": "EditTextPreference", 90 | "key": "password", 91 | "icon": "ic_settings_password", 92 | "summaryProvider": "PasswordSummaryProvider", 93 | }, 94 | // 95 | { 96 | "type": "EditTextPreference", 97 | "key": "sni", 98 | "icon": "ic_action_copyright" 99 | }, 100 | { 101 | "type": "SwitchPreference", 102 | "key": "allowInsecure", 103 | "icon": "ic_notification_enhanced_encryption", 104 | }, 105 | { 106 | "type": "EditTextPreference", 107 | "key": "congestionControl", 108 | "icon": "ic_baseline_stream_24", 109 | }, 110 | ], 111 | }, 112 | ]; 113 | this.common._applyTranslateToPreferenceScreenConfig(sb, TR); 114 | return JSON.stringify(sb); 115 | } 116 | 117 | // 开启设置界面时调用 118 | setSharedStorage(b64Str) { 119 | this.sharedStorage = util.decodeB64Str(b64Str); 120 | this._initDefaultSharedStorage(); 121 | } 122 | 123 | // 开启设置界面时调用 124 | requireSetProfileCache() { 125 | for (var k in this.defaultSharedStorage) { 126 | this.common.setKV(k, this.sharedStorage[k]); 127 | } 128 | } 129 | // 开启设置界面时调用 130 | 131 | // 设置界面创建后调用 132 | onPreferenceCreated() { 133 | let this2 = this 134 | 135 | function listenOnPreferenceChangedNow(key) { 136 | neko.listenOnPreferenceChanged(key) 137 | this2._onPreferenceChanged(key, this2.sharedStorage[key]) 138 | } 139 | } 140 | 141 | // 保存时调用(混合编辑后的值) 142 | sharedStorageFromProfileCache() { 143 | for (var k in this.defaultSharedStorage) { 144 | this.sharedStorage[k] = this.common.getKV(k); 145 | } 146 | this._onSharedStorageUpdated(); 147 | return JSON.stringify(this.sharedStorage); 148 | } 149 | 150 | // 用户修改 preference 时调用 151 | onPreferenceChanged(b64Str) { 152 | let args = util.decodeB64Str(b64Str) 153 | this._onPreferenceChanged(args.key, args.newValue) 154 | } 155 | 156 | _onPreferenceChanged(key, newValue) { 157 | } 158 | 159 | // Interface 160 | 161 | parseShareLink(b64Str) { 162 | let args = util.decodeB64Str(b64Str) 163 | 164 | this.sharedStorage = {} 165 | this._initDefaultSharedStorage() 166 | 167 | var url = util.tryParseURL(args.shareLink) 168 | if (typeof url == "string") return url // error string 169 | 170 | var serverAddress = util.unwrapIpv6(url.hostname) 171 | this.sharedStorage.serverAddress = serverAddress 172 | this.sharedStorage.serverPort = url.host.replace(serverAddress, "").substringAfter(":") 173 | this.sharedStorage.uuid = url.username 174 | this.sharedStorage.password = url.password 175 | this.sharedStorage.name = decodeURIComponent(url.hash.substringAfter("#")) 176 | 177 | util.ifNotNull(url.searchParams.get("congestion_control"), (it) => { 178 | this.sharedStorage.congestionControl = it 179 | }) 180 | util.ifNotNull(url.searchParams.get("sni"), (it) => { 181 | this.sharedStorage.sni = it 182 | }) 183 | util.ifNotNull(url.searchParams.get("allow_insecure"), (it) => { 184 | if (it == "1" || it == "true") this.sharedStorage.allowInsecure = it 185 | }) 186 | 187 | this._onSharedStorageUpdated() 188 | return JSON.stringify(this.sharedStorage) 189 | } 190 | 191 | buildAllConfig(b64Str) { 192 | try { 193 | let args = util.decodeB64Str(b64Str); 194 | let ss = util.decodeB64Str(args.sharedStorage); 195 | 196 | let configObject = { 197 | "listen": "127.0.0.1:" + args.port, 198 | "server": util.wrapUri(args.finalAddress, args.finalPort), 199 | "uuid": ss.uuid, 200 | "password": ss.password, 201 | "allow_insecure": ss.allowInsecure, 202 | "congestion_control": ss.congestionControl, 203 | "log_level": "info" 204 | }; 205 | if (ss.sni.isNotBlank()) { 206 | configObject["sni"] = ss.sni 207 | } else if (!ss.serverAddress.isPureIp()) { 208 | configObject["sni"] = ss.serverAddress 209 | } 210 | 211 | let v = {}; 212 | v.nekoCommands = ["%exe%", "run", "-c", "config.json"]; 213 | v.nekoRunConfigs = [ 214 | { 215 | name: "config.json", 216 | content: JSON.stringify(configObject), 217 | }, 218 | ]; 219 | return JSON.stringify(v); 220 | } catch (error) { 221 | neko.logError(error.toString()); 222 | } 223 | } 224 | } 225 | 226 | export const juicity = new juicityClass(); 227 | -------------------------------------------------------------------------------- /js/plugin_juicity/main.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js"; 2 | import { LANG, LANG_TR } from "../common/translate.js"; 3 | 4 | import { juicity } from "./juicity.js"; 5 | 6 | // Init 7 | 8 | export function nekoInit(b64Str) { 9 | let args = util.decodeB64Str(b64Str); 10 | 11 | LANG = args.lang; 12 | 13 | let plgConfig = { 14 | ok: true, 15 | reason: "", 16 | minVersion: 2, 17 | protocols: [ 18 | { 19 | protocolId: "Juicity", 20 | links: ["juicity://"], 21 | haveStandardLink: true, 22 | canShare: true, 23 | canMux: false, 24 | canMapping: true, 25 | canTCPing: false, 26 | canICMPing: true, 27 | needBypassRootUid: false, 28 | } 29 | ], 30 | }; 31 | return JSON.stringify(plgConfig); 32 | } 33 | 34 | export function nekoProtocol(protocolId) { 35 | if (protocolId == "Juicity") { 36 | return juicity; 37 | } 38 | } 39 | 40 | export function nekoAbout() { 41 | return "早期测试版本,上游版本 v0.3.0\n" + 42 | "1 目前不兼容链式代理\n" + 43 | "这个插件是实验性的。如果在使用过程中遇到任何问题,请自行解决。" 44 | } 45 | 46 | // export interface to browser 47 | global_export("nekoInit", nekoInit) 48 | global_export("nekoProtocol", nekoProtocol) 49 | global_export("nekoAbout", nekoAbout) 50 | -------------------------------------------------------------------------------- /js/plugin_singbox/main.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js"; 2 | import { LANG, LANG_TR } from "../common/translate.js"; 3 | 4 | import { shadowTls } from "./shadowtls.js"; 5 | import { wireguard } from "./wireguard.js"; 6 | 7 | // Init 8 | 9 | export function nekoInit(b64Str) { 10 | let args = util.decodeB64Str(b64Str); 11 | 12 | LANG = args.lang; 13 | 14 | let plgConfig = { 15 | ok: true, 16 | reason: "", 17 | minVersion: 2, 18 | protocols: [ 19 | { 20 | protocolId: "ShadowTLS", 21 | haveStandardLink: false, 22 | canShare: false, 23 | canMux: false, 24 | canMapping: true, 25 | canTCPing: true, 26 | canICMPing: true, 27 | needBypassRootUid: false, 28 | }, 29 | { 30 | protocolId: "WireGuard", 31 | haveStandardLink: false, 32 | canShare: false, 33 | canMux: false, 34 | canMapping: true, 35 | canTCPing: false, 36 | canICMPing: true, 37 | needBypassRootUid: false, 38 | }, 39 | ], 40 | }; 41 | return JSON.stringify(plgConfig); 42 | } 43 | 44 | export function nekoProtocol(protocolId) { 45 | if (protocolId == "ShadowTLS") { 46 | return shadowTls; 47 | } else if (protocolId == "WireGuard") { 48 | return wireguard; 49 | } 50 | } 51 | 52 | export function nekoAbout() { 53 | switch (LANG_TR()) { 54 | case "zh_CN": 55 | return "作者:杰洛特\n" + 56 | "GitHub: https://github.com/mliyuanbiao\n" + 57 | "这个插件是实验性的。如果在使用过程中遇到任何问题,请自行解决。" 58 | default: 59 | return "Author: 杰洛特\n" + 60 | "GitHub: https://github.com/mliyuanbiao\n" + 61 | "This plugin is experimental. If you have any problems during use, please solve them yourself." 62 | } 63 | } 64 | 65 | // export interface to browser 66 | global_export("nekoInit", nekoInit) 67 | global_export("nekoProtocol", nekoProtocol) 68 | global_export("nekoAbout", nekoAbout) 69 | -------------------------------------------------------------------------------- /js/plugin_singbox/shadowtls.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js"; 2 | import { commomClass } from "../common/common.js"; 3 | import { TR } from "../common/translate.js"; 4 | 5 | class shadowTlsClass { 6 | constructor() { 7 | this.sharedStorage = {}; 8 | this.defaultSharedStorage = {}; 9 | this.common = new commomClass(); 10 | } 11 | 12 | _initDefaultSharedStorage() { 13 | // start of default keys 14 | this.defaultSharedStorage.jsVersion = 1; 15 | this.defaultSharedStorage.name = ""; 16 | this.defaultSharedStorage.serverAddress = "127.0.0.1"; 17 | this.defaultSharedStorage.serverPort = "1080"; 18 | // end of default keys 19 | this.defaultSharedStorage.serverMethod = "2022-blake3-aes-128-gcm"; 20 | this.defaultSharedStorage.serverPassword = ""; 21 | this.defaultSharedStorage.shadowTlsServerName = ""; 22 | this.defaultSharedStorage.shadowTlsServerPassword = ""; 23 | this.defaultSharedStorage.shadowTlsVersion = "2"; 24 | this.defaultSharedStorage.utlsEnabled = "false"; 25 | this.defaultSharedStorage.utlsFingerprint = "chrome"; 26 | // UDP over TCP 27 | this.defaultSharedStorage.serverUoT = false 28 | 29 | for (var k in this.defaultSharedStorage) { 30 | let v = this.defaultSharedStorage[k]; 31 | this.common._setType(k, typeof v); 32 | 33 | if (!this.sharedStorage.hasOwnProperty(k)) { 34 | this.sharedStorage[k] = v; 35 | } 36 | } 37 | } 38 | 39 | _onSharedStorageUpdated() { 40 | // not null 41 | for (var k in this.sharedStorage) { 42 | if (this.sharedStorage[k] == null) { 43 | this.sharedStorage[k] = ""; 44 | } 45 | } 46 | this._setShareLink(); 47 | } 48 | 49 | _setShareLink() { } 50 | 51 | // UI Interface 52 | 53 | requirePreferenceScreenConfig() { 54 | let sb = [ 55 | { 56 | title: TR("serverSettings"), 57 | preferences: [ 58 | { 59 | type: "EditTextPreference", 60 | key: "serverAddress", 61 | icon: "ic_hardware_router", 62 | }, 63 | { 64 | type: "EditTextPreference", 65 | key: "serverPort", 66 | icon: "ic_maps_directions_boat", 67 | EditTextPreferenceModifiers: "Port", 68 | }, 69 | { 70 | type: "EditTextPreference", 71 | key: "shadowTlsServerName", 72 | icon: "ic_action_copyright", 73 | }, 74 | { 75 | type: "EditTextPreference", 76 | key: "shadowTlsServerPassword", 77 | icon: "ic_settings_password", 78 | summaryProvider: "PasswordSummaryProvider", 79 | }, 80 | { 81 | type: "SimpleMenuPreference", 82 | key: "shadowTlsVersion", 83 | icon: "ic_baseline_layers_24", 84 | entries: { 85 | 1: "1", 86 | 2: "2", 87 | 3: "3" 88 | }, 89 | }, 90 | { 91 | type: "SimpleMenuPreference", 92 | key: "serverMethod", 93 | icon: "ic_notification_enhanced_encryption", 94 | entries: { 95 | "2022-blake3-aes-128-gcm": "2022-blake3-aes-128-gcm", 96 | "2022-blake3-aes-256-gcm": "2022-blake3-aes-256-gcm", 97 | "2022-blake3-chacha20-poly1305": "2022-blake3-chacha20-poly1305", 98 | "aes-128-gcm": "aes-128-gcm", 99 | "aes-192-gcm": "aes-192-gcm", 100 | "aes-256-gcm": "aes-256-gcm", 101 | "chacha20-ietf-poly1305": "chacha20-ietf-poly1305", 102 | "xchacha20-ietf-poly1305": "xchacha20-ietf-poly1305", 103 | }, 104 | }, 105 | { 106 | type: "EditTextPreference", 107 | key: "serverPassword", 108 | icon: "ic_settings_password", 109 | summaryProvider: "PasswordSummaryProvider", 110 | }, 111 | { 112 | type: "SimpleMenuPreference", 113 | key: "utlsEnabled", 114 | icon: "ic_baseline_vpn_key_24", 115 | entries: { 116 | "true": "true", 117 | "false": "false", 118 | }, 119 | }, 120 | { 121 | type: "SimpleMenuPreference", 122 | key: "utlsFingerprint", 123 | icon: "ic_baseline_fiber_manual_record_24", 124 | entries: { 125 | "chrome": "chrome", 126 | "firefox": "firefox", 127 | "edge": "edge", 128 | "safari": "safari", 129 | "360": "360", 130 | "qq": "qq", 131 | "ios": "ios", 132 | "android": "android", 133 | "random": "random", 134 | } 135 | }, 136 | { 137 | type: "SwitchPreference", 138 | key: "serverUoT", 139 | icon: "baseline_wrap_text_24", 140 | }, 141 | ], 142 | }, 143 | ]; 144 | this.common._applyTranslateToPreferenceScreenConfig(sb, TR); 145 | return JSON.stringify(sb); 146 | } 147 | 148 | // 开启设置界面时调用 149 | setSharedStorage(b64Str) { 150 | this.sharedStorage = util.decodeB64Str(b64Str); 151 | this._initDefaultSharedStorage(); 152 | } 153 | 154 | // 开启设置界面时调用 155 | requireSetProfileCache() { 156 | for (var k in this.defaultSharedStorage) { 157 | this.common.setKV(k, this.sharedStorage[k]); 158 | } 159 | } 160 | // 开启设置界面时调用 161 | 162 | // 设置界面创建后调用 163 | onPreferenceCreated() { 164 | let this2 = this 165 | 166 | function listenOnPreferenceChangedNow(key) { 167 | neko.listenOnPreferenceChanged(key) 168 | this2._onPreferenceChanged(key, this2.sharedStorage[key]) 169 | } 170 | 171 | listenOnPreferenceChangedNow("utlsEnabled") 172 | listenOnPreferenceChangedNow("shadowTlsVersion") 173 | } 174 | 175 | // 保存时调用(混合编辑后的值) 176 | sharedStorageFromProfileCache() { 177 | for (var k in this.defaultSharedStorage) { 178 | this.sharedStorage[k] = this.common.getKV(k); 179 | } 180 | this._onSharedStorageUpdated(); 181 | return JSON.stringify(this.sharedStorage); 182 | } 183 | 184 | // 用户修改 preference 时调用 185 | onPreferenceChanged(b64Str) { 186 | let args = util.decodeB64Str(b64Str) 187 | this._onPreferenceChanged(args.key, args.newValue) 188 | } 189 | 190 | _onPreferenceChanged(key, newValue) { 191 | if (key == "utlsEnabled") { 192 | neko.setPreferenceVisibility("utlsFingerprint", false) 193 | if (newValue == "true") { 194 | neko.setPreferenceVisibility("utlsFingerprint", true) 195 | } 196 | } else if (key == "shadowTlsVersion") { 197 | neko.setPreferenceVisibility("shadowTlsServerPassword", true) 198 | if (newValue == "1") { 199 | neko.setPreferenceVisibility("shadowTlsServerPassword", false) 200 | } 201 | } 202 | } 203 | 204 | // Interface 205 | 206 | parseShareLink(b64Str) { } 207 | 208 | buildAllConfig(b64Str) { 209 | try { 210 | let args = util.decodeB64Str(b64Str); 211 | let ss = util.decodeB64Str(args.sharedStorage); 212 | 213 | // convert string to boolean 214 | if (ss.utlsEnabled === "true"){ 215 | ss.utlsEnabled = true 216 | } else { 217 | ss.utlsEnabled = false 218 | } 219 | 220 | let t0 = { 221 | log: { 222 | disabled: false, 223 | level: "warn", 224 | timestamp: true, 225 | }, 226 | inbounds: [ 227 | { 228 | type: "socks", 229 | tag: "socks-in", 230 | listen: "127.0.0.1", 231 | listen_port: args.port, 232 | }, 233 | ], 234 | outbounds: [ 235 | { 236 | type: "shadowsocks", 237 | method: ss.serverMethod, 238 | password: ss.serverPassword, 239 | detour: "shadowtls-out", 240 | udp_over_tcp: ss.serverUoT, 241 | multiplex: { 242 | enabled: !ss.serverUoT, 243 | max_connections: 4, 244 | min_streams: 4, 245 | }, 246 | }, 247 | { 248 | type: "shadowtls", 249 | tag: "shadowtls-out", 250 | server: args.finalAddress, 251 | server_port: args.finalPort, 252 | version: parseInt(ss.shadowTlsVersion, 10), 253 | password: ss.shadowTlsServerPassword, 254 | tls: { 255 | enabled: true, 256 | server_name: ss.shadowTlsServerName, 257 | utls: { 258 | enabled: ss.utlsEnabled, 259 | fingerprint: ss.utlsFingerprint 260 | } 261 | }, 262 | }, 263 | ], 264 | }; 265 | 266 | // check shadowTlsVersion if = 1, remove password entity from config 267 | if (ss.shadowTlsVersion == 1) { 268 | delete t0.outbounds[1].password; 269 | } 270 | 271 | let v = {}; 272 | v.nekoCommands = ["%exe%", "run", "--config", "config.json"]; 273 | 274 | v.nekoRunConfigs = [ 275 | { 276 | name: "config.json", 277 | content: JSON.stringify(t0), 278 | }, 279 | ]; 280 | 281 | return JSON.stringify(v); 282 | } catch (error) { 283 | neko.logError(error.toString()); 284 | } 285 | } 286 | } 287 | 288 | export const shadowTls = new shadowTlsClass(); 289 | -------------------------------------------------------------------------------- /js/plugin_singbox/wireguard.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js"; 2 | import { commomClass } from "../common/common.js"; 3 | import { TR } from "../common/translate.js"; 4 | 5 | class wireguardClass { 6 | constructor() { 7 | this.sharedStorage = {}; 8 | this.defaultSharedStorage = {}; 9 | this.common = new commomClass(); 10 | } 11 | 12 | _initDefaultSharedStorage() { 13 | // start of default keys 14 | this.defaultSharedStorage.jsVersion = 1; 15 | this.defaultSharedStorage.name = ""; 16 | this.defaultSharedStorage.serverAddress = "127.0.0.1"; 17 | this.defaultSharedStorage.serverPort = "1080"; 18 | // end of default keys 19 | this.defaultSharedStorage.wireguardLocalAddress = ""; 20 | this.defaultSharedStorage.wireguardPrivateKey = ""; 21 | this.defaultSharedStorage.wireguardCertificates = ""; 22 | this.defaultSharedStorage.wireguardPeerPreSharedKey = ""; 23 | this.defaultSharedStorage.wireguardMTU = "1420"; 24 | this.defaultSharedStorage.wireguardDNS = ""; 25 | 26 | for (var k in this.defaultSharedStorage) { 27 | let v = this.defaultSharedStorage[k]; 28 | this.common._setType(k, typeof v); 29 | 30 | if (!this.sharedStorage.hasOwnProperty(k)) { 31 | this.sharedStorage[k] = v; 32 | } 33 | } 34 | } 35 | 36 | _onSharedStorageUpdated() { 37 | // not null 38 | for (var k in this.sharedStorage) { 39 | if (this.sharedStorage[k] == null) { 40 | this.sharedStorage[k] = ""; 41 | } 42 | } 43 | this._setShareLink(); 44 | } 45 | 46 | _setShareLink() { } 47 | 48 | // UI Interface 49 | 50 | requirePreferenceScreenConfig() { 51 | let sb = [ 52 | { 53 | title: TR("serverSettings"), 54 | preferences: [ 55 | { 56 | type: "EditTextPreference", 57 | key: "wireguardLocalAddress", 58 | icon: "ic_baseline_domain_24", 59 | }, 60 | { 61 | type: "EditTextPreference", 62 | key: "wireguardPrivateKey", 63 | icon: "ic_baseline_vpn_key_24", 64 | }, 65 | { 66 | type: "EditTextPreference", 67 | key: "serverAddress", 68 | icon: "ic_hardware_router", 69 | }, 70 | { 71 | type: "EditTextPreference", 72 | key: "serverPort", 73 | icon: "ic_maps_directions_boat", 74 | EditTextPreferenceModifiers: "Port", 75 | }, 76 | { 77 | type: "EditTextPreference", 78 | key: "wireguardCertificates", 79 | icon: "ic_action_copyright", 80 | }, 81 | { 82 | type: "EditTextPreference", 83 | key: "wireguardPeerPreSharedKey", 84 | icon: "ic_settings_password", 85 | summaryProvider: "PasswordSummaryProvider", 86 | }, 87 | { 88 | type: "EditTextPreference", 89 | key: "wireguardMTU", 90 | icon: "baseline_public_24", 91 | }, 92 | { 93 | type: "EditTextPreference", 94 | key: "wireguardDNS", 95 | icon: "ic_action_dns", 96 | }, 97 | ], 98 | }, 99 | ]; 100 | this.common._applyTranslateToPreferenceScreenConfig(sb, TR); 101 | return JSON.stringify(sb); 102 | } 103 | 104 | // 开启设置界面时调用 105 | setSharedStorage(b64Str) { 106 | this.sharedStorage = util.decodeB64Str(b64Str); 107 | this._initDefaultSharedStorage(); 108 | } 109 | 110 | // 开启设置界面时调用 111 | requireSetProfileCache() { 112 | for (var k in this.defaultSharedStorage) { 113 | this.common.setKV(k, this.sharedStorage[k]); 114 | } 115 | } 116 | 117 | // 设置界面创建后调用 118 | onPreferenceCreated() { } 119 | 120 | // 保存时调用(混合编辑后的值) 121 | sharedStorageFromProfileCache() { 122 | for (var k in this.defaultSharedStorage) { 123 | this.sharedStorage[k] = this.common.getKV(k); 124 | } 125 | this._onSharedStorageUpdated(); 126 | return JSON.stringify(this.sharedStorage); 127 | } 128 | 129 | // Interface 130 | 131 | parseShareLink(b64Str) { } 132 | 133 | buildAllConfig(b64Str) { 134 | try { 135 | let args = util.decodeB64Str(b64Str); 136 | let wg = util.decodeB64Str(args.sharedStorage); 137 | 138 | let t0 = { 139 | log: { 140 | disabled: false, 141 | level: "warn", 142 | timestamp: true, 143 | }, 144 | inbounds: [ 145 | { 146 | type: "socks", 147 | tag: "socks-in", 148 | listen: "127.0.0.1", 149 | listen_port: args.port, 150 | }, 151 | ], 152 | outbounds: [ 153 | { 154 | type: "wireguard", 155 | tag: "wireguard-out", 156 | server: args.finalAddress, 157 | server_port: args.finalPort, 158 | system_interface: false, 159 | interface_name: "wg0", 160 | local_address: wg.wireguardLocalAddress 161 | .split("\n") 162 | .map((item) => item.trim()) 163 | .filter((item) => item.length > 0), 164 | private_key: wg.wireguardPrivateKey, 165 | peer_public_key: wg.wireguardCertificates, 166 | pre_shared_key: wg.wireguardPeerPreSharedKey, 167 | mtu: parseInt(wg.wireguardMTU), 168 | }, 169 | ], 170 | }; 171 | if (!wg.wireguardDNS.isBlank()) { 172 | t0.dns = { 173 | servers: [ 174 | { 175 | address: wg.wireguardDNS, 176 | }, 177 | ] 178 | } 179 | } 180 | 181 | let v = {}; 182 | v.nekoCommands = ["%exe%", "run", "--config", "config.json"]; 183 | 184 | v.nekoRunConfigs = [ 185 | { 186 | name: "config.json", 187 | content: JSON.stringify(t0), 188 | }, 189 | ]; 190 | 191 | return JSON.stringify(v); 192 | } catch (error) { 193 | neko.logError(error.toString()); 194 | } 195 | } 196 | } 197 | 198 | export const wireguard = new wireguardClass(); 199 | -------------------------------------------------------------------------------- /js/plugin_xray/main.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js" 2 | import { LANG, LANG_TR } from "../common/translate.js" 3 | 4 | import { vless } from "./vless.js" 5 | import { ss2022 } from "./ss2022.js" 6 | 7 | // Init 8 | 9 | export function nekoInit(b64Str) { 10 | let args = util.decodeB64Str(b64Str) 11 | 12 | LANG = args.lang 13 | 14 | let plgConfig = { 15 | "ok": true, 16 | "reason": "", 17 | "minVersion": 2, 18 | "protocols": [ 19 | { 20 | "protocolId": "VLESS", 21 | "links": ["vless://"], 22 | "haveStandardLink": true, 23 | "canShare": true, 24 | "canMux": true, 25 | "canMapping": true, 26 | "canTCPing": true, 27 | "canICMPing": true, 28 | "needBypassRootUid": false, 29 | }, 30 | { 31 | "protocolId": "Shadowsocks-2022", 32 | "haveStandardLink": false, 33 | "canShare": false, 34 | "canMux": false, 35 | "canMapping": true, 36 | "canTCPing": true, 37 | "canICMPing": true, 38 | "needBypassRootUid": false, 39 | } 40 | ] 41 | } 42 | return JSON.stringify(plgConfig) 43 | } 44 | 45 | export function nekoProtocol(protocolId) { 46 | if (protocolId == "VLESS") { 47 | return vless 48 | } 49 | if (protocolId == "Shadowsocks-2022") { 50 | return ss2022 51 | } 52 | } 53 | 54 | export function nekoAbout() { 55 | switch (LANG_TR()) { 56 | case "zh_CN": 57 | return "使用 Xray 内核提供 VLESS 等协议的实验性支持,效果未知。\n如果在使用过程中遇到任何问题,请自行解决。" 58 | default: 59 | return "Experimental support for protocols such as VLESS using the Xray core, effects unknown. \nIf you have any problems during use, please solve them yourself." 60 | } 61 | } 62 | 63 | // export interface to browser 64 | global_export("nekoInit", nekoInit) 65 | global_export("nekoProtocol", nekoProtocol) 66 | global_export("nekoAbout", nekoAbout) 67 | -------------------------------------------------------------------------------- /js/plugin_xray/ss2022.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js" 2 | import { commomClass } from "../common/common.js" 3 | import { TR } from "../common/translate.js" 4 | 5 | class ss2022Class { 6 | constructor() { 7 | this.sharedStorage = {} 8 | this.defaultSharedStorage = {} 9 | this.common = new commomClass() 10 | } 11 | 12 | _initDefaultSharedStorage() { 13 | // start of default keys 14 | this.defaultSharedStorage.jsVersion = 1 15 | this.defaultSharedStorage.name = "" 16 | this.defaultSharedStorage.serverAddress = "127.0.0.1" 17 | this.defaultSharedStorage.serverPort = "1080" 18 | // end of default keys 19 | this.defaultSharedStorage.serverMethod = "2022-blake3-aes-128-gcm" 20 | this.defaultSharedStorage.serverPassword = "" 21 | // UDP over TCP 22 | this.defaultSharedStorage.serverUoT = false 23 | // reduce IV header entropy 24 | this.defaultSharedStorage.reducedIvHeadEntropy = false 25 | 26 | for (var k in this.defaultSharedStorage) { 27 | let v = this.defaultSharedStorage[k] 28 | this.common._setType(k, typeof v) 29 | 30 | if (!this.sharedStorage.hasOwnProperty(k)) { 31 | this.sharedStorage[k] = v 32 | } 33 | } 34 | 35 | } 36 | 37 | _onSharedStorageUpdated() { 38 | // not null 39 | for (var k in this.sharedStorage) { 40 | if (this.sharedStorage[k] == null) { 41 | this.sharedStorage[k] = "" 42 | } 43 | } 44 | this._setShareLink() 45 | } 46 | 47 | _setShareLink() { } 48 | 49 | // UI Interface 50 | 51 | requirePreferenceScreenConfig() { 52 | let sb = [ 53 | { 54 | "title": TR("serverSettings"), 55 | "preferences": [ 56 | { 57 | "type": "EditTextPreference", 58 | "key": "serverAddress", 59 | "icon": "ic_hardware_router", 60 | }, 61 | { 62 | "type": "EditTextPreference", 63 | "key": "serverPort", 64 | "icon": "ic_maps_directions_boat", 65 | "EditTextPreferenceModifiers": "Port", 66 | }, 67 | { 68 | "type": "SimpleMenuPreference", 69 | "key": "serverMethod", 70 | "icon": "ic_notification_enhanced_encryption", 71 | "entries": { 72 | "2022-blake3-aes-128-gcm": "2022-blake3-aes-128-gcm", 73 | "2022-blake3-aes-256-gcm": "2022-blake3-aes-256-gcm", 74 | "2022-blake3-chacha20-poly1305": "2022-blake3-chacha20-poly1305", 75 | } 76 | }, 77 | { 78 | "type": "EditTextPreference", 79 | "key": "serverPassword", 80 | "icon": "ic_baseline_person_24", 81 | "summaryProvider": "PasswordSummaryProvider", 82 | }, 83 | { 84 | "type": "SwitchPreference", 85 | "key": "serverUoT", 86 | "icon": "baseline_wrap_text_24", 87 | }, 88 | { 89 | "type": "SwitchPreference", 90 | "icon": "ic_baseline_grid_3x3_24", 91 | "key": "reducedIvHeadEntropy", 92 | "summary": TR("reducedIvHeadEntropy_summary") 93 | }, 94 | ] 95 | } 96 | ] 97 | this.common._applyTranslateToPreferenceScreenConfig(sb, TR) 98 | return JSON.stringify(sb) 99 | } 100 | 101 | // 开启设置界面时调用 102 | setSharedStorage(b64Str) { 103 | this.sharedStorage = util.decodeB64Str(b64Str) 104 | this._initDefaultSharedStorage() 105 | } 106 | 107 | // 开启设置界面时调用 108 | requireSetProfileCache() { 109 | for (var k in this.defaultSharedStorage) { 110 | this.common.setKV(k, this.sharedStorage[k]) 111 | } 112 | } 113 | 114 | // 设置界面创建后调用 115 | onPreferenceCreated() { } 116 | 117 | // 保存时调用(混合编辑后的值) 118 | sharedStorageFromProfileCache() { 119 | for (var k in this.defaultSharedStorage) { 120 | this.sharedStorage[k] = this.common.getKV(k) 121 | } 122 | this._onSharedStorageUpdated() 123 | return JSON.stringify(this.sharedStorage) 124 | } 125 | 126 | // Interface 127 | 128 | parseShareLink(b64Str) { } 129 | 130 | buildAllConfig(b64Str) { 131 | try { 132 | let args = util.decodeB64Str(b64Str) 133 | let ss = util.decodeB64Str(args.sharedStorage) 134 | 135 | let t0 = { 136 | "log": { 137 | "loglevel": "debug" 138 | }, 139 | "inbounds": [ 140 | { 141 | "port": args.port, 142 | "listen": "127.0.0.1", 143 | "protocol": "socks", 144 | "settings": { 145 | "udp": true 146 | } 147 | } 148 | ], 149 | "outbounds": [ 150 | { 151 | "protocol": "shadowsocks", 152 | "settings": { 153 | 154 | "servers": [ 155 | { 156 | "address": args.finalAddress, 157 | "port": args.finalPort, 158 | "method": ss.serverMethod, 159 | "password": ss.serverPassword, 160 | "uot": ss.serverUoT, 161 | "reducedIvHeadEntropy": ss.reducedIvHeadEntropy, 162 | } 163 | ] 164 | } 165 | } 166 | ] 167 | } 168 | 169 | let v = {} 170 | v.nekoCommands = ["%exe%", "-config", "config.json"] 171 | 172 | v.nekoRunConfigs = [ 173 | { 174 | "name": "config.json", 175 | "content": JSON.stringify(t0) 176 | } 177 | ] 178 | 179 | return JSON.stringify(v) 180 | } catch (error) { 181 | neko.logError(error.toString()) 182 | } 183 | } 184 | } 185 | 186 | export const ss2022 = new ss2022Class() 187 | -------------------------------------------------------------------------------- /js/plugin_xray/vless.js: -------------------------------------------------------------------------------- 1 | import { util } from "../common/util.js" 2 | import { commomClass } from "../common/common.js" 3 | import { TR } from "../common/translate.js" 4 | import { Base64 } from 'js-base64' 5 | 6 | class vlessClass { 7 | constructor() { 8 | this.sharedStorage = {} 9 | this.defaultSharedStorage = {} 10 | this.common = new commomClass() 11 | } 12 | 13 | _initDefaultSharedStorage() { 14 | // start of default keys 15 | this.defaultSharedStorage.jsVersion = 2 16 | this.defaultSharedStorage.insecureHint = "" 17 | this.defaultSharedStorage.name = "" 18 | this.defaultSharedStorage.serverAddress = "127.0.0.1" 19 | this.defaultSharedStorage.serverPort = "1080" 20 | // end of default keys 21 | this.defaultSharedStorage.serverUserId = "" 22 | this.defaultSharedStorage.serverEncryption = "none" 23 | this.defaultSharedStorage.serverNetwork = "tcp" 24 | this.defaultSharedStorage.serverHeader = "none" 25 | this.defaultSharedStorage.serverQuicSecurity = "chacha20-poly1305" 26 | this.defaultSharedStorage.serverHost = "" 27 | this.defaultSharedStorage.serverPath = "" 28 | this.defaultSharedStorage.serverSecurity = "none" 29 | this.defaultSharedStorage.grpcMultiMode = false 30 | // tls 31 | this.defaultSharedStorage.utlsFingerprint = "" 32 | this.defaultSharedStorage.serverSNI = "" 33 | this.defaultSharedStorage.serverALPN = "" 34 | this.defaultSharedStorage.serverCertificates = "" 35 | this.defaultSharedStorage.serverFlowVision = "" 36 | this.defaultSharedStorage.serverAllowInsecure = false 37 | // reality 38 | this.defaultSharedStorage.publicKey = "" 39 | this.defaultSharedStorage.shortId = "" 40 | this.defaultSharedStorage.spiderX = "" 41 | 42 | for (var k in this.defaultSharedStorage) { 43 | let v = this.defaultSharedStorage[k] 44 | this.common._setType(k, typeof v) 45 | 46 | if (!this.sharedStorage.hasOwnProperty(k)) { 47 | this.sharedStorage[k] = v 48 | } 49 | } 50 | 51 | } 52 | 53 | _onSharedStorageUpdated() { 54 | // not null 55 | for (var k in this.sharedStorage) { 56 | if (this.sharedStorage[k] == null) { 57 | this.sharedStorage[k] = "" 58 | } 59 | } 60 | this._setShareLink() 61 | this.sharedStorage.insecureHint = this._insecureHint() 62 | } 63 | 64 | _insecureHint() { 65 | if (this.sharedStorage.serverSecurity == "none") { 66 | return TR("insecure_cleartext") 67 | } 68 | return "" 69 | } 70 | 71 | _setShareLink() { 72 | var builder = util.newURL("vless") 73 | 74 | if (this.sharedStorage.name.isNotBlank()) builder.hash = "#" + encodeURIComponent(this.sharedStorage.name) 75 | builder.username = this.sharedStorage.serverUserId 76 | builder.host = util.wrapUri(this.sharedStorage.serverAddress, this.sharedStorage.serverPort) 77 | builder.searchParams.set("encryption", this.sharedStorage.serverEncryption) 78 | 79 | var type = this.sharedStorage.serverNetwork 80 | if (type == "h2") type = "http" 81 | builder.searchParams.set("type", type) 82 | 83 | if (type == "tcp") { 84 | if (this.sharedStorage.serverHeader == "http") { 85 | builder.searchParams.set("headerType", this.sharedStorage.serverHeader) 86 | if (this.sharedStorage.serverHost.isNotBlank()) { 87 | builder.searchParams.set("host", this.sharedStorage.serverHost) 88 | } 89 | if (this.sharedStorage.serverPath.isNotBlank()) { 90 | builder.searchParams.set("path", this.sharedStorage.serverPath) 91 | } 92 | } 93 | } else if (type == "kcp") { 94 | if (this.sharedStorage.serverHeader.isNotBlank() && this.sharedStorage.serverHeader != "none") { 95 | builder.searchParams.set("headerType", this.sharedStorage.serverHeader) 96 | } 97 | if (this.sharedStorage.serverPath.isNotBlank()) { 98 | builder.searchParams.set("seed", this.sharedStorage.serverPath) 99 | } 100 | } else if (type == "ws" || type == "http") { 101 | if (this.sharedStorage.serverHost.isNotBlank()) { 102 | builder.searchParams.set("host", this.sharedStorage.serverHost) 103 | } 104 | if (this.sharedStorage.serverPath.isNotBlank()) { 105 | builder.searchParams.set("path", this.sharedStorage.serverPath) 106 | } 107 | } else if (type == "quic") { 108 | if (this.sharedStorage.serverHeader.isNotBlank() && this.sharedStorage.serverHeader != "none") { 109 | builder.searchParams.set("headerType", this.sharedStorage.serverHeader) 110 | } 111 | if (this.sharedStorage.serverQuicSecurity.isNotBlank() && this.sharedStorage.serverQuicSecurity != "none") { 112 | builder.searchParams.set("key", this.sharedStorage.serverPath) 113 | builder.searchParams.set("serverQuicSecurity", this.sharedStorage.serverQuicSecurity) 114 | } 115 | } else if (type == "grpc") { 116 | if (this.sharedStorage.serverPath.isNotBlank()) { 117 | builder.searchParams.set("serviceName", this.sharedStorage.serverPath) 118 | } 119 | if (this.sharedStorage.grpcMultiMode == true) { 120 | builder.searchParams.set("mode", "multi") 121 | } 122 | } 123 | 124 | if (this.sharedStorage.serverSecurity.isNotBlank() && this.sharedStorage.serverSecurity != "none") { 125 | builder.searchParams.set("security", this.sharedStorage.serverSecurity) 126 | switch (this.sharedStorage.serverSecurity) { 127 | case "tls": { 128 | if (this.sharedStorage.serverSNI.isNotBlank()) { 129 | builder.searchParams.set("sni", this.sharedStorage.serverSNI) 130 | } 131 | if (this.sharedStorage.serverALPN.isNotBlank()) { 132 | builder.searchParams.set("alpn", this.sharedStorage.serverALPN) 133 | } 134 | if (this.sharedStorage.serverCertificates.isNotBlank()) { 135 | builder.searchParams.set("cert", this.sharedStorage.serverCertificates) 136 | } 137 | if (this.sharedStorage.serverFlowVision.isNotBlank()) { 138 | builder.searchParams.set("flow", this.sharedStorage.serverFlowVision) 139 | } 140 | if (this.sharedStorage.utlsFingerprint.isNotBlank()) { 141 | builder.searchParams.set("fp", this.sharedStorage.utlsFingerprint) 142 | } 143 | break 144 | } 145 | case "reality": { 146 | if (this.sharedStorage.serverSNI.isNotBlank()) { 147 | builder.searchParams.set("sni", this.sharedStorage.serverSNI) 148 | } 149 | if (this.sharedStorage.serverFlowVision.isNotBlank()) { 150 | builder.searchParams.set("flow", this.sharedStorage.serverFlowVision) 151 | } 152 | if (this.sharedStorage.publicKey.isNotBlank()) { 153 | builder.searchParams.set("pbk", this.sharedStorage.publicKey) 154 | } 155 | if (this.sharedStorage.shortId.isNotBlank()) { 156 | builder.searchParams.set("sid", this.sharedStorage.shortId) 157 | } 158 | if (this.sharedStorage.spiderX.isNotBlank()) { 159 | builder.searchParams.set("spx", this.sharedStorage.spiderX) 160 | } 161 | if (this.sharedStorage.utlsFingerprint.isNotBlank()) { 162 | builder.searchParams.set("fp", this.sharedStorage.utlsFingerprint) 163 | } 164 | break 165 | } 166 | } 167 | } 168 | 169 | this.sharedStorage.shareLink = builder.toString() 170 | } 171 | 172 | // UI Interface 173 | 174 | requirePreferenceScreenConfig() { 175 | let sb = [ 176 | { 177 | "title": TR("serverSettings"), 178 | "preferences": [ 179 | { 180 | "type": "EditTextPreference", 181 | "key": "serverAddress", 182 | "icon": "ic_hardware_router", 183 | }, 184 | { 185 | "type": "EditTextPreference", 186 | "key": "serverPort", 187 | "icon": "ic_maps_directions_boat", 188 | "EditTextPreferenceModifiers": "Port", 189 | }, 190 | { 191 | "type": "EditTextPreference", 192 | "key": "serverUserId", 193 | "icon": "ic_baseline_person_24", 194 | "summaryProvider": "PasswordSummaryProvider", 195 | }, 196 | { 197 | "type": "SimpleMenuPreference", 198 | "key": "serverEncryption", 199 | "icon": "ic_notification_enhanced_encryption", 200 | "entries": { 201 | "none": "none", 202 | } 203 | }, 204 | { 205 | "type": "SimpleMenuPreference", 206 | "key": "serverNetwork", 207 | "icon": "ic_baseline_compare_arrows_24", 208 | "entries": { 209 | "tcp": "tcp", 210 | "kcp": "kcp", 211 | "ws": "ws", 212 | "h2": "h2", 213 | "quic": "quic", 214 | "grpc": "grpc", 215 | } 216 | }, 217 | { 218 | "type": "SimpleMenuPreference", 219 | "key": "serverHeader", 220 | "icon": "ic_baseline_texture_24", 221 | }, 222 | { 223 | "type": "SimpleMenuPreference", 224 | "key": "serverQuicSecurity", 225 | "icon": "ic_baseline_security_24", 226 | "entries": { 227 | "chacha20-poly1305": "chacha20-poly1305", 228 | "aes-128-gcm": "aes-128-gcm", 229 | "none": "none", 230 | } 231 | }, 232 | { 233 | "type": "EditTextPreference", 234 | "key": "serverHost", 235 | "icon": "ic_baseline_airplanemode_active_24" 236 | }, 237 | { 238 | "type": "EditTextPreference", 239 | "key": "serverPath", 240 | "icon": "ic_baseline_format_align_left_24" 241 | }, 242 | { 243 | "type": "SimpleMenuPreference", 244 | "key": "serverSecurity", 245 | "icon": "ic_baseline_layers_24", 246 | "entries": { 247 | "none": "none", 248 | "tls": "tls", 249 | "reality": "reality", 250 | } 251 | }, 252 | { 253 | "type": "SwitchPreference", 254 | "key": "grpcMultiMode", 255 | "summary": TR("grpcMultiMode_summary") 256 | }, 257 | ] 258 | }, 259 | { 260 | "key": "serverSecurityCategory", 261 | "preferences": [ 262 | { 263 | "type": "SimpleMenuPreference", 264 | "key": "utlsFingerprint", 265 | "entries": { 266 | "": "", 267 | "chrome": "chrome", 268 | "firefox": "firefox", 269 | "safari": "safari", 270 | "ios": "ios", 271 | "android": "android", 272 | "edge": "edge", 273 | "360": "360", 274 | "qq": "qq", 275 | "random": "random", 276 | "randomized": "randomized", 277 | } 278 | }, 279 | { 280 | "type": "EditTextPreference", 281 | "key": "serverSNI", 282 | "icon": "ic_action_copyright" 283 | }, 284 | { 285 | "type": "EditTextPreference", 286 | "key": "serverALPN", 287 | "icon": "ic_baseline_legend_toggle_24" 288 | }, 289 | { 290 | "type": "EditTextPreference", 291 | "key": "serverCertificates", 292 | "icon": "ic_baseline_vpn_key_24" 293 | }, 294 | { 295 | "type": "SimpleMenuPreference", 296 | "key": "serverFlowVision", 297 | "icon": "ic_baseline_stream_24", 298 | "entries": { 299 | "": "", 300 | "xtls-rprx-vision": "xtls-rprx-vision", 301 | "xtls-rprx-vision-udp443": "xtls-rprx-vision-udp443" 302 | } 303 | }, 304 | { 305 | "type": "SwitchPreference", 306 | "key": "serverAllowInsecure", 307 | "icon": "ic_notification_enhanced_encryption", 308 | "summary": TR("serverAllowInsecure_summary") 309 | }, 310 | { 311 | "type": "EditTextPreference", 312 | "key": "publicKey", 313 | "icon": "ic_baseline_vpn_key_24" 314 | }, 315 | { 316 | "type": "EditTextPreference", 317 | "key": "shortId", 318 | "icon": "ic_baseline_texture_24" 319 | }, 320 | { 321 | "type": "EditTextPreference", 322 | "key": "spiderX", 323 | "icon": "ic_baseline_format_align_left_24" 324 | }, 325 | ] 326 | } 327 | ] 328 | this.common._applyTranslateToPreferenceScreenConfig(sb, TR) 329 | return JSON.stringify(sb) 330 | } 331 | 332 | // 开启设置界面时调用 333 | setSharedStorage(b64Str) { 334 | this.sharedStorage = util.decodeB64Str(b64Str) 335 | this._initDefaultSharedStorage() 336 | } 337 | 338 | // 开启设置界面时调用 339 | requireSetProfileCache() { 340 | for (var k in this.defaultSharedStorage) { 341 | this.common.setKV(k, this.sharedStorage[k]) 342 | } 343 | } 344 | 345 | // 设置界面创建后调用 346 | onPreferenceCreated() { 347 | let this2 = this 348 | 349 | function listenOnPreferenceChangedNow(key) { 350 | neko.listenOnPreferenceChanged(key) 351 | this2._onPreferenceChanged(key, this2.sharedStorage[key]) 352 | } 353 | 354 | listenOnPreferenceChangedNow("serverNetwork") 355 | listenOnPreferenceChangedNow("serverSecurity") 356 | } 357 | 358 | // 保存时调用(混合编辑后的值) 359 | sharedStorageFromProfileCache() { 360 | for (var k in this.defaultSharedStorage) { 361 | this.sharedStorage[k] = this.common.getKV(k) 362 | } 363 | this._onSharedStorageUpdated() 364 | return JSON.stringify(this.sharedStorage) 365 | } 366 | 367 | // 用户修改 preference 时调用 368 | onPreferenceChanged(b64Str) { 369 | let args = util.decodeB64Str(b64Str) 370 | this._onPreferenceChanged(args.key, args.newValue) 371 | } 372 | 373 | _onPreferenceChanged(key, newValue) { 374 | if (key == "serverNetwork") { 375 | neko.setPreferenceVisibility("serverQuicSecurity", false) 376 | // neko.setPreferenceVisibility("serverWsCategory", false) 377 | neko.setPreferenceVisibility("grpcMultiMode", false) 378 | neko.setMenu("serverHeader", JSON.stringify({ 379 | "none": "none", 380 | "srtp": "srtp", 381 | "utp": "utp", 382 | "wechat-video": "wechat-video", 383 | "dtls": "dtls", 384 | "wireguard": "wireguard", 385 | })) 386 | 387 | if (newValue == "tcp") { 388 | neko.setPreferenceVisibility("serverHeader", true) 389 | neko.setPreferenceVisibility("serverHost", false) 390 | neko.setPreferenceVisibility("serverPath", false) 391 | neko.setMenu("serverHeader", JSON.stringify({ 392 | "none": "none", 393 | "http": "http", 394 | })) 395 | } else if (newValue == "mkcp") { 396 | neko.setPreferenceVisibility("serverHeader", true) 397 | neko.setPreferenceVisibility("serverHost", false) 398 | neko.setPreferenceVisibility("serverPath", true) 399 | neko.setPreferenceTitle("serverPath", TR("serverPath_mkcp")) 400 | } else if (newValue == "ws") { 401 | neko.setPreferenceVisibility("serverHeader", false) 402 | neko.setPreferenceVisibility("serverHost", true) 403 | neko.setPreferenceVisibility("serverPath", true) 404 | neko.setPreferenceTitle("serverHost", TR("serverHost_ws")) 405 | neko.setPreferenceTitle("serverPath", TR("serverPath_ws")) 406 | } else if (newValue == "h2") { 407 | neko.setPreferenceVisibility("serverHeader", false) 408 | neko.setPreferenceVisibility("serverHost", true) 409 | neko.setPreferenceVisibility("serverPath", true) 410 | neko.setPreferenceVisibility("serverWsCategory", true) // TODO 暂时不支持浏览器转发 411 | neko.setPreferenceTitle("serverHost", TR("serverHost_http")) 412 | neko.setPreferenceTitle("serverPath", TR("serverPath_http")) 413 | } else if (newValue == "quic") { 414 | neko.setPreferenceVisibility("serverHeader", true) 415 | neko.setPreferenceVisibility("serverQuicSecurity", true) 416 | neko.setPreferenceVisibility("serverHost", false) 417 | neko.setPreferenceVisibility("serverPath", true) 418 | neko.setPreferenceTitle("serverPath", TR("serverPath_quic")) 419 | } else if (newValue == "grpc") { 420 | neko.setPreferenceVisibility("serverHeader", false) 421 | neko.setPreferenceVisibility("serverHost", false) 422 | neko.setPreferenceVisibility("serverPath", true) 423 | neko.setPreferenceVisibility("grpcMultiMode", true) 424 | neko.setPreferenceTitle("serverPath", TR("serverPath_grpc")) 425 | } 426 | } else if (key == "serverSecurity") { 427 | if (newValue == "none") { 428 | neko.setPreferenceVisibility("serverSecurityCategory", false) 429 | } else { 430 | neko.setPreferenceVisibility("serverSecurityCategory", true) 431 | let isREALITY = (newValue == "reality") 432 | neko.setPreferenceVisibility("publicKey", isREALITY) 433 | neko.setPreferenceVisibility("shortId", isREALITY) 434 | neko.setPreferenceVisibility("spiderX", isREALITY) 435 | neko.setPreferenceVisibility("serverALPN", !isREALITY) 436 | neko.setPreferenceVisibility("serverCertificates", !isREALITY) 437 | neko.setPreferenceVisibility("serverAllowInsecure", !isREALITY) 438 | } 439 | } 440 | } 441 | 442 | // Interface 443 | 444 | parseShareLink(b64Str) { 445 | let args = util.decodeB64Str(b64Str) 446 | 447 | this.sharedStorage = {} 448 | this._initDefaultSharedStorage() 449 | 450 | var url = util.tryParseURL(args.shareLink) 451 | if (typeof url == "string") return url 452 | 453 | if (Base64.isValid(url.hostname)) return "不支持的格式" 454 | 455 | var serverAddress = util.unwrapIpv6(url.hostname) 456 | this.sharedStorage.serverAddress = serverAddress 457 | this.sharedStorage.serverPort = url.host.replace(serverAddress, "").substringAfter(":") 458 | this.sharedStorage.serverUserId = url.username 459 | this.sharedStorage.name = decodeURIComponent(url.hash.substringAfter("#")) 460 | 461 | util.ifNotNull(url.searchParams.get("path"), (it) => { 462 | this.sharedStorage.serverPath = it 463 | }) 464 | 465 | var protocol = url.searchParams.get("type") 466 | if (protocol == null) protocol = "tcp" 467 | if (protocol == "http") protocol = "h2" 468 | this.sharedStorage.serverNetwork = protocol 469 | 470 | switch (url.searchParams.get("security")) { 471 | case "tls": { 472 | this.sharedStorage.serverSecurity = "tls" 473 | util.ifNotNull(url.searchParams.get("sni"), (it) => { 474 | this.sharedStorage.serverSNI = it 475 | }) 476 | util.ifNotNull(url.searchParams.get("alpn"), (it) => { 477 | this.sharedStorage.serverALPN = it 478 | }) 479 | util.ifNotNull(url.searchParams.get("cert"), (it) => { 480 | this.sharedStorage.serverCertificates = it 481 | }) 482 | util.ifNotNull(url.searchParams.get("flow"), (it) => { 483 | this.sharedStorage.serverFlowVision = it 484 | }) 485 | util.ifNotNull(url.searchParams.get("fp"), (it) => { 486 | this.sharedStorage.utlsFingerprint = it 487 | }) 488 | break 489 | } 490 | case "reality": { 491 | this.sharedStorage.serverSecurity = "reality" 492 | util.ifNotNull(url.searchParams.get("sni"), (it) => { 493 | this.sharedStorage.serverSNI = it 494 | }) 495 | util.ifNotNull(url.searchParams.get("flow"), (it) => { 496 | this.sharedStorage.serverFlowVision = it 497 | }) 498 | util.ifNotNull(url.searchParams.get("pbk"), (it) => { 499 | this.sharedStorage.publicKey = it 500 | }) 501 | util.ifNotNull(url.searchParams.get("sid"), (it) => { 502 | this.sharedStorage.shortId = it 503 | }) 504 | util.ifNotNull(url.searchParams.get("spx"), (it) => { 505 | this.sharedStorage.spiderX = it 506 | }) 507 | util.ifNotNull(url.searchParams.get("fp"), (it) => { 508 | this.sharedStorage.utlsFingerprint = it 509 | }) 510 | break 511 | } 512 | } 513 | 514 | switch (protocol) { 515 | case "tcp": { 516 | util.ifNotNull(url.searchParams.get("headerType"), (headerType) => { 517 | if (headerType == "http") { 518 | this.sharedStorage.serverHeader = headerType 519 | util.ifNotNull(url.searchParams.get("host"), (it) => { 520 | this.sharedStorage.serverHost = it 521 | }) 522 | } 523 | }) 524 | break 525 | } 526 | case "kcp": { 527 | util.ifNotNull(url.searchParams.get("headerType"), (it) => { 528 | this.sharedStorage.serverHeader = it 529 | }) 530 | util.ifNotNull(url.searchParams.get("seed"), (it) => { 531 | this.sharedStorage.serverPath = it 532 | }) 533 | break 534 | } 535 | case "h2": { 536 | util.ifNotNull(url.searchParams.get("host"), (it) => { 537 | this.sharedStorage.serverHost = it 538 | }) 539 | break 540 | } 541 | case "ws": { 542 | util.ifNotNull(url.searchParams.get("host"), (it) => { 543 | this.sharedStorage.serverHost = it 544 | }) 545 | break 546 | } 547 | case "quic": { 548 | util.ifNotNull(url.searchParams.get("headerType"), (it) => { 549 | this.sharedStorage.serverHeader = it 550 | }) 551 | util.ifNotNull(url.searchParams.get("quicSecurity"), (quicSecurity) => { 552 | this.sharedStorage.serverQuicSecurity = quicSecurity 553 | util.ifNotNull(url.searchParams.get("key"), (it) => { 554 | this.sharedStorage.serverPath = it 555 | }) 556 | }) 557 | break 558 | } 559 | case "grpc": { 560 | util.ifNotNull(url.searchParams.get("serviceName"), (it) => { 561 | this.sharedStorage.serverPath = it 562 | }) 563 | if (url.searchParams.get("serviceName") == "multi") this.sharedStorage.grpcMultiMode = true 564 | break 565 | } 566 | } 567 | 568 | this._onSharedStorageUpdated() 569 | return JSON.stringify(this.sharedStorage) 570 | } 571 | 572 | buildAllConfig(b64Str) { 573 | try { 574 | let args = util.decodeB64Str(b64Str) 575 | let ss = util.decodeB64Str(args.sharedStorage) 576 | 577 | let canMux = false 578 | if (ss.serverNetwork == "tcp" || ss.serverNetwork == "ws" || ss.serverNetwork == "h2") { 579 | canMux = ss.serverFlowVision.isBlank() 580 | } 581 | 582 | let t0 = { 583 | "log": { 584 | "loglevel": "debug" 585 | }, 586 | "inbounds": [ 587 | { 588 | "port": args.port, 589 | "listen": "127.0.0.1", 590 | "protocol": "socks", 591 | "settings": { 592 | "udp": true 593 | } 594 | } 595 | ], 596 | "outbounds": [ 597 | { 598 | "mux": { 599 | "enabled": args.muxEnabled && canMux, 600 | "concurrency": args.muxConcurrency 601 | }, 602 | "protocol": "vless", 603 | "settings": { 604 | "vnext": [ 605 | { 606 | "address": args.finalAddress, // 换成你的域名或服务器 IP(发起请求时无需解析域名了) 607 | "port": args.finalPort, 608 | "users": [ 609 | { 610 | "id": ss.serverUserId, // 填写你的 UUID 611 | "encryption": ss.serverEncryption, 612 | "level": 0, 613 | } 614 | ] 615 | } 616 | ] 617 | }, 618 | "streamSettings": { 619 | "network": ss.serverNetwork, 620 | "security": ss.serverSecurity, 621 | } 622 | } 623 | ] 624 | } 625 | 626 | // fill http host & SNI 627 | if (ss.serverHost.isBlank()) { 628 | ss.serverHost = ss.serverAddress 629 | } 630 | if (ss.serverSNI.isBlank()) { 631 | ss.serverSNI = ss.serverAddress 632 | } 633 | 634 | switch (ss.serverNetwork) { 635 | case "tcp": { 636 | let t1 = { 637 | "header": { 638 | "type": ss.serverHeader 639 | }, 640 | } 641 | t0.outbounds[0].streamSettings["tcpSettings"] = t1 642 | break 643 | } 644 | case "ws": { 645 | let t1 = { 646 | "path": util.addSplash(ss.serverPath), 647 | } 648 | t1["headers"] = { "Host": ss.serverHost.firstLine() } 649 | t0.outbounds[0].streamSettings["wsSettings"] = t1 650 | break 651 | } 652 | case "kcp": { 653 | let t1 = { 654 | "header": { 655 | "type": ss.serverHeader 656 | }, 657 | "seed": ss.serverPath, 658 | } 659 | t0.outbounds[0].streamSettings["kcpSettings"] = t1 660 | break 661 | } 662 | case "h2": { 663 | let t1 = { 664 | "host": ss.serverHost.lines(), 665 | "path": util.addSplash(ss.serverPath), 666 | } 667 | t0.outbounds[0].streamSettings["httpSettings"] = t1 668 | break 669 | } 670 | case "quic": { 671 | let t1 = { 672 | "security": ss.serverQuicSecurity, 673 | "key": ss.serverPath, 674 | "header": { 675 | "type": ss.serverHeader 676 | }, 677 | } 678 | t0.outbounds[0].streamSettings["quicSettings"] = t1 679 | break 680 | } 681 | case "grpc": { 682 | let t1 = { 683 | "serviceName": ss.serverPath, 684 | "multiMode": ss.grpcMultiMode, 685 | } 686 | t0.outbounds[0].streamSettings["grpcSettings"] = t1 687 | break 688 | } 689 | } 690 | 691 | switch (ss.serverSecurity) { 692 | case "tls": { 693 | let t2 = { 694 | "serverName": ss.serverSNI, 695 | "allowInsecure": ss.serverAllowInsecure, 696 | } 697 | if (ss.utlsFingerprint.isNotBlank()) t2["fingerprint"] = ss.utlsFingerprint 698 | if (ss.serverALPN.isNotBlank()) t2["alpn"] = ss.serverALPN.lines() 699 | if (ss.serverCertificates.isNotBlank()) t2["certificates"] = { "certificate": ss.serverCertificates.lines() } 700 | t0.outbounds[0].streamSettings["tlsSettings"] = t2 701 | t0.outbounds[0].settings.vnext[0].users[0]["flow"] = ss.serverFlowVision 702 | break 703 | } 704 | case "reality": { 705 | let t2 = { 706 | "serverName": ss.serverSNI, 707 | } 708 | if (ss.utlsFingerprint.isNotBlank()) t2["fingerprint"] = ss.utlsFingerprint 709 | if (ss.publicKey.isNotBlank()) t2["publicKey"] = ss.publicKey 710 | if (ss.shortId.isNotBlank()) t2["shortId"] = ss.shortId 711 | if (ss.spiderX.isNotBlank()) t2["spiderX"] = ss.spiderX 712 | t0.outbounds[0].streamSettings["realitySettings"] = t2 713 | t0.outbounds[0].settings.vnext[0].users[0]["flow"] = ss.serverFlowVision 714 | break 715 | } 716 | } 717 | 718 | let v = {} 719 | v.nekoCommands = ["%exe%", "-config", "config.json"] 720 | 721 | v.nekoRunConfigs = [ 722 | { 723 | "name": "config.json", 724 | "content": JSON.stringify(t0) 725 | } 726 | ] 727 | 728 | return JSON.stringify(v) 729 | } catch (error) { 730 | neko.logError(error.toString()) 731 | } 732 | } 733 | } 734 | 735 | export const vless = new vlessClass() 736 | -------------------------------------------------------------------------------- /js/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | target: ['browserslist'], 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), 9 | filename: 'p.js', 10 | publicPath: './' 11 | }, 12 | optimization: { 13 | minimize: true 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.js$/, exclude: /node_modules/, 19 | use: [ 20 | { 21 | loader: 'babel-loader', 22 | options: { 23 | presets: [ 24 | '@babel/preset-env' 25 | ], 26 | } 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | build() { 4 | [ $dl ] && bash download.sh "$1" 5 | cd js 6 | bash make.sh "$1" 7 | cd .. 8 | rm -f app_"$1"/build/outputs/apk/release/* 9 | ./gradlew :app_"$1":assembleRelease 10 | } 11 | 12 | build $1 13 | -------------------------------------------------------------------------------- /release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskedeken/plugins/592119092fecb6f7e970bded792fd8cdce397f24/release.keystore -------------------------------------------------------------------------------- /requirement.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | echo "$LOCAL_PROPERTIES" | base64 -d > local.properties 4 | 5 | sudo npm install -g webpack-cli@4.9.2 6 | cd js 7 | npm install 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | rootProject.name = "Matsuri Plugins" 9 | 10 | include ':common' 11 | 12 | include ':app_xray' 13 | include ':app_brook' 14 | include ':app_singbox' 15 | 16 | include ':app_hysteria' 17 | include ':app_tuic' 18 | 19 | include ':app_naive' 20 | include ':app_trojan-go' 21 | include ':app_tuic5' 22 | include ':app_juicity' 23 | --------------------------------------------------------------------------------