mirror of
https://github.com/funkemunky/AntiVPN.git
synced 2026-05-31 17:31:55 +00:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d46d25db1 | |||
| 89b20df703 | |||
| aa0dc4001a | |||
| 2e079079d7 | |||
|
8185c9aad0
|
|||
|
5f07b2393a
|
|||
|
775d29114a
|
|||
| ee07deb68f | |||
| 1acdfe8c4b | |||
| 57109e4c97 | |||
|
a483a90851
|
|||
|
c6d282e3cc
|
|||
| ce6e040d3d | |||
|
45d2a7eaa0
|
|||
|
9e68524bd7
|
|||
| 756b3b04c7 | |||
|
8bbc964f0d
|
|||
| 1ebaa64d6d | |||
| 7983172423 | |||
| 3f46db4ad9 | |||
|
0ea8be6ea3
|
|||
| 3ead4a0093 | |||
| 8b5bc65159 | |||
|
11ef7e8d50
|
|||
|
ea4fe063b2
|
|||
|
38dcfcb1fe
|
|||
|
0894ef8e3d
|
|||
| 3892cf43b3 | |||
|
95d8747bd5
|
|||
| ee7a059b01 | |||
| 51340754e6 | |||
| 0e2468d3fb | |||
| faa8bdbb19 | |||
| 0f14e68c36 | |||
| 772e924303 | |||
| a195106708 | |||
|
de6dc8aa7b
|
|||
| 6246bce70d | |||
| 9fac54fe0b | |||
| ba9ab6fb31 | |||
| 39961a3683 | |||
|
5792b81cb1
|
|||
|
e044d27e27
|
|||
|
17cbed8bda
|
|||
|
50c357f508
|
|||
|
dddd860c15
|
|||
|
e09217877c
|
|||
|
7ffba38992
|
|||
|
8a4b86c9ef
|
|||
|
ac57a540c2
|
|||
|
18d9bcea39
|
|||
|
9f7f4b40f0
|
|||
| 7913676323 | |||
| 9ea3141ae7 | |||
|
cd7008ee09
|
|||
|
c743404069
|
|||
| 58395bb705 | |||
| bd8bd943bd | |||
|
c95c9bb8f3
|
|||
| c7734b2294 | |||
| 8faa59f161 | |||
|
c09269cc20
|
|||
| 011d18ad46 | |||
| f4d1c7a62c | |||
| 6dc0f13c2c | |||
| 606144d404 | |||
|
fe2f9271d8
|
|||
|
43aefc1aed
|
|||
|
ae605a32a5
|
|||
| 1c3e653dda | |||
| 1930a86a0d | |||
| edd255e29d | |||
| c191fbccfa | |||
| 9697150465 | |||
| a5ea502f17 | |||
| bb3a31e100 | |||
| 83f06b29c8 | |||
| 247b329280 | |||
| 069142a06b |
@@ -2,59 +2,31 @@ name: create-release.yml
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-and-release:
|
||||||
name: Build and Test
|
name: Build and Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
|
||||||
steps:
|
steps:
|
||||||
- name: Cache local Maven repository
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.m2/repository
|
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-maven-
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
- name: Set up Maven
|
- name: Set up Gradle
|
||||||
uses: stCarolas/setup-maven@v5
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
maven-version: 3.9.6
|
gradle-version: '9.4.1'
|
||||||
- name: Compile
|
- name: Build
|
||||||
run: mvn -B package --file pom.xml
|
run: gradle build --no-daemon
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Upload AntiVPN
|
- name: Get Version Number from Gradle
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: AntiVPN-Universal
|
|
||||||
path: Universal/target/AntiVPN-*.jar
|
|
||||||
- name: Upload Sponge plugin
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: AntiVPN-Sponge
|
|
||||||
path: Sponge/target/Sponge-*.jar
|
|
||||||
release:
|
|
||||||
name: Create Release
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- name: Download AntiVPN
|
|
||||||
uses: actions/download-artifact@v5
|
|
||||||
with:
|
|
||||||
name: AntiVPN-Universal
|
|
||||||
- name: Download Sponge plugin
|
|
||||||
uses: actions/download-artifact@v5
|
|
||||||
with:
|
|
||||||
name: AntiVPN-Sponge
|
|
||||||
- name: Get Version Number from Pom
|
|
||||||
id: get_version
|
id: get_version
|
||||||
run: echo "VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
|
run: |
|
||||||
|
VERSION=$(gradle properties -q | awk -F': ' '/^version:/ {print $2; exit}')
|
||||||
|
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||||
- name: Extract latest CHANGELOG entry
|
- name: Extract latest CHANGELOG entry
|
||||||
id: changelog
|
id: changelog
|
||||||
run: |
|
run: |
|
||||||
@@ -62,32 +34,23 @@ jobs:
|
|||||||
CHANGELOG_ESCAPED=$(echo "$CHANGELOG_CONTENT" | sed ':a;N;$!ba;s/\n/%0A/g')
|
CHANGELOG_ESCAPED=$(echo "$CHANGELOG_CONTENT" | sed ':a;N;$!ba;s/\n/%0A/g')
|
||||||
echo "Extracted latest release notes from CHANGELOG.md:"
|
echo "Extracted latest release notes from CHANGELOG.md:"
|
||||||
echo -e "$CHANGELOG_CONTENT"
|
echo -e "$CHANGELOG_CONTENT"
|
||||||
echo "::set-output name=content::$CHANGELOG_ESCAPED"
|
echo "content=$CHANGELOG_ESCAPED" >> "$GITHUB_OUTPUT"
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
id: create_release
|
id: create_release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ github.run_number }}
|
tag_name: v${{ env.VERSION }}
|
||||||
release_name: Release v${{ github.run_number }}
|
release_name: Release v${{ env.VERSION }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
body: ${{ steps.changelog.outputs.content }}
|
body: ${{ steps.changelog.outputs.content }}
|
||||||
- uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: ./AntiVPN-*.jar
|
|
||||||
asset_name: AntiVPN-Universal-v${{ env.VERSION }}.jar
|
|
||||||
asset_content_type: application/java-archive
|
|
||||||
- uses: actions/upload-release-asset@v1
|
- uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ./Sponge-*.jar
|
asset_path: ./build/libs/AntiVPN-${{ env.VERSION }}-universal.jar
|
||||||
asset_name: AntiVPN-Sponge-v${{ env.VERSION }}.jar
|
asset_name: AntiVPN-v${{ env.VERSION }}.jar
|
||||||
asset_content_type: application/java-archive
|
asset_content_type: application/java-archive
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'zulu'
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: '9.4.1'
|
||||||
|
- name: Build
|
||||||
|
run: gradle build -x test --no-daemon
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Upload AntiVPN
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AntiVPN-Universal
|
||||||
|
path: build/libs/AntiVPN-*-universal.jar
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build and Test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Cache local Maven repository
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.m2/repository
|
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-maven-
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- name: Set up JDK 21
|
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
|
||||||
java-version: '21'
|
|
||||||
distribution: 'zulu'
|
|
||||||
- name: Set up Maven
|
|
||||||
uses: stCarolas/setup-maven@v5
|
|
||||||
with:
|
|
||||||
maven-version: 3.9.6
|
|
||||||
- name: Compile
|
|
||||||
run: mvn -B package --file pom.xml
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Upload AntiVPN
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: AntiVPN-Universal
|
|
||||||
path: Universal/target/AntiVPN-*.jar
|
|
||||||
- name: Upload Sponge plugin
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: AntiVPN-Sponge
|
|
||||||
path: Sponge/target/Sponge-*.jar
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'zulu'
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: '9.4.1'
|
||||||
|
- name: Build
|
||||||
|
run: gradle build -x test --no-daemon
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Upload AntiVPN
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AntiVPN-Universal
|
||||||
|
path: build/libs/AntiVPN-*-universal.jar
|
||||||
|
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'zulu'
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: '9.4.1'
|
||||||
|
- name: Build and Test
|
||||||
|
run: gradle build --no-daemon
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -188,6 +188,7 @@ fabric.properties
|
|||||||
|
|
||||||
# Package Files #
|
# Package Files #
|
||||||
*.jar
|
*.jar
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
*.war
|
*.war
|
||||||
*.nar
|
*.nar
|
||||||
*.ear
|
*.ear
|
||||||
@@ -293,3 +294,5 @@ $RECYCLE.BIN/
|
|||||||
*.lnk
|
*.lnk
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans
|
# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans
|
||||||
|
/.gradle/
|
||||||
|
.grade/**
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.gradleup.shadow'
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluationDependsOn(':Bukkit:Plugin')
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'org.spigotmc:spigot-api:1.20.2-R0.1-SNAPSHOT'
|
||||||
|
compileOnly project(':Bukkit:Plugin')
|
||||||
|
implementation project(':Common:loader-utils')
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveClassifier.set('')
|
||||||
|
|
||||||
|
// Include the shaded plugin jar as a single resource file
|
||||||
|
from(project(':Bukkit:Plugin').tasks.shadowJar) {
|
||||||
|
rename { 'antivpn-bukkit.jarinjar' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the plugin is built before packaging the loader
|
||||||
|
tasks.named('shadowJar') {
|
||||||
|
dependsOn(':Bukkit:Plugin:shadowJar')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.build.dependsOn shadowJar
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.bukkit.loader;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.loader.JarInJarClassLoader;
|
||||||
|
import dev.brighten.antivpn.loader.LoaderBootstrap;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public class BukkitLoaderPlugin extends JavaPlugin {
|
||||||
|
|
||||||
|
private static final String JAR_NAME = "antivpn-bukkit.jarinjar";
|
||||||
|
private static final String SOURCE_NAME = "antivpn-source.jarinjar";
|
||||||
|
private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bukkit.BukkitPlugin";
|
||||||
|
|
||||||
|
private final LoaderBootstrap plugin;
|
||||||
|
|
||||||
|
public BukkitLoaderPlugin() {
|
||||||
|
JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME);
|
||||||
|
this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, JavaPlugin.class, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad() {
|
||||||
|
this.plugin.onLoad(getDataFolder());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
this.plugin.onEnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
this.plugin.onDisable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
name: KauriVPN
|
||||||
|
main: dev.brighten.antivpn.bukkit.loader.BukkitLoaderPlugin
|
||||||
|
version: ${project.version}
|
||||||
|
author: funkemunky
|
||||||
|
api-version: 1.13
|
||||||
|
folia-supported: true
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.gradleup.shadow'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'org.spigotmc:spigot-api:1.20.2-R0.1-SNAPSHOT'
|
||||||
|
implementation project(':Common:Source')
|
||||||
|
implementation project(':Common:loader-utils')
|
||||||
|
implementation 'org.bstats:bstats-bukkit:2.2.1'
|
||||||
|
testImplementation 'com.github.seeseemelk:MockBukkit-v1.20:3.84.0'
|
||||||
|
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||||
|
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||||
|
testImplementation 'org.mockito:mockito-subclass:5.11.0'
|
||||||
|
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveClassifier.set('')
|
||||||
|
relocate 'org.bstats', 'dev.brighten.antivpn.bukkit.org.bstats'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
systemProperty 'mockito.mockmaker', 'subclass'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.build.dependsOn shadowJar
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bukkit;
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
|
import dev.brighten.antivpn.api.OfflinePlayer;
|
||||||
|
import dev.brighten.antivpn.api.VPNExecutor;
|
||||||
|
import dev.brighten.antivpn.utils.StringUtil;
|
||||||
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class BukkitListener extends VPNExecutor implements Listener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerListeners() {
|
||||||
|
Bukkit.getPluginManager()
|
||||||
|
.registerEvents(this, BukkitPlugin.pluginInstance.getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, String log, Object... objects) {
|
||||||
|
Bukkit.getLogger().log(level, String.format(log, objects));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(String log, Object... objects) {
|
||||||
|
log(Level.INFO, String.format(log, objects));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logException(String message, Throwable ex) {
|
||||||
|
Bukkit.getLogger().log(Level.SEVERE, message, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runCommand(String command) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
public void run() {
|
||||||
|
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(),
|
||||||
|
ChatColor.translateAlternateColorCodes('&', command));
|
||||||
|
}
|
||||||
|
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disablePlugin() {
|
||||||
|
HandlerList.unregisterAll(this);
|
||||||
|
Bukkit.getPluginManager().disablePlugin(BukkitPlugin.pluginInstance.getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH)
|
||||||
|
public void onLogin(final PlayerLoginEvent event) {
|
||||||
|
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
|
||||||
|
.orElse(new OfflinePlayer(
|
||||||
|
event.getPlayer().getUniqueId(),
|
||||||
|
event.getPlayer().getName(),
|
||||||
|
event.getAddress()
|
||||||
|
));
|
||||||
|
|
||||||
|
player.checkPlayer(result -> {
|
||||||
|
if(!result.resultType().isShouldBlock()) return;
|
||||||
|
|
||||||
|
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setResult(PlayerLoginEvent.Result.KICK_BANNED);
|
||||||
|
event.setKickMessage(switch (result.resultType()) {
|
||||||
|
case DENIED_COUNTRY -> StringUtil.varReplace(
|
||||||
|
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
|
||||||
|
player,
|
||||||
|
result.response()
|
||||||
|
);
|
||||||
|
case DENIED_PROXY ->
|
||||||
|
StringUtil.varReplace(
|
||||||
|
AntiVPN.getInstance().getVpnConfig().getKickMessage(),
|
||||||
|
player,
|
||||||
|
result.response()
|
||||||
|
);
|
||||||
|
default -> "You were kicked by KauriVPN for an unknown reason!";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onJoin(final PlayerJoinEvent event) {
|
||||||
|
AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
|
||||||
|
.ifPresent(APIPlayer::checkAlertsState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onQuit(PlayerQuitEvent event) {
|
||||||
|
AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
|
public class BukkitPlayer extends APIPlayer {
|
||||||
|
|
||||||
|
private final Player player;
|
||||||
|
public BukkitPlayer(Player player) {
|
||||||
|
super(player.getUniqueId(), player.getName(), player.getAddress() != null ? player.getAddress().getAddress() : null);
|
||||||
|
|
||||||
|
this.player = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String message) {
|
||||||
|
player.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void kickPlayer(String reason) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
public void run() {
|
||||||
|
player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason));
|
||||||
|
}
|
||||||
|
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(String permission) {
|
||||||
|
return player.hasPermission(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bukkit;
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
import dev.brighten.antivpn.api.APIPlayer;
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
+42
-11
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bukkit;
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
@@ -7,6 +23,7 @@ import dev.brighten.antivpn.database.VPNDatabase;
|
|||||||
import dev.brighten.antivpn.database.local.H2VPN;
|
import dev.brighten.antivpn.database.local.H2VPN;
|
||||||
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
||||||
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
||||||
|
import dev.brighten.antivpn.loader.LoaderBootstrap;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.bstats.bukkit.Metrics;
|
import org.bstats.bukkit.Metrics;
|
||||||
import org.bstats.charts.SimplePie;
|
import org.bstats.charts.SimplePie;
|
||||||
@@ -17,20 +34,34 @@ import org.bukkit.plugin.SimplePluginManager;
|
|||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class BukkitPlugin extends JavaPlugin {
|
public class BukkitPlugin implements LoaderBootstrap {
|
||||||
|
|
||||||
public static BukkitPlugin pluginInstance;
|
public static BukkitPlugin pluginInstance;
|
||||||
private SimpleCommandMap commandMap;
|
private SimpleCommandMap commandMap;
|
||||||
|
@Getter
|
||||||
|
private File dataFolder;
|
||||||
private final List<org.bukkit.command.Command> registeredCommands = new ArrayList<>();
|
private final List<org.bukkit.command.Command> registeredCommands = new ArrayList<>();
|
||||||
|
@Getter
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
|
||||||
|
public BukkitPlugin(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private PlayerCommandRunner playerCommandRunner;
|
private PlayerCommandRunner playerCommandRunner;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(File dataFolder) {
|
||||||
|
this.dataFolder = dataFolder;
|
||||||
|
}
|
||||||
|
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
pluginInstance = this;
|
pluginInstance = this;
|
||||||
|
|
||||||
@@ -43,18 +74,18 @@ public class BukkitPlugin extends JavaPlugin {
|
|||||||
// Loading our bStats metrics to be pushed to https://bstats.org
|
// Loading our bStats metrics to be pushed to https://bstats.org
|
||||||
if(AntiVPN.getInstance().getVpnConfig().metrics()) {
|
if(AntiVPN.getInstance().getVpnConfig().metrics()) {
|
||||||
Bukkit.getLogger().info("Starting bStats metrics...");
|
Bukkit.getLogger().info("Starting bStats metrics...");
|
||||||
Metrics metrics = new Metrics(this, 12615);
|
Metrics metrics = new Metrics(plugin, 12615);
|
||||||
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
|
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0;
|
AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0;
|
||||||
}
|
}
|
||||||
}.runTaskTimerAsynchronously(this, 12000, 12000);
|
}.runTaskTimerAsynchronously(plugin, 12000, 12000);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getLogger().info("Setting up and registering commands...");
|
Bukkit.getLogger().info("Setting up and registering commands...");
|
||||||
// We need access to the commandMap to register our commands without using the "proper" method
|
// We need access to the commandMap to register our commands without using the "proper" method
|
||||||
if (pluginInstance.getServer().getPluginManager() instanceof SimplePluginManager manager) {
|
if (Bukkit.getServer().getPluginManager() instanceof SimplePluginManager manager) {
|
||||||
try {
|
try {
|
||||||
Field field = SimplePluginManager.class.getDeclaredField("commandMap");
|
Field field = SimplePluginManager.class.getDeclaredField("commandMap");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
@@ -73,7 +104,7 @@ public class BukkitPlugin extends JavaPlugin {
|
|||||||
registeredCommands.add(newCommand);
|
registeredCommands.add(newCommand);
|
||||||
|
|
||||||
// This tells Bukkit to register our command for use.
|
// This tells Bukkit to register our command for use.
|
||||||
commandMap.register(pluginInstance.getName(), newCommand);
|
commandMap.register(plugin.getName(), newCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Finish system before implementing on startup
|
//TODO Finish system before implementing on startup
|
||||||
@@ -83,7 +114,7 @@ public class BukkitPlugin extends JavaPlugin {
|
|||||||
.get());
|
.get());
|
||||||
AntiVPN.getInstance().getMessageHandler().reloadStrings();*/
|
AntiVPN.getInstance().getMessageHandler().reloadStrings();*/
|
||||||
|
|
||||||
reloadConfig();
|
plugin.reloadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -109,19 +140,19 @@ public class BukkitPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getLogger().info("Unregistering listeners...");
|
Bukkit.getLogger().info("Unregistering listeners...");
|
||||||
HandlerList.unregisterAll(this);
|
HandlerList.unregisterAll(plugin);
|
||||||
|
|
||||||
Bukkit.getLogger().info("Cancelling any running tasks...");
|
Bukkit.getLogger().info("Cancelling any running tasks...");
|
||||||
Bukkit.getScheduler().cancelTasks(this);
|
Bukkit.getScheduler().cancelTasks(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDatabaseType() {
|
private String getDatabaseType() {
|
||||||
VPNDatabase database = AntiVPN.getInstance().getDatabase();
|
VPNDatabase database = AntiVPN.getInstance().getDatabase();
|
||||||
|
|
||||||
if(database instanceof H2VPN) {
|
if(database instanceof MySqlVPN) {
|
||||||
return "H2";
|
|
||||||
} else if(database instanceof MySqlVPN) {
|
|
||||||
return "MySQL";
|
return "MySQL";
|
||||||
|
} else if(database instanceof H2VPN) {
|
||||||
|
return "H2";
|
||||||
} else if(database instanceof MongoVPN) {
|
} else if(database instanceof MongoVPN) {
|
||||||
return "MongoDB";
|
return "MongoDB";
|
||||||
} else {
|
} else {
|
||||||
+17
-1
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bukkit;
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
import dev.brighten.antivpn.utils.MiscUtils;
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
@@ -35,7 +51,7 @@ public class PlayerCommandRunner {
|
|||||||
public void run() {
|
public void run() {
|
||||||
action.getAction().run();
|
action.getAction().run();
|
||||||
}
|
}
|
||||||
}.runTask(BukkitPlugin.pluginInstance);
|
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
|
||||||
|
|
||||||
playerActions.poll();
|
playerActions.poll();
|
||||||
}
|
}
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bukkit.command;
|
package dev.brighten.antivpn.bukkit.command;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
|
import be.seeseemelk.mockbukkit.MockBukkit;
|
||||||
|
import be.seeseemelk.mockbukkit.ServerMock;
|
||||||
|
import be.seeseemelk.mockbukkit.entity.PlayerMock;
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.api.PlayerExecutor;
|
||||||
|
import dev.brighten.antivpn.api.VPNConfig;
|
||||||
|
import dev.brighten.antivpn.api.VPNExecutor;
|
||||||
|
import dev.brighten.antivpn.message.MessageHandler;
|
||||||
|
import dev.brighten.antivpn.message.VpnString;
|
||||||
|
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||||
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.plugin.PluginDescriptionFile;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
public class BukkitListenerTest {
|
||||||
|
|
||||||
|
private ServerMock server;
|
||||||
|
private BukkitListener listener;
|
||||||
|
private VPNExecutor vpnExecutor;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
server = MockBukkit.mock(new RecordingServerMock());
|
||||||
|
JavaPlugin plugin = MockBukkit.loadWith(
|
||||||
|
TestPlugin.class,
|
||||||
|
new PluginDescriptionFile("AntiVPNTest", "1.0.0", TestPlugin.class.getName())
|
||||||
|
);
|
||||||
|
BukkitPlugin.pluginInstance = new BukkitPlugin(plugin);
|
||||||
|
|
||||||
|
AntiVPN antiVPN = mock(AntiVPN.class);
|
||||||
|
VPNConfig config = mock(VPNConfig.class);
|
||||||
|
PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
|
||||||
|
vpnExecutor = mock(VPNExecutor.class);
|
||||||
|
MessageHandler messageHandler = mock(MessageHandler.class);
|
||||||
|
|
||||||
|
when(antiVPN.getVpnConfig()).thenReturn(config);
|
||||||
|
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
|
||||||
|
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
|
||||||
|
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
|
||||||
|
|
||||||
|
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
|
||||||
|
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
|
||||||
|
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
|
||||||
|
when(config.isKickPlayers()).thenReturn(true);
|
||||||
|
when(config.getKickMessage()).thenReturn("Blocked!");
|
||||||
|
|
||||||
|
VpnString mockVpnString = mock(VpnString.class);
|
||||||
|
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
|
||||||
|
when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
|
||||||
|
|
||||||
|
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
|
||||||
|
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
|
||||||
|
.method("N/A").countryName("N/A").city("N/A").build()
|
||||||
|
));
|
||||||
|
|
||||||
|
// Use reflection to set the private static INSTANCE field
|
||||||
|
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||||
|
instanceField.setAccessible(true);
|
||||||
|
instanceField.set(null, antiVPN);
|
||||||
|
|
||||||
|
listener = new BukkitListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
// Reset the singleton
|
||||||
|
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||||
|
instanceField.setAccessible(true);
|
||||||
|
instanceField.set(null, null);
|
||||||
|
BukkitPlugin.pluginInstance = null;
|
||||||
|
|
||||||
|
MockBukkit.unmock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginEventAllowed() throws Exception {
|
||||||
|
PlayerMock player = server.addPlayer("TestPlayer");
|
||||||
|
InetAddress address = InetAddress.getByName("127.0.0.1");
|
||||||
|
|
||||||
|
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
|
||||||
|
|
||||||
|
listener.onLogin(event);
|
||||||
|
|
||||||
|
assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginEventBlocked() throws Exception {
|
||||||
|
PlayerMock player = server.addPlayer("ProxyPlayer");
|
||||||
|
InetAddress address = InetAddress.getByName("1.1.1.1");
|
||||||
|
|
||||||
|
// Mock proxy response
|
||||||
|
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
|
||||||
|
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
|
||||||
|
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
|
||||||
|
));
|
||||||
|
|
||||||
|
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
|
||||||
|
|
||||||
|
listener.onLogin(event);
|
||||||
|
|
||||||
|
assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult());
|
||||||
|
assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginPipelineProxyPlayerIsKickedWithoutErrors() throws Exception {
|
||||||
|
PlayerMock player = server.addPlayer("PipelineProxyPlayer");
|
||||||
|
InetAddress address = InetAddress.getByName("1.1.1.1");
|
||||||
|
|
||||||
|
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
|
||||||
|
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
|
||||||
|
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
|
||||||
|
));
|
||||||
|
|
||||||
|
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> listener.onLogin(event));
|
||||||
|
|
||||||
|
assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult());
|
||||||
|
assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunCommandDispatchesOnPrimaryThreadWhenCalledAsynchronously() {
|
||||||
|
RecordingServerMock recordingServer = (RecordingServerMock) server;
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
try {
|
||||||
|
CompletableFuture<Void> asyncCall = CompletableFuture.runAsync(
|
||||||
|
() -> listener.runCommand("antivpn-test &aok"),
|
||||||
|
executor
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> asyncCall.get(5, TimeUnit.SECONDS));
|
||||||
|
assertFalse(recordingServer.commandDispatched(), "Command should be scheduled, not dispatched asynchronously");
|
||||||
|
|
||||||
|
server.getScheduler().performOneTick();
|
||||||
|
|
||||||
|
assertTrue(recordingServer.commandDispatched(), "Scheduled command should be dispatched on the next server tick");
|
||||||
|
assertTrue(recordingServer.dispatchedOnPrimaryThread(), "Command dispatch must happen on Bukkit's primary thread");
|
||||||
|
assertEquals("antivpn-test §aok", recordingServer.dispatchedCommand());
|
||||||
|
} finally {
|
||||||
|
executor.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestPlugin extends JavaPlugin {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RecordingServerMock extends ServerMock {
|
||||||
|
private final AtomicBoolean commandDispatched = new AtomicBoolean();
|
||||||
|
private final AtomicBoolean dispatchedOnPrimaryThread = new AtomicBoolean();
|
||||||
|
private final AtomicReference<String> dispatchedCommand = new AtomicReference<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) {
|
||||||
|
commandDispatched.set(true);
|
||||||
|
dispatchedOnPrimaryThread.set(isPrimaryThread());
|
||||||
|
dispatchedCommand.set(commandLine);
|
||||||
|
return super.dispatchCommand(sender, commandLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean commandDispatched() {
|
||||||
|
return commandDispatched.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean dispatchedOnPrimaryThread() {
|
||||||
|
return dispatchedOnPrimaryThread.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dispatchedCommand() {
|
||||||
|
return dispatchedCommand.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package dev.brighten.antivpn.bukkit;
|
||||||
|
|
||||||
|
import be.seeseemelk.mockbukkit.MockBukkit;
|
||||||
|
import be.seeseemelk.mockbukkit.ServerMock;
|
||||||
|
import be.seeseemelk.mockbukkit.entity.PlayerMock;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class BukkitPlayerTest {
|
||||||
|
|
||||||
|
private ServerMock server;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
server = MockBukkit.mock();
|
||||||
|
|
||||||
|
BukkitPlugin pluginBootstrap = mock(BukkitPlugin.class);
|
||||||
|
JavaPlugin plugin = MockBukkit.createMockPlugin();
|
||||||
|
when(pluginBootstrap.getPlugin()).thenReturn(plugin);
|
||||||
|
BukkitPlugin.pluginInstance = pluginBootstrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
BukkitPlugin.pluginInstance = null;
|
||||||
|
MockBukkit.unmock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void kickPlayerCalledFromAsyncContext_isScheduledAndExecutedOnMainThread() {
|
||||||
|
PlayerMock player = server.addPlayer("AsyncKickPlayer");
|
||||||
|
BukkitPlayer bukkitPlayer = new BukkitPlayer(player);
|
||||||
|
|
||||||
|
assertTrue(player.isOnline());
|
||||||
|
|
||||||
|
CompletableFuture<Void> asyncKick = CompletableFuture.runAsync(() -> bukkitPlayer.kickPlayer("&cBlocked!"));
|
||||||
|
assertDoesNotThrow(() -> asyncKick.get(1, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
assertTrue(player.isOnline(), "Kick should be deferred to the server scheduler");
|
||||||
|
|
||||||
|
server.getScheduler().performTicks(1);
|
||||||
|
|
||||||
|
assertFalse(player.isOnline(), "Player should be kicked when scheduled task runs");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>AntiVPN</artifactId>
|
|
||||||
<groupId>dev.brighten.antivpn</groupId>
|
|
||||||
<version>1.9.4</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>Bukkit</artifactId>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>3.13.0</version>
|
|
||||||
<configuration>
|
|
||||||
<source>17</source>
|
|
||||||
<target>17</target>
|
|
||||||
<compilerArgument>-XDignore.symbol.file</compilerArgument>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
<version>3.6.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<minimizeJar>true</minimizeJar>
|
|
||||||
<relocations>
|
|
||||||
<relocation>
|
|
||||||
<pattern>org.bstats</pattern>
|
|
||||||
<!-- Replace this with your package! -->
|
|
||||||
<shadedPattern>dev.brighten.antivpn.bukkit.org.bstats</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>org.yaml.snakeyaml</pattern>
|
|
||||||
<shadedPattern>dev.brighten.antivpn.shaded.org.yaml.snakeyaml</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
</relocations>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>spigot-repo</id>
|
|
||||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.spigotmc</groupId>
|
|
||||||
<artifactId>spigot-api</artifactId>
|
|
||||||
<version>1.20.2-R0.1-SNAPSHOT</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>dev.brighten.antivpn</groupId>
|
|
||||||
<artifactId>Common</artifactId>
|
|
||||||
<version>1.9.4</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bstats</groupId>
|
|
||||||
<artifactId>bstats-bukkit</artifactId>
|
|
||||||
<version>2.2.1</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
package dev.brighten.antivpn.bukkit;
|
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
|
||||||
import dev.brighten.antivpn.api.APIPlayer;
|
|
||||||
import dev.brighten.antivpn.api.CheckResult;
|
|
||||||
import dev.brighten.antivpn.api.OfflinePlayer;
|
|
||||||
import dev.brighten.antivpn.api.VPNExecutor;
|
|
||||||
import dev.brighten.antivpn.message.VpnString;
|
|
||||||
import dev.brighten.antivpn.utils.StringUtil;
|
|
||||||
import dev.brighten.antivpn.utils.Tuple;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.EventPriority;
|
|
||||||
import org.bukkit.event.HandlerList;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
|
||||||
import org.bukkit.event.player.PlayerLoginEvent;
|
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public class BukkitListener extends VPNExecutor implements Listener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerListeners() {
|
|
||||||
BukkitPlugin.pluginInstance.getServer().getPluginManager()
|
|
||||||
.registerEvents(this, BukkitPlugin.pluginInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void log(Level level, String log, Object... objects) {
|
|
||||||
Bukkit.getLogger().log(level, String.format(log, objects));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void log(String log, Object... objects) {
|
|
||||||
log(Level.INFO, String.format(log, objects));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logException(String message, Throwable ex) {
|
|
||||||
Bukkit.getLogger().log(Level.SEVERE, message, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void runCommand(String command) {
|
|
||||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(),
|
|
||||||
ChatColor.translateAlternateColorCodes('&', command));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disablePlugin() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
BukkitPlugin.pluginInstance.getServer().getPluginManager().disablePlugin(BukkitPlugin.pluginInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGH)
|
|
||||||
public void onLogin(final PlayerLoginEvent event) {
|
|
||||||
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
|
|
||||||
.orElse(new OfflinePlayer(
|
|
||||||
event.getPlayer().getUniqueId(),
|
|
||||||
event.getPlayer().getName(),
|
|
||||||
event.getAddress()
|
|
||||||
));
|
|
||||||
|
|
||||||
CheckResult instantResult = player.checkPlayer(result -> {
|
|
||||||
if(!result.resultType().isShouldBlock()) return;
|
|
||||||
|
|
||||||
AntiVPN.getInstance().getExecutor().log(Level.INFO, "Adding %s to kick", event.getPlayer().getName());
|
|
||||||
AntiVPN.getInstance().getExecutor().getToKick().add(new Tuple<>(result, event.getPlayer().getUniqueId()));
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!instantResult.resultType().isShouldBlock()) return;
|
|
||||||
|
|
||||||
AntiVPN.getInstance().getExecutor().getToKick()
|
|
||||||
.add(new Tuple<>(instantResult, event.getPlayer().getUniqueId()));
|
|
||||||
|
|
||||||
if(!AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AntiVPN.getInstance().getExecutor().log(Level.INFO, "%s was kicked from pre-login cache with IP %s", event.getPlayer().getName(), instantResult.response().getIp());
|
|
||||||
|
|
||||||
event.setResult(PlayerLoginEvent.Result.KICK_BANNED);
|
|
||||||
switch (instantResult.resultType()) {
|
|
||||||
case DENIED_COUNTRY -> event.setKickMessage(StringUtil.translateAlternateColorCodes('&',
|
|
||||||
StringUtil.varReplace(
|
|
||||||
AntiVPN.getInstance().getVpnConfig().countryVanillaKickReason(),
|
|
||||||
player,
|
|
||||||
instantResult.response()
|
|
||||||
)));
|
|
||||||
case DENIED_PROXY -> {
|
|
||||||
if(AntiVPN.getInstance().getVpnConfig().alertToStaff()) {
|
|
||||||
AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream()
|
|
||||||
.filter(APIPlayer::isAlertsEnabled)
|
|
||||||
.forEach(pl ->
|
|
||||||
pl.sendMessage(StringUtil.varReplace(
|
|
||||||
ChatColor.translateAlternateColorCodes(
|
|
||||||
'&',
|
|
||||||
AntiVPN.getInstance().getVpnConfig().alertMessage()),
|
|
||||||
player,
|
|
||||||
instantResult.response())));
|
|
||||||
}
|
|
||||||
event.setKickMessage(StringUtil.translateAlternateColorCodes('&',
|
|
||||||
StringUtil.varReplace(
|
|
||||||
AntiVPN.getInstance().getVpnConfig().getKickString(),
|
|
||||||
player,
|
|
||||||
instantResult.response()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
|
||||||
public void onJoin(final PlayerJoinEvent event) {
|
|
||||||
AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
|
|
||||||
.ifPresent(player -> AntiVPN.getInstance().getDatabase().alertsState(player.getUuid(), enabled -> {
|
|
||||||
if(enabled) {
|
|
||||||
player.setAlertsEnabled(true);
|
|
||||||
player.sendMessage(AntiVPN.getInstance().getMessageHandler()
|
|
||||||
.getString("command-alerts-toggled")
|
|
||||||
.getFormattedMessage(new VpnString.Var<>("state", true)));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onQuit(PlayerQuitEvent event) {
|
|
||||||
AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package dev.brighten.antivpn.bukkit;
|
|
||||||
|
|
||||||
import dev.brighten.antivpn.api.APIPlayer;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
|
||||||
|
|
||||||
public class BukkitPlayer extends APIPlayer {
|
|
||||||
|
|
||||||
private final Player player;
|
|
||||||
public BukkitPlayer(Player player) {
|
|
||||||
super(player.getUniqueId(), player.getName(), player.getAddress().getAddress());
|
|
||||||
|
|
||||||
this.player = player;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(String message) {
|
|
||||||
player.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void kickPlayer(String reason) {
|
|
||||||
if(!Bukkit.isPrimaryThread()) {
|
|
||||||
new BukkitRunnable() {
|
|
||||||
public void run() {
|
|
||||||
player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason));
|
|
||||||
}
|
|
||||||
}.runTask(BukkitPlugin.pluginInstance);
|
|
||||||
} else player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasPermission(String permission) {
|
|
||||||
return player.hasPermission(permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
name: KauriVPN
|
|
||||||
main: dev.brighten.antivpn.bukkit.BukkitPlugin
|
|
||||||
version: ${project.version}
|
|
||||||
author: funkemunky
|
|
||||||
api-version: 1.13
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.gradleup.shadow'
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluationDependsOn(':Bungee:BungeePlugin')
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'net.md-5:bungeecord-api:1.21-R0.2'
|
||||||
|
compileOnly project(':Bungee:BungeePlugin')
|
||||||
|
implementation project(':Common:loader-utils')
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveClassifier.set('')
|
||||||
|
|
||||||
|
// Include the shaded plugin jar as a single resource file
|
||||||
|
from(project(':Bungee:BungeePlugin').tasks.shadowJar) {
|
||||||
|
rename { 'antivpn-bungee.jarinjar' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('shadowJar') {
|
||||||
|
dependsOn(':Bungee:BungeePlugin:shadowJar')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.build.dependsOn shadowJar
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.bungee;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.loader.JarInJarClassLoader;
|
||||||
|
import dev.brighten.antivpn.loader.LoaderBootstrap;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
|
||||||
|
public class BungeeLoaderPlugin extends Plugin {
|
||||||
|
|
||||||
|
private static final String JAR_NAME = "antivpn-bungee.jarinjar";
|
||||||
|
private static final String SOURCE_NAME = "antivpn-source.jarinjar";
|
||||||
|
private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bungee.BungeePlugin";
|
||||||
|
|
||||||
|
private final LoaderBootstrap plugin;
|
||||||
|
|
||||||
|
public BungeeLoaderPlugin() {
|
||||||
|
JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME);
|
||||||
|
this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Plugin.class, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad() {
|
||||||
|
this.plugin.onLoad(getDataFolder());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
this.plugin.onEnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
this.plugin.onDisable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: KauriVPN
|
name: KauriVPN
|
||||||
main: dev.brighten.antivpn.bungee.BungeePlugin
|
main: dev.brighten.antivpn.bungee.BungeeLoaderPlugin
|
||||||
description: A simple and fast antivpn plugin.
|
description: A simple and fast antivpn plugin.
|
||||||
version: ${project.version}
|
version: ${project.version}
|
||||||
author: funkemunky
|
author: funkemunky
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.gradleup.shadow'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'net.md-5:bungeecord-api:1.21-R0.2'
|
||||||
|
testImplementation 'net.md-5:bungeecord-api:1.21-R0.2'
|
||||||
|
implementation project(':Common:Source')
|
||||||
|
implementation project(':Common:loader-utils')
|
||||||
|
implementation 'org.bstats:bstats-bungeecord:2.2.1'
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
|
||||||
|
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||||
|
testImplementation 'org.mockito:mockito-subclass:5.11.0'
|
||||||
|
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||||
|
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||||
|
}
|
||||||
|
tasks.compileJava.dependsOn(':Common:Source:jar')
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
systemProperty 'mockito.mockmaker', 'subclass'
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveClassifier.set('')
|
||||||
|
relocate 'org.bstats', 'dev.brighten.antivpn.bungee.org.bstats'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.build.dependsOn shadowJar
|
||||||
+42
-39
@@ -1,12 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bungee;
|
package dev.brighten.antivpn.bungee;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
import dev.brighten.antivpn.api.*;
|
import dev.brighten.antivpn.api.*;
|
||||||
import dev.brighten.antivpn.utils.MiscUtils;
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
import dev.brighten.antivpn.utils.StringUtil;
|
import dev.brighten.antivpn.utils.StringUtil;
|
||||||
import dev.brighten.antivpn.utils.Tuple;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import net.md_5.bungee.api.chat.TextComponent;
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
|
import net.md_5.bungee.api.event.LoginEvent;
|
||||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||||
import net.md_5.bungee.api.plugin.Listener;
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
@@ -25,7 +40,7 @@ public class BungeeListener extends VPNExecutor implements Listener {
|
|||||||
@Override
|
@Override
|
||||||
public void registerListeners() {
|
public void registerListeners() {
|
||||||
BungeePlugin.pluginInstance.getProxy().getPluginManager()
|
BungeePlugin.pluginInstance.getProxy().getPluginManager()
|
||||||
.registerListener(BungeePlugin.pluginInstance, this);
|
.registerListener(BungeePlugin.pluginInstance.getPlugin(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -35,7 +50,7 @@ public class BungeeListener extends VPNExecutor implements Listener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void log(String log, Object... objects) {
|
public void log(String log, Object... objects) {
|
||||||
log(Level.INFO, String.format(log, objects));
|
log(Level.INFO, log, objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -51,12 +66,12 @@ public class BungeeListener extends VPNExecutor implements Listener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disablePlugin() {
|
public void disablePlugin() {
|
||||||
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance);
|
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance.getPlugin());
|
||||||
if (cacheResetTask != null) {
|
if (cacheResetTask != null) {
|
||||||
cacheResetTask.cancel();
|
cacheResetTask.cancel();
|
||||||
cacheResetTask = null;
|
cacheResetTask = null;
|
||||||
}
|
}
|
||||||
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance);
|
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance.getPlugin());
|
||||||
BungeePlugin.pluginInstance.onDisable();
|
BungeePlugin.pluginInstance.onDisable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,43 +88,31 @@ public class BungeeListener extends VPNExecutor implements Listener {
|
|||||||
((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress());
|
((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress());
|
||||||
});
|
});
|
||||||
|
|
||||||
CheckResult instantResult = player.checkPlayer(result -> {
|
player.checkPlayer(result -> {
|
||||||
if (!result.resultType().isShouldBlock()) return;
|
if (!result.resultType().isShouldBlock()) return;
|
||||||
AntiVPN.getInstance().getExecutor().getToKick()
|
|
||||||
.add(new Tuple<>(result, player.getUuid()));
|
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setCancelled(true);
|
||||||
|
event.setReason(TextComponent.fromLegacy(StringUtil.varReplace(switch (result.resultType()) {
|
||||||
|
case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
|
||||||
|
.getKickMessage(), player, result.response());
|
||||||
|
case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
|
||||||
|
.getCountryVanillaKickReason(), player, result.response());
|
||||||
|
default -> "You were kicked by KauriVPN for an unknown reason!";
|
||||||
|
}, player, result.response())));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!instantResult.resultType().isShouldBlock()) {
|
@EventHandler(priority = EventPriority.HIGH)
|
||||||
return;
|
public void onJoin(LoginEvent event) {
|
||||||
}
|
if(event.isCancelled()) return;
|
||||||
|
|
||||||
AntiVPN.getInstance().getExecutor().getToKick()
|
// Handling player alerts on join
|
||||||
.add(new Tuple<>(instantResult, player.getUuid()));
|
AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId())
|
||||||
|
.ifPresent(APIPlayer::checkAlertsState);
|
||||||
if (!AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.setCancelled(true);
|
|
||||||
AntiVPN.getInstance().getExecutor().log(Level.INFO,
|
|
||||||
"%s was kicked from pre-login proxy cache.",
|
|
||||||
event.getConnection().getName());
|
|
||||||
|
|
||||||
|
|
||||||
switch (instantResult.resultType()) {
|
|
||||||
case DENIED_PROXY -> event.setReason(TextComponent.fromLegacy(ChatColor
|
|
||||||
.translateAlternateColorCodes('&',
|
|
||||||
StringUtil.varReplace(
|
|
||||||
AntiVPN.getInstance().getVpnConfig().getKickString(),
|
|
||||||
player,
|
|
||||||
instantResult.response()))));
|
|
||||||
case DENIED_COUNTRY -> event.setReason(TextComponent.fromLegacy(ChatColor
|
|
||||||
.translateAlternateColorCodes('&',
|
|
||||||
StringUtil.varReplace(
|
|
||||||
AntiVPN.getInstance().getVpnConfig().countryVanillaKickReason(),
|
|
||||||
player,
|
|
||||||
instantResult.response()))));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bungee;
|
package dev.brighten.antivpn.bungee;
|
||||||
|
|
||||||
import dev.brighten.antivpn.api.APIPlayer;
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bungee;
|
package dev.brighten.antivpn.bungee;
|
||||||
|
|
||||||
import dev.brighten.antivpn.api.APIPlayer;
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.bungee;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.bungee.command.BungeeCommand;
|
||||||
|
import dev.brighten.antivpn.command.Command;
|
||||||
|
import dev.brighten.antivpn.database.VPNDatabase;
|
||||||
|
import dev.brighten.antivpn.database.local.H2VPN;
|
||||||
|
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
||||||
|
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
||||||
|
import dev.brighten.antivpn.loader.LoaderBootstrap;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import org.bstats.bungeecord.Metrics;
|
||||||
|
import org.bstats.charts.SimplePie;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class BungeePlugin implements LoaderBootstrap {
|
||||||
|
|
||||||
|
public static BungeePlugin pluginInstance;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private File dataFolder;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Plugin plugin;
|
||||||
|
|
||||||
|
public BungeePlugin(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(File dataFolder) {
|
||||||
|
this.dataFolder = dataFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
pluginInstance = this;
|
||||||
|
|
||||||
|
//Setting up config
|
||||||
|
ProxyServer.getInstance().getLogger().info("Loading config...");
|
||||||
|
|
||||||
|
|
||||||
|
//Loading plugin
|
||||||
|
ProxyServer.getInstance().getLogger().info("Starting AntiVPN services...");
|
||||||
|
AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder());
|
||||||
|
|
||||||
|
if(AntiVPN.getInstance().getVpnConfig().metrics()) {
|
||||||
|
ProxyServer.getInstance().getLogger().info("Starting bStats metrics...");
|
||||||
|
Metrics metrics = new Metrics(getPlugin(), 12616);
|
||||||
|
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
|
||||||
|
ProxyServer.getInstance().getScheduler().schedule(getPlugin(),
|
||||||
|
() -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0,
|
||||||
|
10, 10, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Command command : AntiVPN.getInstance().getCommands()) {
|
||||||
|
ProxyServer.getInstance().getPluginManager().registerCommand(getPlugin(), new BungeeCommand(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
AntiVPN.getInstance().stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDatabaseType() {
|
||||||
|
VPNDatabase database = AntiVPN.getInstance().getDatabase();
|
||||||
|
if(database instanceof MySqlVPN) {
|
||||||
|
return "MySQL";
|
||||||
|
} else if(database instanceof H2VPN) {
|
||||||
|
return "H2";
|
||||||
|
} else if(database instanceof MongoVPN) {
|
||||||
|
return "MongoDB";
|
||||||
|
} else {
|
||||||
|
return "No-Database";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyServer getProxy() {
|
||||||
|
return ProxyServer.getInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bungee.command;
|
package dev.brighten.antivpn.bungee.command;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.bungee.command;
|
package dev.brighten.antivpn.bungee.command;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package dev.brighten.antivpn.bungee;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.api.PlayerExecutor;
|
||||||
|
import dev.brighten.antivpn.api.VPNConfig;
|
||||||
|
import dev.brighten.antivpn.api.VPNExecutor;
|
||||||
|
import dev.brighten.antivpn.message.MessageHandler;
|
||||||
|
import dev.brighten.antivpn.message.VpnString;
|
||||||
|
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||||
|
import net.md_5.bungee.api.connection.PendingConnection;
|
||||||
|
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
public class BungeeListenerTest {
|
||||||
|
|
||||||
|
private BungeeListener listener;
|
||||||
|
private VPNExecutor vpnExecutor;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
AntiVPN antiVPN = mock(AntiVPN.class);
|
||||||
|
VPNConfig config = mock(VPNConfig.class);
|
||||||
|
PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
|
||||||
|
vpnExecutor = mock(VPNExecutor.class);
|
||||||
|
MessageHandler messageHandler = mock(MessageHandler.class);
|
||||||
|
|
||||||
|
when(antiVPN.getVpnConfig()).thenReturn(config);
|
||||||
|
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
|
||||||
|
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
|
||||||
|
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
|
||||||
|
|
||||||
|
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
|
||||||
|
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
|
||||||
|
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
|
||||||
|
when(config.isKickPlayers()).thenReturn(true);
|
||||||
|
when(config.getKickMessage()).thenReturn("Blocked!");
|
||||||
|
|
||||||
|
VpnString mockVpnString = mock(VpnString.class);
|
||||||
|
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
|
||||||
|
when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
|
||||||
|
|
||||||
|
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
|
||||||
|
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
|
||||||
|
.method("N/A").countryName("N/A").city("N/A").build()
|
||||||
|
));
|
||||||
|
|
||||||
|
// Use reflection to set the private static INSTANCE field
|
||||||
|
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||||
|
instanceField.setAccessible(true);
|
||||||
|
instanceField.set(null, antiVPN);
|
||||||
|
|
||||||
|
listener = new BungeeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
// Reset the singleton
|
||||||
|
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||||
|
instanceField.setAccessible(true);
|
||||||
|
instanceField.set(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreLoginEventAllowed() {
|
||||||
|
PreLoginEvent event = mock(PreLoginEvent.class);
|
||||||
|
PendingConnection connection = mock(PendingConnection.class);
|
||||||
|
|
||||||
|
when(event.getConnection()).thenReturn(connection);
|
||||||
|
when(connection.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||||
|
when(connection.getName()).thenReturn("TestPlayer");
|
||||||
|
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345));
|
||||||
|
|
||||||
|
listener.onListener(event);
|
||||||
|
|
||||||
|
verify(event, never()).setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreLoginEventBlocked() {
|
||||||
|
PreLoginEvent event = mock(PreLoginEvent.class);
|
||||||
|
PendingConnection connection = mock(PendingConnection.class);
|
||||||
|
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
when(event.getConnection()).thenReturn(connection);
|
||||||
|
when(connection.getUniqueId()).thenReturn(uuid);
|
||||||
|
when(connection.getName()).thenReturn("ProxyPlayer");
|
||||||
|
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345));
|
||||||
|
|
||||||
|
// Mock proxy response
|
||||||
|
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
|
||||||
|
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
|
||||||
|
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
|
||||||
|
));
|
||||||
|
|
||||||
|
listener.onListener(event);
|
||||||
|
|
||||||
|
verify(event).setCancelled(true);
|
||||||
|
verify(event).setReason(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>AntiVPN</artifactId>
|
|
||||||
<groupId>dev.brighten.antivpn</groupId>
|
|
||||||
<version>1.9.4</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>Bungee</artifactId>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>3.13.0</version>
|
|
||||||
<configuration>
|
|
||||||
<source>17</source>
|
|
||||||
<target>17</target>
|
|
||||||
<compilerArgument>-XDignore.symbol.file</compilerArgument>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
<version>3.6.0</version>
|
|
||||||
<configuration>
|
|
||||||
<relocations>
|
|
||||||
<relocation>
|
|
||||||
<pattern>org.bstats</pattern>
|
|
||||||
<!-- Replace this with your package! -->
|
|
||||||
<shadedPattern>dev.brighten.antivpn.bungee.org.bstats</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>org.yaml.snakeyaml</pattern>
|
|
||||||
<shadedPattern>dev.brighten.antivpn.shaded.org.yaml.snakeyaml</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
</relocations>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>dev.brighten.antivpn</groupId>
|
|
||||||
<artifactId>Common</artifactId>
|
|
||||||
<version>1.9.4</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.md-5</groupId>
|
|
||||||
<artifactId>bungeecord-api</artifactId>
|
|
||||||
<version>1.21-R0.2</version>
|
|
||||||
<type>jar</type>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bstats</groupId>
|
|
||||||
<artifactId>bstats-bungeecord</artifactId>
|
|
||||||
<version>2.2.1</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package dev.brighten.antivpn.bungee;
|
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
|
||||||
import dev.brighten.antivpn.bungee.command.BungeeCommand;
|
|
||||||
import dev.brighten.antivpn.command.Command;
|
|
||||||
import dev.brighten.antivpn.database.VPNDatabase;
|
|
||||||
import dev.brighten.antivpn.database.local.H2VPN;
|
|
||||||
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
|
||||||
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
|
||||||
import net.md_5.bungee.api.plugin.Plugin;
|
|
||||||
import org.bstats.bungeecord.Metrics;
|
|
||||||
import org.bstats.charts.SimplePie;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class BungeePlugin extends Plugin {
|
|
||||||
|
|
||||||
public static BungeePlugin pluginInstance;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
pluginInstance = this;
|
|
||||||
|
|
||||||
//Setting up config
|
|
||||||
getProxy().getLogger().info("Loading config...");
|
|
||||||
|
|
||||||
|
|
||||||
//Loading plugin
|
|
||||||
getProxy().getLogger().info("Starting AntiVPN services...");
|
|
||||||
AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder());
|
|
||||||
|
|
||||||
if(AntiVPN.getInstance().getVpnConfig().metrics()) {
|
|
||||||
getProxy().getLogger().info("Starting bStats metrics...");
|
|
||||||
Metrics metrics = new Metrics(this, 12616);
|
|
||||||
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
|
|
||||||
getProxy().getScheduler().schedule(this,
|
|
||||||
() -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0,
|
|
||||||
10, 10, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Command command : AntiVPN.getInstance().getCommands()) {
|
|
||||||
getProxy().getPluginManager().registerCommand(pluginInstance, new BungeeCommand(command));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
AntiVPN.getInstance().stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDatabaseType() {
|
|
||||||
VPNDatabase database = AntiVPN.getInstance().getDatabase();
|
|
||||||
|
|
||||||
if(database instanceof H2VPN) {
|
|
||||||
return "H2";
|
|
||||||
} else if(database instanceof MySqlVPN) {
|
|
||||||
return "MySQL";
|
|
||||||
} else if(database instanceof MongoVPN) {
|
|
||||||
return "MongoDB";
|
|
||||||
} else {
|
|
||||||
return "No-Database";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+33
-23
@@ -4,36 +4,46 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.10.0] - 2026-04-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- CIDR allowlisting, including commands to add, remove, view, and search entries
|
||||||
|
- MongoDB support for CIDR allowlist storage
|
||||||
|
- VPN detection webhooks with Discord and Slack formatting options
|
||||||
|
- Mojang API fallback support for player lookups
|
||||||
|
- Folia support
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved player blocking so flagged users are removed more reliably across platforms
|
||||||
|
- Updated allowlist handling to validate CIDR entries more consistently
|
||||||
|
- Improved database cleanup for outdated cached responses
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- SQL startup and loading issues, including MySQL library injection problems
|
||||||
|
- CIDR parsing issues and MongoDB CIDR lookup failures
|
||||||
|
- Allowlist-related SQL errors
|
||||||
|
- Repeated webhook spam from duplicate VPN detection events
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Expanded webhook setup documentation for Discord and Slack
|
||||||
|
|
||||||
## [1.9.4] - 2025-09-30
|
## [1.9.4] - 2025-09-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- New dependency management system with automatic library loading and relocation
|
- Sponge platform support
|
||||||
- Caffeine cache implementation to replace Guava
|
- UUID lookup support for player validation
|
||||||
- Sponge platform support with full event handling and command system
|
- Better scheduled kick checking
|
||||||
- UUID lookup functionality for player validation
|
- Java 17 and Java 21 support
|
||||||
- Enhanced kick checking system with scheduled task execution
|
- Database metrics tracking for bStats
|
||||||
- Support for Java 17 and Java 21 runtime environments
|
|
||||||
- New database metrics tracking for bStats
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **BREAKING**: Minimum Java version upgraded from 8 to 17
|
- **BREAKING**: Minimum Java version upgraded from 8 to 17
|
||||||
- Replaced Guava cache with Caffeine cache for better performance
|
- Replaced the old cache implementation with Caffeine for better performance
|
||||||
- Modernized player checking system with asynchronous processing
|
- Improved asynchronous player checking and VPN detection handling
|
||||||
- Improved database connection handling with proper resource management
|
- Improved database connection management and error handling
|
||||||
- Enhanced VPN/Proxy detection with new `CheckResult` and `ResultType` system
|
|
||||||
- Updated Maven dependencies and build process
|
|
||||||
- Reorganized project structure (Assembly → Universal module)
|
|
||||||
- Improved error handling and exception logging throughout codebase
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- H2 database compatibility issues with automatic backup and recovery
|
- H2 database compatibility issues with automatic backup and recovery
|
||||||
- Memory leaks in database result set handling with try-with-resources
|
- Memory leaks and resource cleanup problems in database handling
|
||||||
- Thread safety issues in player cache management
|
- Thread safety issues in player cache management
|
||||||
- Command registration and unregistration during plugin lifecycle
|
- Command registration issues during plugin startup and shutdown
|
||||||
- Proper cleanup of database drivers on shutdown
|
|
||||||
- Resource management in SQL connections and prepared statements
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
- Guava dependency (replaced with Caffeine and built-in utilities)
|
|
||||||
- Legacy cached response handling system
|
|
||||||
- Old table format compatibility code
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.gradleup.shadow'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.ow2.asm:asm:9.8'
|
||||||
|
implementation 'org.ow2.asm:asm-commons:9.8'
|
||||||
|
implementation 'org.yaml:snakeyaml:2.2'
|
||||||
|
implementation 'org.jetbrains:annotations:26.0.2'
|
||||||
|
|
||||||
|
compileOnly 'com.mysql:mysql-connector-j:9.3.0'
|
||||||
|
compileOnly 'com.h2database:h2:2.2.220'
|
||||||
|
implementation'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||||
|
compileOnly 'org.mongodb:mongo-java-driver:3.12.14'
|
||||||
|
|
||||||
|
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter:5.8.1"
|
||||||
|
testImplementation "org.testcontainers:testcontainers:2.0.4"
|
||||||
|
testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.4"
|
||||||
|
testImplementation 'org.testcontainers:mysql:1.20.4'
|
||||||
|
testImplementation 'org.testcontainers:mongodb:1.20.4'
|
||||||
|
testRuntimeOnly 'org.slf4j:slf4j-simple:2.0.16'
|
||||||
|
testImplementation 'com.mysql:mysql-connector-j:9.3.0'
|
||||||
|
testImplementation 'com.h2database:h2:2.2.220'
|
||||||
|
testImplementation 'org.mongodb:mongo-java-driver:3.12.14'
|
||||||
|
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveClassifier.set('')
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
exclude 'dev/brighten/antivpn/depends/Relocate*'
|
||||||
|
exclude 'dev/brighten/antivpn/depends/MavenLibraries*'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.build.dependsOn shadowJar
|
||||||
|
components.java.withVariantsFromConfiguration(configurations.shadowRuntimeElements) {
|
||||||
|
skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
archiveClassifier.set('raw')
|
||||||
|
}
|
||||||
+25
-18
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn;
|
package dev.brighten.antivpn;
|
||||||
|
|
||||||
import dev.brighten.antivpn.api.PlayerExecutor;
|
import dev.brighten.antivpn.api.PlayerExecutor;
|
||||||
@@ -11,7 +27,6 @@ import dev.brighten.antivpn.database.mongo.MongoVPN;
|
|||||||
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
||||||
import dev.brighten.antivpn.depends.LibraryLoader;
|
import dev.brighten.antivpn.depends.LibraryLoader;
|
||||||
import dev.brighten.antivpn.depends.MavenLibrary;
|
import dev.brighten.antivpn.depends.MavenLibrary;
|
||||||
import dev.brighten.antivpn.depends.Relocate;
|
|
||||||
import dev.brighten.antivpn.message.MessageHandler;
|
import dev.brighten.antivpn.message.MessageHandler;
|
||||||
import dev.brighten.antivpn.utils.ConfigDefault;
|
import dev.brighten.antivpn.utils.ConfigDefault;
|
||||||
import dev.brighten.antivpn.utils.MiscUtils;
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
@@ -32,25 +47,13 @@ import java.util.List;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter(AccessLevel.PRIVATE)
|
@Setter(AccessLevel.PRIVATE)
|
||||||
@MavenLibrary(groupId = "com.h2database", artifactId ="h2", version = "2.2.220", relocations = {
|
@MavenLibrary(groupId = "com.h2database", artifactId ="h2", version = "2.2.220")
|
||||||
@Relocate(from ="org" + ".\\h2", to ="dev.brighten.antivpn.shaded.org.h2")})
|
@MavenLibrary(groupId = "org.mongodb", artifactId = "mongo-java-driver", version = "3.12.14")
|
||||||
@MavenLibrary(groupId = "org.mongodb", artifactId = "mongo-java-driver", version = "3.12.14", relocations = {
|
|
||||||
@Relocate(from = "com." + "\\mongodb", to = "dev.brighten.antivpn.shaded.com.mongodb"),
|
|
||||||
@Relocate(from = "org" + "\\.bson", to = "dev.brighten.antivpn.shaded.org.bson")
|
|
||||||
})
|
|
||||||
@MavenLibrary(
|
@MavenLibrary(
|
||||||
groupId = "com.mysql",
|
groupId = "com.mysql",
|
||||||
artifactId = "mysql-connector-j",
|
artifactId = "mysql-connector-j",
|
||||||
version = "9.1.0",
|
version = "9.3.0"
|
||||||
relocations = {
|
|
||||||
@Relocate(from = "com.my\\" + "sql.cj", to = "dev.brighten.antivpn.shaded.com.mysql.cj"),
|
|
||||||
@Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc")
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
@MavenLibrary(groupId = "com.\\github\\.ben-manes\\.caffeine", artifactId = "caffeine", version = "3.1.8",
|
|
||||||
relocations = {
|
|
||||||
@Relocate(from = "com\\.github\\.benmanes\\.caffeine", to = "dev.brighten.antivpn.shaded.com.github.benmanes.caffeine"),
|
|
||||||
})
|
|
||||||
public class AntiVPN {
|
public class AntiVPN {
|
||||||
|
|
||||||
private static AntiVPN INSTANCE;
|
private static AntiVPN INSTANCE;
|
||||||
@@ -109,7 +112,7 @@ public class AntiVPN {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "mysql":
|
case "mysql":
|
||||||
case "sql":{
|
case "sql": {
|
||||||
AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
|
AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
|
||||||
INSTANCE.database = new MySqlVPN();
|
INSTANCE.database = new MySqlVPN();
|
||||||
INSTANCE.database.init();
|
INSTANCE.database.init();
|
||||||
@@ -190,8 +193,12 @@ public class AntiVPN {
|
|||||||
executor.log("Failed to deregister H2 driver: " + e.getMessage());
|
executor.log("Failed to deregister H2 driver: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VPNExecutor.threadExecutor.shutdown();
|
if (executor != null && executor.getThreadExecutor() != null) {
|
||||||
|
executor.getThreadExecutor().shutdown();
|
||||||
|
}
|
||||||
if(database != null) database.shutdown();
|
if(database != null) database.shutdown();
|
||||||
|
|
||||||
|
INSTANCE = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadDatabase() {
|
public void reloadDatabase() {
|
||||||
+58
-14
@@ -1,8 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.api;
|
package dev.brighten.antivpn.api;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.message.VpnString;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -40,23 +57,45 @@ public abstract class APIPlayer {
|
|||||||
public void updateAlertsState() {
|
public void updateAlertsState() {
|
||||||
//Updating into database so its synced across servers and saved on logout.
|
//Updating into database so its synced across servers and saved on logout.
|
||||||
AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled);
|
AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled);
|
||||||
|
|
||||||
|
sendMessage(AntiVPN.getInstance().getMessageHandler()
|
||||||
|
.getString("command-alerts-toggled")
|
||||||
|
.getFormattedMessage(new VpnString.Var<>("state", alertsEnabled)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckResult checkPlayer(Consumer<CheckResult> onKick) {
|
public void checkAlertsState() {
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() ->
|
||||||
|
AntiVPN.getInstance().getDatabase().alertsState(uuid, state -> {
|
||||||
|
if(state) {
|
||||||
|
alertsEnabled = true;
|
||||||
|
updateAlertsState();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkPlayer(Consumer<CheckResult> onResult) {
|
||||||
if (hasPermission("antivpn.bypass") //Has bypass permission
|
if (hasPermission("antivpn.bypass") //Has bypass permission
|
||||||
//Is exempt
|
//Is exempt
|
||||||
|| (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
|
|| (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
|
||||||
//Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|
//Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|
||||||
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress())
|
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32")
|
||||||
|| AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream()
|
|| AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream()
|
||||||
.anyMatch(name::startsWith)) return new CheckResult(null, ResultType.WHITELISTED);
|
.anyMatch(name::startsWith)) {
|
||||||
|
onResult.accept(new CheckResult(null, ResultType.WHITELISTED, false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress());
|
CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress());
|
||||||
|
|
||||||
if(cachedResult != null) {
|
if(cachedResult != null) {
|
||||||
if(cachedResult.response().getIp().equals(ip.getHostAddress())) {
|
if(cachedResult.response().getIp().equals(ip.getHostAddress())) {
|
||||||
AntiVPN.getInstance().getExecutor().log(Level.FINE, "Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType());
|
AntiVPN.getInstance().getExecutor().log(Level.FINE, "Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType());
|
||||||
return cachedResult;
|
if(cachedResult.resultType().isShouldBlock()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this);
|
||||||
|
}
|
||||||
|
onResult.accept(cachedResult);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,36 +105,41 @@ public abstract class APIPlayer {
|
|||||||
AntiVPN.getInstance().getExecutor().log(Level.WARNING, "The API query was not a success! " +
|
AntiVPN.getInstance().getExecutor().log(Level.WARNING, "The API query was not a success! " +
|
||||||
"You may need to upgrade your license on " +
|
"You may need to upgrade your license on " +
|
||||||
"https://funkemunky.cc/shop");
|
"https://funkemunky.cc/shop");
|
||||||
|
onResult.accept(new CheckResult(null, ResultType.API_FAILURE, false));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// If the countryList() size is zero, no need to check.
|
// If the countryList() size is zero, no need to check.
|
||||||
// Running country check first
|
// Running country check first
|
||||||
CheckResult checkResult;
|
CheckResult checkResult;
|
||||||
if (!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty()
|
if (!AntiVPN.getInstance().getVpnConfig().getCountryList().isEmpty()
|
||||||
&& !((uuid != null && AntiVPN.getInstance().getExecutor()
|
&& !((uuid != null && AntiVPN.getInstance().getExecutor()
|
||||||
.isWhitelisted(uuid))
|
.isWhitelisted(uuid))
|
||||||
//Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|
//Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|
||||||
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress()))
|
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32"))
|
||||||
// This bit of code will decide whether or not to kick the player
|
// This bit of code will decide whether or not to kick the player
|
||||||
// If it contains the code and it is set to whitelist, it will not kick
|
// If it contains the code and it is set to whitelist, it will not kick
|
||||||
// as they are equal and vise versa. However, if the contains does not match
|
// as they are equal and vise versa. However, if the contains does not match
|
||||||
// the state, it will kick.
|
// the state, it will kick.
|
||||||
&& AntiVPN.getInstance().getVpnConfig().countryList()
|
&& AntiVPN.getInstance().getVpnConfig().getCountryList()
|
||||||
.contains(result.getCountryCode())
|
.contains(result.getCountryCode())
|
||||||
!= AntiVPN.getInstance().getVpnConfig().whitelistCountries()) {
|
!= AntiVPN.getInstance().getVpnConfig().getWhitelistCountries()) {
|
||||||
//Using our built in kicking system if no commands are configured
|
//Using our built in kicking system if no commands are configured
|
||||||
checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY);
|
checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY, false);
|
||||||
} else if (result.isProxy()) {
|
} else if (result.isProxy()) {
|
||||||
checkResult = new CheckResult(result, ResultType.DENIED_PROXY);
|
checkResult = new CheckResult(result, ResultType.DENIED_PROXY, false);
|
||||||
} else {
|
} else {
|
||||||
checkResult = new CheckResult(result, ResultType.ALLOWED);
|
checkResult = new CheckResult(result, ResultType.ALLOWED, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AntiVPN.getInstance().getExecutor().log(Level.FINE, "Result for " + ip.getHostAddress() + " is " + checkResult.resultType());
|
AntiVPN.getInstance().getExecutor().log(Level.FINE, "Result for " + ip.getHostAddress() + " is " + checkResult.resultType());
|
||||||
|
|
||||||
checkResultCache.put(ip.getHostAddress(), checkResult);
|
checkResultCache.put(ip.getHostAddress(), new CheckResult(checkResult.response(), checkResult.resultType(), true));
|
||||||
onKick.accept(checkResult);
|
if(checkResult.resultType().isShouldBlock()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this);
|
||||||
|
}
|
||||||
|
onResult.accept(checkResult);
|
||||||
AntiVPN.getInstance().checked++;
|
AntiVPN.getInstance().checked++;
|
||||||
});
|
});
|
||||||
return new CheckResult(null, ResultType.UNKNOWN);
|
onResult.accept(new CheckResult(null, ResultType.UNKNOWN, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.api;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||||
|
|
||||||
|
public record CheckResult(VPNResponse response, ResultType resultType, boolean isFromCache) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.api;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class OfflinePlayer extends APIPlayer {
|
||||||
|
|
||||||
|
public OfflinePlayer(UUID uuid, String name, InetAddress ip) {
|
||||||
|
super(uuid, name, ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void kickPlayer(String reason) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(String permission) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface PlayerExecutor {
|
||||||
|
|
||||||
|
Optional<APIPlayer> getPlayer(String name);
|
||||||
|
|
||||||
|
Optional<APIPlayer> getPlayer(UUID uuid);
|
||||||
|
|
||||||
|
void unloadPlayer(UUID uuid);
|
||||||
|
|
||||||
|
List<APIPlayer> getOnlinePlayers();
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.api;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public enum ResultType {
|
||||||
|
ALLOWED(false),
|
||||||
|
WHITELISTED(false),
|
||||||
|
DENIED_COUNTRY(true),
|
||||||
|
DENIED_PROXY(true),
|
||||||
|
API_FAILURE(false),
|
||||||
|
UNKNOWN(false);
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final boolean shouldBlock;
|
||||||
|
|
||||||
|
ResultType(boolean shouldBlock) {
|
||||||
|
this.shouldBlock = shouldBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
-123
@@ -1,7 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.api;
|
package dev.brighten.antivpn.api;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
import dev.brighten.antivpn.utils.ConfigDefault;
|
import dev.brighten.antivpn.utils.ConfigDefault;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -52,61 +69,60 @@ public class VPNConfig {
|
|||||||
defCountrylist = new ConfigDefault<>(new ArrayList<>(), "countries.list",
|
defCountrylist = new ConfigDefault<>(new ArrayList<>(), "countries.list",
|
||||||
AntiVPN.getInstance());
|
AntiVPN.getInstance());
|
||||||
|
|
||||||
private String license, kickMessage, databaseType, databaseName, mongoURL, username, password, ip, alertMsg,
|
@Getter
|
||||||
countryVanillaKickReason;
|
private String license;
|
||||||
private List<String> prefixWhitelists, commands, countryList, countryKickCommands;
|
@Getter
|
||||||
|
private String kickMessage;
|
||||||
|
@Getter
|
||||||
|
private String databaseType;
|
||||||
|
@Getter
|
||||||
|
private String databaseName;
|
||||||
|
private String mongoURL;
|
||||||
|
@Getter
|
||||||
|
private String username;
|
||||||
|
@Getter
|
||||||
|
private String password;
|
||||||
|
@Getter
|
||||||
|
private String ip;
|
||||||
|
@Getter
|
||||||
|
private String alertMsg;
|
||||||
|
@Getter
|
||||||
|
private String countryVanillaKickReason;
|
||||||
|
@Getter
|
||||||
|
private List<String> prefixWhitelists;
|
||||||
|
private List<String> commands;
|
||||||
|
@Getter
|
||||||
|
private List<String> countryList;
|
||||||
|
private List<String> countryKickCommands;
|
||||||
private int port;
|
private int port;
|
||||||
private boolean cacheResults, databaseEnabled, useCredentials, commandsEnabled, kickPlayers, alertToStaff,
|
private boolean cacheResults;
|
||||||
metrics, whitelistCountries;
|
@Getter
|
||||||
|
private boolean databaseEnabled;
|
||||||
|
private boolean useCredentials;
|
||||||
|
@Getter
|
||||||
|
private boolean commandsEnabled;
|
||||||
|
@Getter
|
||||||
|
private boolean kickPlayers;
|
||||||
|
private boolean alertToStaff;
|
||||||
|
private boolean metrics;
|
||||||
|
private boolean whitelistCountries;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* License from https://funkemunky.cc/shop to be used for more queries.
|
* If true, results will be cached to reduce queries to <a href="https://funkemunky.cc">...</a>
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getLicense() {
|
|
||||||
return license;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, results will be cached to reduce queries to https://funkemunky.cc
|
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public boolean cachedResults() {
|
public boolean cachedResults() {
|
||||||
return cacheResults;
|
return cacheResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Will be used for vanilla kick message when {@link VPNConfig#runCommands()} is true.
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getKickString() {
|
|
||||||
return kickMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message to send staff on proxy detection.
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String alertMessage() {
|
|
||||||
return alertMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, staff will be alerted on proxy detection.
|
* If true, staff will be alerted on proxy detection.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public boolean alertToStaff() {
|
public boolean isAlertToSTaff() {
|
||||||
return alertToStaff;
|
return alertToStaff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, will run {@link VPNConfig#commands()} on detect. If not, it will use vanilla kicking methods.
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public boolean runCommands() {
|
|
||||||
return commandsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commands to run on proxy detection.
|
* Commands to run on proxy detection.
|
||||||
* @return List
|
* @return List
|
||||||
@@ -115,31 +131,6 @@ public class VPNConfig {
|
|||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If false, no commands nor kick will be run on proxy detection.
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public boolean kickPlayersOnDetect() {
|
|
||||||
return kickPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns Strings of which are checked against the beginning of player names. Used to
|
|
||||||
* allow Geyser-connected players to join.
|
|
||||||
* @return List
|
|
||||||
*/
|
|
||||||
public List<String> getPrefixWhitelists() {
|
|
||||||
return prefixWhitelists;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if we want to use a database
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public boolean isDatabaseEnabled() {
|
|
||||||
return databaseEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the database we want to connect to requires credentials.
|
* Whether or not the database we want to connect to requires credentials.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
@@ -156,59 +147,11 @@ public class VPNConfig {
|
|||||||
return mongoURL;
|
return mongoURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Database type. Either MySQL and Mongo.
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getDatabaseType() {
|
|
||||||
return databaseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database name
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getDatabaseName() {
|
|
||||||
return databaseName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database username
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database Password
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database IP
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getIp() {
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of ISO country codes we need to check.
|
|
||||||
* @return List
|
|
||||||
*/
|
|
||||||
public List<String> countryList() {
|
|
||||||
return countryList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist them.
|
* If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist them.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public boolean whitelistCountries() {
|
public boolean getWhitelistCountries() {
|
||||||
return whitelistCountries;
|
return whitelistCountries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,14 +163,6 @@ public class VPNConfig {
|
|||||||
return countryKickCommands;
|
return countryKickCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the vanilla kick reason for bad country locations
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String countryVanillaKickReason() {
|
|
||||||
return countryVanillaKickReason;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port
|
* Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port
|
||||||
* based on {@link VPNConfig#getDatabaseType()} lowerCase().
|
* based on {@link VPNConfig#getDatabaseType()} lowerCase().
|
||||||
@@ -251,7 +186,7 @@ public class VPNConfig {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, https://bstats.org metrics will be collected to improve KauriVPN.
|
* If true, <a href="https://bstats.org">...</a> metrics will be collected to improve KauriVPN.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public boolean metrics() {
|
public boolean metrics() {
|
||||||
@@ -287,4 +222,4 @@ public class VPNConfig {
|
|||||||
countryVanillaKickReason = defaultCountryKickReason.get();
|
countryVanillaKickReason = defaultCountryKickReason.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+83
-40
@@ -1,6 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.api;
|
package dev.brighten.antivpn.api;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
import dev.brighten.antivpn.utils.StringUtil;
|
import dev.brighten.antivpn.utils.StringUtil;
|
||||||
import dev.brighten.antivpn.utils.Tuple;
|
import dev.brighten.antivpn.utils.Tuple;
|
||||||
import dev.brighten.antivpn.utils.json.JSONException;
|
import dev.brighten.antivpn.utils.json.JSONException;
|
||||||
@@ -9,23 +28,20 @@ import dev.brighten.antivpn.web.objects.VPNResponse;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public abstract class VPNExecutor {
|
public abstract class VPNExecutor {
|
||||||
public static ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2);
|
private final ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2);
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final Set<UUID> whitelisted = Collections.synchronizedSet(new HashSet<>());
|
private final Set<UUID> whitelisted = Collections.synchronizedSet(new HashSet<>());
|
||||||
@Getter
|
private final Set<CIDRUtils> whitelistedIps = Collections.synchronizedSet(new HashSet<>());
|
||||||
private final Set<String> whitelistedIps = Collections.synchronizedSet(new HashSet<>());
|
private final Queue<Tuple<CheckResult, UUID>> toKick = new LinkedBlockingQueue<>();
|
||||||
|
private final Queue<APIPlayer> playersToRecheck = new LinkedBlockingQueue<>();
|
||||||
|
private ScheduledFuture<?> kickTask = null;
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final List<Tuple<CheckResult, UUID>> toKick = Collections.synchronizedList(new LinkedList<>());
|
|
||||||
|
|
||||||
public abstract void registerListeners();
|
public abstract void registerListeners();
|
||||||
|
|
||||||
@@ -42,15 +58,13 @@ public abstract class VPNExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void startKickChecks() {
|
public void startKickChecks() {
|
||||||
threadExecutor.scheduleAtFixedRate(() -> {
|
kickTask = threadExecutor.scheduleAtFixedRate(() -> {
|
||||||
synchronized (toKick) {
|
synchronized (toKick) {
|
||||||
if(toKick.isEmpty()) return;
|
if(toKick.isEmpty()) return;
|
||||||
|
|
||||||
Iterator<Tuple<CheckResult, UUID>> i = toKick.iterator();
|
Tuple<CheckResult, UUID> toCheck;
|
||||||
|
|
||||||
while(i.hasNext()) {
|
|
||||||
var toCheck = i.next();
|
|
||||||
|
|
||||||
|
while((toCheck = toKick.poll()) != null) {
|
||||||
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second());
|
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second());
|
||||||
|
|
||||||
if(player.isEmpty()) {
|
if(player.isEmpty()) {
|
||||||
@@ -58,48 +72,62 @@ public abstract class VPNExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleKickingOfPlayer(toCheck.first(), player.get());
|
handleKickingOfPlayer(toCheck.first(), player.get());
|
||||||
|
|
||||||
i.remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 8, 2, TimeUnit.SECONDS);
|
}, 8, 2, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleKickingOfPlayer(CheckResult result, APIPlayer player) {
|
public void handleKickingOfPlayer(CheckResult result, APIPlayer player) {
|
||||||
if (AntiVPN.getInstance().getVpnConfig().alertToStaff()) AntiVPN.getInstance().getPlayerExecutor()
|
|
||||||
|
//Ensuring kick task is always running
|
||||||
|
if(kickTask == null || kickTask.isDone() || kickTask.isCancelled()) {
|
||||||
|
startKickChecks();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AntiVPN.getInstance().getVpnConfig().isAlertToSTaff()) AntiVPN.getInstance().getPlayerExecutor()
|
||||||
.getOnlinePlayers()
|
.getOnlinePlayers()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(APIPlayer::isAlertsEnabled)
|
.filter(APIPlayer::isAlertsEnabled)
|
||||||
.forEach(pl ->
|
.forEach(pl ->
|
||||||
pl.sendMessage(StringUtil.translateAlternateColorCodes('&',
|
pl.sendMessage(StringUtil.translateAlternateColorCodes('&',
|
||||||
StringUtil.varReplace(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig()
|
StringUtil.varReplace(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig()
|
||||||
.alertMessage(), player, result.response()))));
|
.getAlertMsg(), player, result.response()))));
|
||||||
|
|
||||||
if(AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) {
|
if(AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
|
||||||
switch (result.resultType()) {
|
switch (result.resultType()) {
|
||||||
case DENIED_PROXY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
|
case DENIED_PROXY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
|
||||||
.getKickString(), player, result.response()));
|
.getKickMessage(), player, result.response()));
|
||||||
case DENIED_COUNTRY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
|
case DENIED_COUNTRY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
|
||||||
.countryVanillaKickReason(), player, result.response()));
|
.getCountryVanillaKickReason(), player, result.response()));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if(!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!AntiVPN.getInstance().getVpnConfig().runCommands()) return;
|
Runnable runCommands = () -> {
|
||||||
|
switch (result.resultType()) {
|
||||||
|
case DENIED_PROXY -> {
|
||||||
|
for (String command : AntiVPN.getInstance().getVpnConfig().commands()) {
|
||||||
|
runCommand(StringUtil.varReplace(command, player, result.response()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case DENIED_COUNTRY -> {
|
||||||
|
for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) {
|
||||||
|
runCommand(StringUtil.varReplace(command, player, result.response()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
switch (result.resultType()) {
|
// Fixes the commands running too fast and causing messaging errors by any downstream plugins like LiteBans
|
||||||
case DENIED_PROXY -> {
|
var scheduleResult = threadExecutor.schedule(runCommands, 1, TimeUnit.SECONDS);
|
||||||
for (String command : AntiVPN.getInstance().getVpnConfig().commands()) {
|
|
||||||
runCommand(StringUtil.translateAlternateColorCodes('&',
|
if(scheduleResult.isCancelled()) {
|
||||||
StringUtil.varReplace(command, player, result.response())));
|
runCommands.run();
|
||||||
}
|
|
||||||
}
|
|
||||||
case DENIED_COUNTRY -> {
|
|
||||||
for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) {
|
|
||||||
runCommand(StringUtil.translateAlternateColorCodes('&',
|
|
||||||
StringUtil.varReplace(command, player, result.response())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Ensuring players are actually kicked as they are supposed to be.
|
||||||
|
toKick.add(new Tuple<>(result, player.getUuid()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWhitelisted(UUID uuid) {
|
public boolean isWhitelisted(UUID uuid) {
|
||||||
@@ -109,14 +137,29 @@ public abstract class VPNExecutor {
|
|||||||
return whitelisted.contains(uuid);
|
return whitelisted.contains(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWhitelisted(String ip) {
|
public boolean isWhitelisted(String cidr) {
|
||||||
if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
|
if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
|
||||||
return AntiVPN.getInstance().getDatabase().isWhitelisted(ip);
|
return AntiVPN.getInstance().getDatabase().isWhitelisted(cidr);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return whitelistedIps.contains(new CIDRUtils(cidr));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return whitelistedIps.contains(ip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Cache<String, VPNResponse> cachedResponses = Caffeine.newBuilder()
|
||||||
|
.expireAfterWrite(20, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(4000)
|
||||||
|
.build();
|
||||||
|
|
||||||
public CompletableFuture<VPNResponse> checkIp(String ip) {
|
public CompletableFuture<VPNResponse> checkIp(String ip) {
|
||||||
|
VPNResponse cached = cachedResponses.getIfPresent(ip);
|
||||||
|
|
||||||
|
if(cached != null) {
|
||||||
|
return CompletableFuture.completedFuture(cached);
|
||||||
|
}
|
||||||
|
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
Optional<VPNResponse> cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip);
|
Optional<VPNResponse> cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip);
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.command;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class Command {
|
||||||
|
|
||||||
|
public abstract String permission();
|
||||||
|
|
||||||
|
public abstract String name();
|
||||||
|
|
||||||
|
public abstract String[] aliases();
|
||||||
|
|
||||||
|
public abstract String description();
|
||||||
|
|
||||||
|
public abstract String usage();
|
||||||
|
|
||||||
|
public abstract String parent();
|
||||||
|
|
||||||
|
public abstract Command[] children();
|
||||||
|
|
||||||
|
public abstract String execute(CommandExecutor executor, String[] args);
|
||||||
|
|
||||||
|
public abstract List<String> tabComplete(CommandExecutor executor, String alias, String[] args);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.command;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface CommandExecutor {
|
||||||
|
|
||||||
|
void sendMessage(String message, Object... objects);
|
||||||
|
boolean hasPermission(String permission);
|
||||||
|
Optional<APIPlayer> getPlayer();
|
||||||
|
boolean isPlayer();
|
||||||
|
|
||||||
|
}
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.command.impl;
|
package dev.brighten.antivpn.command.impl;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.command.impl;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
|
import dev.brighten.antivpn.command.Command;
|
||||||
|
import dev.brighten.antivpn.command.CommandExecutor;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class AllowlistCommand extends Command {
|
||||||
|
|
||||||
|
private static final String[] secondArgs = new String[] {"add", "remove", "show", "search"};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String permission() {
|
||||||
|
return "antivpn.command.allowlist";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "allowlist";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] aliases() {
|
||||||
|
return new String[] {"whitelist"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Add/remove players to/from exemption list.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String usage() {
|
||||||
|
return "<add <player/uuid/ip> | remove <player/uuid/ip> | show [page] | search <query> [page]>";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String parent() {
|
||||||
|
return "antivpn";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Command[] children() {
|
||||||
|
return new Command[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute(CommandExecutor executor, String[] args) {
|
||||||
|
if(args.length == 0 || Arrays.stream(secondArgs).noneMatch(arg -> arg.equalsIgnoreCase(args[0]))) {
|
||||||
|
return "&cUsage: /antivpn allowlist " + usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(args[0].equalsIgnoreCase("show")) {
|
||||||
|
// args[1] = optional page number (defaults to 1)
|
||||||
|
int page = 1;
|
||||||
|
if (args.length > 1) {
|
||||||
|
try {
|
||||||
|
page = Integer.parseInt(args[1]);
|
||||||
|
if (page < 1) page = 1;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return "&cUsage: /antivpn allowlist show [page]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
|
||||||
|
|
||||||
|
List<UUID> uuids = databaseEnabled
|
||||||
|
? AntiVPN.getInstance().getDatabase().getAllWhitelisted()
|
||||||
|
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted());
|
||||||
|
List<CIDRUtils> ips = databaseEnabled
|
||||||
|
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()
|
||||||
|
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps());
|
||||||
|
|
||||||
|
List<String> entries = new ArrayList<>();
|
||||||
|
for (UUID uuid : uuids) {
|
||||||
|
entries.add("&7- &fUUID: &e" + uuid);
|
||||||
|
}
|
||||||
|
for (CIDRUtils cidr : ips) {
|
||||||
|
entries.add("&7- &fIP: &e" + cidr.getCidr());
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildPage(entries, page, null, "show");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(args[0].equalsIgnoreCase("search")) {
|
||||||
|
// args[1..n-1] = query terms; args[n] = optional page number if last arg is an integer
|
||||||
|
if (args.length < 2) {
|
||||||
|
return "&cUsage: /antivpn allowlist search <query> [page]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect optional trailing page number
|
||||||
|
int page = 1;
|
||||||
|
int queryEnd = args.length;
|
||||||
|
try {
|
||||||
|
int candidate = Integer.parseInt(args[args.length - 1]);
|
||||||
|
if (candidate >= 1 && args.length > 2) {
|
||||||
|
page = candidate;
|
||||||
|
queryEnd = args.length - 1;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
|
||||||
|
String search = String.join(" ", Arrays.copyOfRange(args, 1, queryEnd)).toLowerCase();
|
||||||
|
// Strip color code characters to prevent formatting injection in output
|
||||||
|
String safeSearch = search.replace("&", "");
|
||||||
|
|
||||||
|
if (safeSearch.isEmpty()) {
|
||||||
|
return "&cUsage: /antivpn allowlist search <query> [page]";
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
|
||||||
|
|
||||||
|
List<UUID> uuids = databaseEnabled
|
||||||
|
? AntiVPN.getInstance().getDatabase().getAllWhitelisted()
|
||||||
|
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted());
|
||||||
|
List<CIDRUtils> ips = databaseEnabled
|
||||||
|
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()
|
||||||
|
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps());
|
||||||
|
|
||||||
|
List<String> entries = new ArrayList<>();
|
||||||
|
for (UUID uuid : uuids) {
|
||||||
|
String entry = uuid.toString();
|
||||||
|
if (entry.toLowerCase().contains(search)) {
|
||||||
|
entries.add("&7- &fUUID: &e" + entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (CIDRUtils cidr : ips) {
|
||||||
|
String entry = cidr.getCidr();
|
||||||
|
if (entry.toLowerCase().contains(search)) {
|
||||||
|
entries.add("&7- &fIP: &e" + entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildPage(entries, page, safeSearch, "search " + safeSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(args.length == 1)
|
||||||
|
return "&cYou have to provide a player to allow or deny exemption.";
|
||||||
|
|
||||||
|
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
|
||||||
|
|
||||||
|
if(!databaseEnabled) executor.sendMessage("&cThe database is currently not setup, " +
|
||||||
|
"so any changes here will disappear after a restart.");
|
||||||
|
|
||||||
|
CIDRUtils cidrUtils;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cidrUtils = new CIDRUtils(args[1]);
|
||||||
|
} catch(IllegalArgumentException | UnknownHostException e) {
|
||||||
|
cidrUtils = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cidrUtils != null) {
|
||||||
|
if(!databaseEnabled) {
|
||||||
|
return switch (args[0].toLowerCase()) {
|
||||||
|
case "add", "insert" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
|
||||||
|
yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr());
|
||||||
|
}
|
||||||
|
case "remove", "delete" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
|
||||||
|
yield String.format("&cRemoved &%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
|
||||||
|
}
|
||||||
|
default -> "&c\"" + args[0] + "\" is not a valid argument";
|
||||||
|
};
|
||||||
|
} else return switch (args[0].toLowerCase()) {
|
||||||
|
case "add", "insert" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
|
||||||
|
AntiVPN.getInstance().getDatabase().addWhitelist(cidrUtils);
|
||||||
|
yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr());
|
||||||
|
}
|
||||||
|
case "remove", "delete" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
|
||||||
|
AntiVPN.getInstance().getDatabase().removeWhitelist(cidrUtils);
|
||||||
|
yield String.format("&cRemoved &6%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
|
||||||
|
}
|
||||||
|
default -> "&c\"" + args[0] + "\" is not a valid argument";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if(MiscUtils.isIpv4(args[1])) {
|
||||||
|
if(!databaseEnabled) {
|
||||||
|
try {
|
||||||
|
return switch(args[0].toLowerCase()) {
|
||||||
|
case "add", "insert" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(new CIDRUtils(args[1] + "/32"));
|
||||||
|
AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32"));
|
||||||
|
yield String.format("&aAdded &6%s &ato the exemption allowlist.", args[1] + "/32");
|
||||||
|
}
|
||||||
|
case "remove", "delete" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(new CIDRUtils(args[1] + "/32"));
|
||||||
|
AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32"));
|
||||||
|
yield String.format("&cRemoved &6%s &cfrom the exemption allowlist.", args[1] + "/32");
|
||||||
|
}
|
||||||
|
default -> "&c\"" + args[0] + "\" is not a valid argument";
|
||||||
|
};
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e);
|
||||||
|
return "&cInvalid IP format for allowlist command";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return switch (args[0].toLowerCase()) {
|
||||||
|
case "add", "insert" -> {
|
||||||
|
AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32"));
|
||||||
|
yield String.format("&aAdded &6%s &a to the exemption allowlist.", args[1] + "/32");
|
||||||
|
}
|
||||||
|
case "remove", "delete" -> {
|
||||||
|
AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32"));
|
||||||
|
yield String.format("&cRemoved &6%s &c from the exemption allowlist.", args[1] + "/32");
|
||||||
|
}
|
||||||
|
default -> "&c\"" + args[0] + "\" is not a valid argument";
|
||||||
|
};
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e);
|
||||||
|
return "&cInvalid IP format for allowlist command";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UUID uuid;
|
||||||
|
try {
|
||||||
|
uuid = UUID.fromString(args[1]);
|
||||||
|
} catch(IllegalArgumentException e) {
|
||||||
|
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[1]);
|
||||||
|
if (player.isPresent()) {
|
||||||
|
uuid = player.get().getUuid();
|
||||||
|
} else {
|
||||||
|
uuid = MiscUtils.lookupUUID(args[1]);
|
||||||
|
if (uuid == null) {
|
||||||
|
return "&cCould not find a UUID for \"" + args[1] + "\". They might not have provided a valid username.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!databaseEnabled) {
|
||||||
|
return switch (args[0].toLowerCase()) {
|
||||||
|
case "add" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
|
||||||
|
yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString());
|
||||||
|
}
|
||||||
|
case "remove", "delete" -> {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
|
||||||
|
yield String.format("&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString());
|
||||||
|
}
|
||||||
|
default -> "&c\"" + args[0] + "\" is not a valid argument";
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return switch (args[0].toLowerCase()) {
|
||||||
|
case "add" -> {
|
||||||
|
AntiVPN.getInstance().getDatabase().addWhitelist(uuid);
|
||||||
|
yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString());
|
||||||
|
}
|
||||||
|
case "remove", "delete" -> {
|
||||||
|
AntiVPN.getInstance().getDatabase().removeWhitelist(uuid);
|
||||||
|
yield String.format("&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString());
|
||||||
|
}
|
||||||
|
default -> "&c\"" + args[0] + "\" is not a valid argument";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
|
||||||
|
return switch (args.length) {
|
||||||
|
case 1 -> Arrays.stream(secondArgs)
|
||||||
|
.filter(narg -> narg.toLowerCase().startsWith(args[0].toLowerCase()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
case 2 -> {
|
||||||
|
if (args[0].equalsIgnoreCase("show") || args[0].equalsIgnoreCase("search")) {
|
||||||
|
yield Collections.emptyList();
|
||||||
|
}
|
||||||
|
yield AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream()
|
||||||
|
.map(APIPlayer::getName)
|
||||||
|
.filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
default -> Collections.emptyList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPage(List<String> entries, int page, String safeSearch, String subcommandPrefix) {
|
||||||
|
int pageSize = 10;
|
||||||
|
int totalPages = Math.max(1, (entries.size() + pageSize - 1) / pageSize);
|
||||||
|
if (page > totalPages) page = totalPages;
|
||||||
|
|
||||||
|
List<String> messages = new ArrayList<>();
|
||||||
|
messages.add("&8&m-----------------------------------------------------");
|
||||||
|
messages.add("&6&lAllowlist Entries &8(&7Page &f" + page + "&7/&f" + totalPages + "&8)"
|
||||||
|
+ (safeSearch != null ? " &7(search: &f" + safeSearch + "&7)" : ""));
|
||||||
|
messages.add("");
|
||||||
|
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
messages.add(safeSearch != null
|
||||||
|
? "&cNo allowlist entries matching &f\"" + safeSearch + "&c\" were found."
|
||||||
|
: "&cThe allowlist is empty.");
|
||||||
|
} else {
|
||||||
|
int start = (page - 1) * pageSize;
|
||||||
|
int end = Math.min(start + pageSize, entries.size());
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
messages.add(entries.get(i));
|
||||||
|
}
|
||||||
|
if (totalPages > 1) {
|
||||||
|
messages.add("");
|
||||||
|
if (page > 1) {
|
||||||
|
messages.add("&7Previous page: &f/antivpn allowlist " + subcommandPrefix + " " + (page - 1));
|
||||||
|
}
|
||||||
|
if (page < totalPages) {
|
||||||
|
messages.add("&7Next page: &f/antivpn allowlist " + subcommandPrefix + " " + (page + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages.add("&8&m-----------------------------------------------------");
|
||||||
|
return String.join("\n", messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.command.impl;
|
package dev.brighten.antivpn.command.impl;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
+18
-1
@@ -1,6 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.command.impl;
|
package dev.brighten.antivpn.command.impl;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.api.VPNExecutor;
|
||||||
import dev.brighten.antivpn.command.Command;
|
import dev.brighten.antivpn.command.Command;
|
||||||
import dev.brighten.antivpn.command.CommandExecutor;
|
import dev.brighten.antivpn.command.CommandExecutor;
|
||||||
|
|
||||||
@@ -45,7 +62,7 @@ public class ClearCacheCommand extends Command {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String execute(CommandExecutor executor, String[] args) {
|
public String execute(CommandExecutor executor, String[] args) {
|
||||||
AntiVPN.getInstance().getDatabase().clearResponses();
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> AntiVPN.getInstance().getDatabase().clearResponses());
|
||||||
return "&aCleared all cached API response information!";
|
return "&aCleared all cached API response information!";
|
||||||
}
|
}
|
||||||
|
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.command.impl;
|
package dev.brighten.antivpn.command.impl;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
+17
-1
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.command.impl;
|
package dev.brighten.antivpn.command.impl;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
@@ -51,7 +67,7 @@ public class PlanCommand extends Command {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String execute(CommandExecutor executor, String[] args) {
|
public String execute(CommandExecutor executor, String[] args) {
|
||||||
VPNExecutor.threadExecutor.execute(() -> {
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
|
||||||
QueryResponse result;
|
QueryResponse result;
|
||||||
try {
|
try {
|
||||||
if(AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) {
|
if(AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) {
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.command.impl;
|
package dev.brighten.antivpn.command.impl;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database;
|
||||||
|
|
||||||
|
public class DatabaseException extends RuntimeException {
|
||||||
|
public DatabaseException(String message, Throwable e) {
|
||||||
|
super(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public interface VPNDatabase {
|
||||||
|
Optional<VPNResponse> getStoredResponse(String ip);
|
||||||
|
|
||||||
|
void cacheResponse(VPNResponse toCache);
|
||||||
|
|
||||||
|
void deleteResponse(String ip);
|
||||||
|
|
||||||
|
boolean isWhitelisted(UUID uuid);
|
||||||
|
|
||||||
|
boolean isWhitelisted(String cidr);
|
||||||
|
|
||||||
|
boolean isWhitelisted(CIDRUtils cidr);
|
||||||
|
|
||||||
|
void addWhitelist(UUID uuid);
|
||||||
|
|
||||||
|
void removeWhitelist(UUID uuid);
|
||||||
|
|
||||||
|
void addWhitelist(CIDRUtils cidr);
|
||||||
|
|
||||||
|
void removeWhitelist(CIDRUtils cidr);
|
||||||
|
|
||||||
|
List<UUID> getAllWhitelisted();
|
||||||
|
|
||||||
|
List<CIDRUtils> getAllWhitelistedIps();
|
||||||
|
|
||||||
|
void alertsState(UUID uuid, Consumer<Boolean> result);
|
||||||
|
|
||||||
|
void updateAlertsState(UUID uuid, boolean state);
|
||||||
|
|
||||||
|
void clearResponses();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void shutdown();
|
||||||
|
}
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.local;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.VPNDatabase;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.ExecutableStatement;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.MySQL;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.Query;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class H2VPN implements VPNDatabase {
|
||||||
|
|
||||||
|
|
||||||
|
public H2VPN() {
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
|
||||||
|
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
|
||||||
|
|
||||||
|
//Refreshing whitelisted players
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().clear();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted()
|
||||||
|
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
|
||||||
|
|
||||||
|
//Refreshing whitlisted IPs
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps()
|
||||||
|
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
|
||||||
|
}, 2, 30, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<VPNResponse> getStoredResponse(String ip) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()|| MySQL.isClosed())
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
try(ExecutableStatement statement = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) {
|
||||||
|
try(ResultSet rs = statement.executeQuery()) {
|
||||||
|
if (rs != null && rs.next()) {
|
||||||
|
return Optional.of(new VPNResponse(rs.getString("asn"), rs.getString("ip"),
|
||||||
|
rs.getString("countryName"), rs.getString("countryCode"),
|
||||||
|
rs.getString("city"), rs.getString("timeZone"),
|
||||||
|
rs.getString("method"), rs.getString("isp"), "N/A",
|
||||||
|
rs.getBoolean("proxy"), rs.getBoolean("cached"), true,
|
||||||
|
rs.getDouble("latitude"), rs.getDouble("longitude"),
|
||||||
|
rs.getTimestamp("inserted").getTime(), -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("There was a problem getting a response for "
|
||||||
|
+ ip, e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Query.
|
||||||
|
* prepare("create table if not exists `responses` (`ip` varchar(45) not null, "
|
||||||
|
* +
|
||||||
|
* "`countryName` varchar(64), `countryCode` varchar(10), `city` varchar(64), `timeZone` varchar(64), "
|
||||||
|
* +
|
||||||
|
* "`method` varchar(32), `isp` varchar(32), `proxy` boolean, `cached` boolean "
|
||||||
|
* + "`latitude` double, `longitude` double)");
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void cacheResponse(VPNResponse toCache) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
|
||||||
|
+ "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
|
||||||
|
.append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName())
|
||||||
|
.append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone())
|
||||||
|
.append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy())
|
||||||
|
.append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis()))
|
||||||
|
.append(toCache.getLatitude()).append(toCache.getLongitude())) {
|
||||||
|
statement.execute();
|
||||||
|
} catch(SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not cache response for IP: " + toCache.getIp(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteResponse(String ip) {
|
||||||
|
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("delete from `responses` where `ip` = ?").append(ip)) {
|
||||||
|
statement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not delete response from IP: " + ip, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted(UUID uuid) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1")
|
||||||
|
.append(uuid.toString())) {
|
||||||
|
try(var set = statement.executeQuery()) {
|
||||||
|
return set != null && set.next() && set.getString("uuid") != null;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted(String cidr) {
|
||||||
|
return isWhitelisted(new CIDRUtils(cidr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted(CIDRUtils cidr) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BigInteger start = cidr.getStartIpInt();
|
||||||
|
BigInteger end = cidr.getEndIpInt();
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("SELECT * FROM `whitelisted-ranges` WHERE ip_start <= ? AND ip_end >= ?")
|
||||||
|
.append(start).append(end)) {
|
||||||
|
|
||||||
|
try(var result = statement.executeQuery()) {
|
||||||
|
return result.next();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for cidr '" + cidr + "' due to SQL error.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addWhitelist(UUID uuid) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("insert into `whitelisted` (`uuid`) values (?)").append(uuid.toString())) {
|
||||||
|
statement.execute();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not add uuid '" + uuid + "' to whitelist due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWhitelist(UUID uuid) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return;
|
||||||
|
try(var statement = Query.prepare("delete from `whitelisted` where `uuid` = ?").append(uuid.toString())) {
|
||||||
|
statement.execute();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not remove uuid '" + uuid + "' from whitelist due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addWhitelist(CIDRUtils cidr) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
|
||||||
|
.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) {
|
||||||
|
statement.execute();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWhitelist(CIDRUtils cidr) {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("delete from `whitelisted-ranges` where `cidr_string` = ?").append(cidr.getCidr())) {
|
||||||
|
statement.execute();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not remove cidr '" + cidr + "' from whitelist due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UUID> getAllWhitelisted() {
|
||||||
|
List<UUID> uuids = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return uuids;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("select uuid from `whitelisted`")) {
|
||||||
|
statement.execute(set -> uuids.add(UUID.fromString(set.getString("uuid"))));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted players due to SQL error.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CIDRUtils> getAllWhitelistedIps() {
|
||||||
|
List<CIDRUtils> ips = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|
||||||
|
return ips;
|
||||||
|
try(var statement = Query.prepare("select `cidr_string`, `ip_start`, `ip_end` from `whitelisted-ranges`")) {
|
||||||
|
statement.execute(set -> {
|
||||||
|
try {
|
||||||
|
String cidrString = set.getString("cidr_string");
|
||||||
|
|
||||||
|
ips.add(new CIDRUtils(cidrString));
|
||||||
|
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor()
|
||||||
|
.logException("Could not format ip "
|
||||||
|
+ set.getString("cidr_string") + " into a CIDR!", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ips due to SQL error.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void alertsState(UUID uuid, Consumer<Boolean> result) {
|
||||||
|
if(MySQL.isClosed()) return;
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("select * from `alerts` where `uuid` = ? limit 1")
|
||||||
|
.append(uuid.toString())) {
|
||||||
|
try(var set = statement.executeQuery()) {
|
||||||
|
result.accept(set != null && set.next() && set.getString("uuid") != null);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("There was a problem getting alerts state for " + uuid, e);
|
||||||
|
result.accept(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAlertsState(UUID uuid, boolean enabled) {
|
||||||
|
if(MySQL.isClosed()) return;
|
||||||
|
|
||||||
|
if(enabled) {
|
||||||
|
//We want to make sure there isn't already a uuid inserted to prevent double insertions
|
||||||
|
alertsState(uuid, alreadyEnabled -> { //No need to make another thread execute, already async
|
||||||
|
if(!alreadyEnabled) {
|
||||||
|
try(var statement = Query.prepare("insert into `alerts` (`uuid`) values (?)")
|
||||||
|
.append(uuid.toString())) {
|
||||||
|
statement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor()
|
||||||
|
.logException("There was a problem updating alerts state for " + uuid, e);
|
||||||
|
}
|
||||||
|
} //No need to insert again of already enabled
|
||||||
|
});
|
||||||
|
//Removing any uuid from the alerts table will disable alerts globally.
|
||||||
|
} else {
|
||||||
|
try(var statement = Query.prepare("delete from `alerts` where `uuid` = ?").append(uuid.toString())) {
|
||||||
|
statement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("There was a problem updating alerts state for "
|
||||||
|
+ uuid, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearResponses() {
|
||||||
|
if(MySQL.isClosed()) return;
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("delete from `responses`")) {
|
||||||
|
statement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("There was a problem clearing responses.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled())
|
||||||
|
return;
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Initializing H2...");
|
||||||
|
MySQL.initH2();
|
||||||
|
try {
|
||||||
|
for (Version<H2VPN> version : Version.h2Versions) {
|
||||||
|
if(version.needsUpdate(this)) {
|
||||||
|
version.update(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not complete version setup due to SQL error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Creating tables...");
|
||||||
|
|
||||||
|
//Running check for old table types to update
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
MySQL.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void backupDatabase() {
|
||||||
|
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
|
||||||
|
|
||||||
|
if(!dataFolder.exists() || MySQL.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var connection = Query.getConn();
|
||||||
|
if (connection == null || connection.getMetaData() == null
|
||||||
|
|| !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not verify database type before H2 backup.", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File backupDir = new File(dataFolder, "backups");
|
||||||
|
if (!backupDir.exists() && !backupDir.mkdirs()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File backupFile = new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip");
|
||||||
|
String backupPath = backupFile.getAbsolutePath()
|
||||||
|
.replace("\\", "/")
|
||||||
|
.replace("'", "''");
|
||||||
|
|
||||||
|
try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) {
|
||||||
|
statement.execute();
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not create H2 backup before migration.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.local.version;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.VPNDatabase;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.ExecutableStatement;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.Query;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
|
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class First implements Version<VPNDatabase> {
|
||||||
|
|
||||||
|
private final List<AutoCloseable> toClose = new ArrayList<>();
|
||||||
|
@Override
|
||||||
|
public void update(VPNDatabase database) throws DatabaseException {
|
||||||
|
try {
|
||||||
|
closeOnEnd(Query.prepare("create table if not exists `whitelisted` (`uuid` varchar(36) not null)"))
|
||||||
|
.execute();
|
||||||
|
closeOnEnd(Query.prepare("create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)"))
|
||||||
|
.execute();
|
||||||
|
closeOnEnd(Query
|
||||||
|
.prepare("create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12),"
|
||||||
|
+ "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), "
|
||||||
|
+ "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp,"
|
||||||
|
+ "`latitude` double, `longitude` double)")).execute();
|
||||||
|
closeOnEnd(Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)"))
|
||||||
|
.execute();
|
||||||
|
closeOnEnd(Query.prepare("create table if not exists `database_version` (`version` int)")).execute();
|
||||||
|
closeOnEnd(Query.prepare("insert into `database_version` (`version`) values (?)")
|
||||||
|
.append(versionNumber())).execute();
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Creating indexes...");
|
||||||
|
createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`");
|
||||||
|
createIndexIfAbsent("responses", "responses_ip_1", "`ip`");
|
||||||
|
createIndexIfAbsent("responses", "responses_proxy_1", "`proxy`");
|
||||||
|
createIndexIfAbsent("responses", "responses_inserted_1", "`inserted`");
|
||||||
|
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException("Failed to update database", e);
|
||||||
|
} finally {
|
||||||
|
MiscUtils.close(toClose.toArray(AutoCloseable[]::new));
|
||||||
|
toClose.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutableStatement closeOnEnd(ExecutableStatement statement) {
|
||||||
|
toClose.add(statement);
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createIndexIfAbsent(String tableName, String indexName, String columnList) throws SQLException {
|
||||||
|
if (hasIndex(tableName, indexName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeOnEnd(Query.prepare(String.format(
|
||||||
|
"create index `%s` on `%s` (%s)",
|
||||||
|
indexName,
|
||||||
|
tableName,
|
||||||
|
columnList
|
||||||
|
))).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException {
|
||||||
|
if (!hasIndex(tableName, indexName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeOnEnd(Query.prepare(String.format(
|
||||||
|
"drop index `%s` on `%s`",
|
||||||
|
indexName,
|
||||||
|
tableName
|
||||||
|
))).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasIndex(String tableName, String indexName) throws SQLException {
|
||||||
|
DatabaseMetaData metaData = Query.getConn().getMetaData();
|
||||||
|
|
||||||
|
try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) {
|
||||||
|
while (indexes.next()) {
|
||||||
|
String existingIndexName = indexes.getString("INDEX_NAME");
|
||||||
|
|
||||||
|
if (existingIndexName != null && existingIndexName.equalsIgnoreCase(indexName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int versionNumber() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsUpdate(VPNDatabase database) {
|
||||||
|
try(var statement = Query.prepare("select * from `database_version` where version = 0")) {
|
||||||
|
try(ResultSet set = statement.executeQuery()) {
|
||||||
|
return !set.next();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.local.version;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.VPNDatabase;
|
||||||
|
import dev.brighten.antivpn.database.local.H2VPN;
|
||||||
|
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.ExecutableStatement;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.Query;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Second extends First implements Version<VPNDatabase> {
|
||||||
|
private final List<AutoCloseable> toClose = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(VPNDatabase database) throws DatabaseException {
|
||||||
|
if(database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) {
|
||||||
|
h2VPN.backupDatabase();
|
||||||
|
}
|
||||||
|
List<String> whitelistedIps = new ArrayList<>();
|
||||||
|
|
||||||
|
try (var statement = Query.prepare("SELECT * FROM `whitelisted-ips`")) {
|
||||||
|
try(var set = statement.executeQuery()) {
|
||||||
|
while (set.next()) {
|
||||||
|
whitelistedIps.add(set.getString("ip"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException("Could not get whitelisted ips from database!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
closeOnEnd(Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ranges` " +
|
||||||
|
"(id INT AUTO_INCREMENT PRIMARY KEY, " +
|
||||||
|
"cidr_string VARCHAR(45), " +
|
||||||
|
"ip_start BIGINT NOT NULL, " +
|
||||||
|
"ip_end BIGINT NOT NULL)"))
|
||||||
|
.execute();
|
||||||
|
createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end");
|
||||||
|
|
||||||
|
var cidrs = whitelistedIps.stream().map(ip -> {
|
||||||
|
try {
|
||||||
|
return new CIDRUtils(ip + "/32");
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException("Could not format ip " + ip + " into a CIDR!", e);
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
var insertStatement = Query.prepare("INSERT INTO `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) VALUES (?, ?, ?)");
|
||||||
|
for (CIDRUtils cidr : cidrs) {
|
||||||
|
insertStatement = insertStatement
|
||||||
|
.append(cidr.toString())
|
||||||
|
.append(cidr.getStartIpInt())
|
||||||
|
.append(cidr.getEndIpInt())
|
||||||
|
.addBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] updateCounts = insertStatement.executeBatch();
|
||||||
|
|
||||||
|
for (int updateCount : updateCounts) {
|
||||||
|
if(updateCount == 0) {
|
||||||
|
throw new RuntimeException("Could not insert a CIDR from previous whitelisted lists, attempted to restore previous database!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropIndexIfPresent("whitelisted-ips", "ip_1");
|
||||||
|
dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1");
|
||||||
|
closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute();
|
||||||
|
closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Failed to update database to version 1: " + e.getMessage());
|
||||||
|
try {
|
||||||
|
rollback(whitelistedIps);
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
throw new DatabaseException("Failed to rollback database!", e);
|
||||||
|
}
|
||||||
|
throw new DatabaseException("Failed to update to version one, rolling back database!", e);
|
||||||
|
} finally {
|
||||||
|
MiscUtils.close(toClose.toArray(AutoCloseable[]::new));
|
||||||
|
toClose.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutableStatement closeOnEnd(ExecutableStatement statement) {
|
||||||
|
toClose.add(statement);
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rollback(List<String> ipAddresses) throws SQLException {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
|
||||||
|
dropIndexIfPresent("whitelisted-ranges", "idx_ip_range");
|
||||||
|
try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) {
|
||||||
|
statement.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("DELETE FROM `database_version` WHERE version = ?").append(versionNumber())) {
|
||||||
|
statement.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ips` (`ip` VARCHAR(45) NOT NULL)")) {
|
||||||
|
statement.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) {
|
||||||
|
statement.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
try(var statement = Query.prepare("INSERT INTO `whitelisted-ips` (`ip`) VALUES (?)")) {
|
||||||
|
for (String ip : ipAddresses) {
|
||||||
|
statement.append(ip);
|
||||||
|
statement.addBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.executeBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int versionNumber() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsUpdate(VPNDatabase database) {
|
||||||
|
try (var statement = Query.prepare("select * from `database_version` where version = 1")) {
|
||||||
|
try(var set = statement.executeQuery()) {
|
||||||
|
return !set.next();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.local.version;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.VPNDatabase;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.Query;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class Third implements Version<VPNDatabase> {
|
||||||
|
@Override
|
||||||
|
public void update(VPNDatabase database) throws DatabaseException {
|
||||||
|
List<CIDRUtils> ipRanges = new ArrayList<>();
|
||||||
|
List<CIDRUtils> rangesToInsert = new ArrayList<>();
|
||||||
|
List<BigInteger[]> rangesToRemove = new ArrayList<>();
|
||||||
|
try (var preparedQuery = Query.prepare("select ip_start, ip_end from `whitelisted-ranges`")) {
|
||||||
|
preparedQuery.execute(set -> {
|
||||||
|
BigInteger start = set.getBigDecimal("ip_start").toBigInteger();
|
||||||
|
BigInteger end = set.getBigDecimal("ip_end").toBigInteger();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var range = MiscUtils.rangeToCidrs(start, end);
|
||||||
|
|
||||||
|
if(range.size() > 1) {
|
||||||
|
rangesToRemove.add(new BigInteger[]{start, end});
|
||||||
|
rangesToInsert.addAll(range);
|
||||||
|
AntiVPN.getInstance().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end);
|
||||||
|
} else ipRanges.addAll(range);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(
|
||||||
|
String.format("Could not convert ip range to CIDR! %s, %s", start, end), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ranges due to SQL error.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size());
|
||||||
|
|
||||||
|
for (CIDRUtils cidr : rangesToInsert) {
|
||||||
|
try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
|
||||||
|
.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) {
|
||||||
|
statement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size());
|
||||||
|
|
||||||
|
for (BigInteger[] range : rangesToRemove) {
|
||||||
|
try(var statement = Query.prepare("delete from `whitelisted-ranges` where `ip_start` = ? and `ip_end` = ?")) {
|
||||||
|
statement.append(range[0]).append(range[1]).execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not remove cidr range '" + range[0] + ", " + range[1] + "' from whitelist due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Updating %s ranges to proper CIDR notation with the database", ipRanges.size());
|
||||||
|
|
||||||
|
for (CIDRUtils cidr : ipRanges) {
|
||||||
|
try(var statement = Query.prepare("update `whitelisted-ranges` set `cidr_string` = ? where `ip_start` = ? and `ip_end` = ?")) {
|
||||||
|
statement.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt()).execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not update cidr '" + cidr + "' to proper CIDR notation in whitelist due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var preparedStatement = Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())) {
|
||||||
|
preparedStatement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not update database version to 2 due to SQL error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int versionNumber() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsUpdate(VPNDatabase database) {
|
||||||
|
try (var statement = Query.prepare("select * from `database_version` where version = 2")) {
|
||||||
|
try(var set = statement.executeQuery()) {
|
||||||
|
return !set.next();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.mongo;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import com.mongodb.*;
|
||||||
|
import com.mongodb.client.MongoClient;
|
||||||
|
import com.mongodb.client.MongoClients;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import com.mongodb.client.model.UpdateOptions;
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.VPNDatabase;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
import org.bson.types.Decimal128;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class MongoVPN implements VPNDatabase {
|
||||||
|
|
||||||
|
public MongoCollection<Document> settingsDocument;
|
||||||
|
MongoCollection<Document> cacheDocument;
|
||||||
|
private MongoClient client;
|
||||||
|
public MongoDatabase antivpnDatabase;
|
||||||
|
|
||||||
|
public MongoVPN() {
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
|
||||||
|
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
|
||||||
|
|
||||||
|
//Refreshing whitelisted players
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().clear();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted()
|
||||||
|
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
|
||||||
|
|
||||||
|
//Refreshing whitlisted IPs
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps()
|
||||||
|
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
|
||||||
|
}, 2, 30, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Optional<VPNResponse> getStoredResponse(String ip) {
|
||||||
|
Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first();
|
||||||
|
|
||||||
|
if(rdoc != null) {
|
||||||
|
long lastUpdate = rdoc.get("lastAccess", 0L);
|
||||||
|
|
||||||
|
if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) {
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip)
|
||||||
|
.countryName(rdoc.getString("countryName"))
|
||||||
|
.countryCode(rdoc.getString("countryCode"))
|
||||||
|
.city(rdoc.getString("city"))
|
||||||
|
.isp(rdoc.getString("isp"))
|
||||||
|
.method(rdoc.getString("method"))
|
||||||
|
.timeZone(rdoc.getString("timeZone"))
|
||||||
|
.proxy(rdoc.getBoolean("proxy"))
|
||||||
|
.cached(rdoc.getBoolean("cached"))
|
||||||
|
.success(true)
|
||||||
|
.latitude(rdoc.getDouble("latitude"))
|
||||||
|
.longitude(rdoc.getDouble("longitude"))
|
||||||
|
.lastAccess(rdoc.get("lastAccess", 0L))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cacheResponse(VPNResponse toCache) {
|
||||||
|
if(AntiVPN.getInstance().getVpnConfig().cachedResults()) {
|
||||||
|
Document rdoc = new Document("ip", toCache.getIp());
|
||||||
|
|
||||||
|
rdoc.put("asn", toCache.getAsn());
|
||||||
|
rdoc.put("countryName", toCache.getCountryName());
|
||||||
|
rdoc.put("countryCode", toCache.getCountryCode());
|
||||||
|
rdoc.put("city", toCache.getCity());
|
||||||
|
rdoc.put("isp", toCache.getIsp());
|
||||||
|
rdoc.put("method", toCache.getMethod());
|
||||||
|
rdoc.put("timeZone", toCache.getTimeZone());
|
||||||
|
rdoc.put("proxy", toCache.isProxy());
|
||||||
|
rdoc.put("cached", toCache.isCached());
|
||||||
|
rdoc.put("success", toCache.isSuccess());
|
||||||
|
rdoc.put("latitude", toCache.getLatitude());
|
||||||
|
rdoc.put("longitude", toCache.getLongitude());
|
||||||
|
rdoc.put("lastAccess", System.currentTimeMillis());
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
|
||||||
|
Bson update = new Document("$set", rdoc);
|
||||||
|
cacheDocument.updateOne(Filters.eq("ip", toCache.getIp()), update,
|
||||||
|
new UpdateOptions().upsert(true));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteResponse(String ip) {
|
||||||
|
cacheDocument.deleteMany(Filters.eq("ip", ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted(UUID uuid) {
|
||||||
|
return settingsDocument
|
||||||
|
.find(Filters.and(Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.eq("uuid", uuid.toString()))).first() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted(String cidr) {
|
||||||
|
try {
|
||||||
|
return isWhitelisted(new CIDRUtils(cidr));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Failed to check whitelist for IP: " + cidr, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted(CIDRUtils cidr) {
|
||||||
|
var start = new Decimal128(new BigDecimal(cidr.getStartIpInt()));
|
||||||
|
var end = new Decimal128(new BigDecimal(cidr.getEndIpInt()));
|
||||||
|
return settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.lte("ip_start", start), Filters.gte("ip_end", end))).first() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addWhitelist(UUID uuid) {
|
||||||
|
Document wdoc = new Document("setting", "whitelist");
|
||||||
|
wdoc.put("uuid", uuid.toString());
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
|
||||||
|
settingsDocument.insertOne(wdoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWhitelist(UUID uuid) {
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
|
||||||
|
settingsDocument.deleteMany(Filters
|
||||||
|
.and(
|
||||||
|
Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.eq("uuid", uuid.toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addWhitelist(CIDRUtils cidr) {
|
||||||
|
Document doc = new Document("setting", "whitelist");
|
||||||
|
doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt())));
|
||||||
|
doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())));
|
||||||
|
doc.append("cidr_string", cidr.getCidr());
|
||||||
|
|
||||||
|
settingsDocument.insertOne(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWhitelist(CIDRUtils cidr) {
|
||||||
|
settingsDocument.deleteMany(Filters
|
||||||
|
.and(
|
||||||
|
Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))),
|
||||||
|
Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UUID> getAllWhitelisted() {
|
||||||
|
List<UUID> uuids = new ArrayList<>();
|
||||||
|
settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.exists("uuid")))
|
||||||
|
.forEach((Consumer<? super Document>) doc -> uuids.add(UUID.fromString(doc.getString("uuid"))));
|
||||||
|
return uuids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CIDRUtils> getAllWhitelistedIps() {
|
||||||
|
List<CIDRUtils> ips = new ArrayList<>();
|
||||||
|
settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.exists("cidr_string"))).forEach((Consumer<? super Document>) doc -> {
|
||||||
|
try {
|
||||||
|
var cidr = new CIDRUtils(doc.getString("cidr_string"));
|
||||||
|
ips.add(cidr);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Could not format ip " + doc.getString("cidr_string") + " into a CIDR!", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void alertsState(UUID uuid, Consumer<Boolean> result) {
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> result.accept(settingsDocument
|
||||||
|
.find(Filters.and(Filters.eq("setting", "alerts"),
|
||||||
|
Filters.eq("uuid", uuid.toString()))).first() != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAlertsState(UUID uuid, boolean state) {
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
|
||||||
|
settingsDocument.deleteMany(Filters.and(Filters.eq("setting", "alerts"),
|
||||||
|
Filters.eq("uuid", uuid.toString())));
|
||||||
|
if(state) {
|
||||||
|
Document adoc = new Document("setting", "alerts");
|
||||||
|
|
||||||
|
adoc.put("uuid", uuid.toString());
|
||||||
|
settingsDocument.insertOne(adoc);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearResponses() {
|
||||||
|
cacheDocument.deleteMany(Filters.exists("ip"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
if(!AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL().isEmpty()) { //URL
|
||||||
|
ConnectionString cs = new ConnectionString(AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL());
|
||||||
|
MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(cs).build();
|
||||||
|
client = MongoClients.create(settings);
|
||||||
|
} else {
|
||||||
|
MongoClientSettings.Builder settingsBld = MongoClientSettings.builder().readPreference(ReadPreference.nearest())
|
||||||
|
.applyToClusterSettings(builder -> builder.
|
||||||
|
hosts(Collections.singletonList(
|
||||||
|
new ServerAddress(
|
||||||
|
AntiVPN.getInstance().getVpnConfig().getIp(),
|
||||||
|
AntiVPN.getInstance().getVpnConfig().getPort())
|
||||||
|
)));
|
||||||
|
if(AntiVPN.getInstance().getVpnConfig().useDatabaseCreds()) {
|
||||||
|
settingsBld.credential(MongoCredential
|
||||||
|
.createCredential(AntiVPN.getInstance().getVpnConfig().getUsername(),
|
||||||
|
AntiVPN.getInstance().getVpnConfig().getDatabaseName(),
|
||||||
|
AntiVPN.getInstance().getVpnConfig().getPassword().toCharArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
client = MongoClients.create(settingsBld.build());
|
||||||
|
}
|
||||||
|
antivpnDatabase = client.getDatabase(AntiVPN.getInstance().getVpnConfig().getDatabaseName());
|
||||||
|
|
||||||
|
settingsDocument = antivpnDatabase.getCollection("settings");
|
||||||
|
|
||||||
|
cacheDocument = antivpnDatabase.getCollection("cache");
|
||||||
|
|
||||||
|
for (Version<MongoVPN> mongoDbVersion : Version.mongoDbVersions) {
|
||||||
|
if(mongoDbVersion.needsUpdate(this)) {
|
||||||
|
mongoDbVersion.update(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
settingsDocument = null;
|
||||||
|
cacheDocument = null;
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.mongo.version;
|
||||||
|
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import com.mongodb.client.model.Indexes;
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import org.bson.Document;
|
||||||
|
|
||||||
|
public class MongoFirst implements Version<MongoVPN> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(MongoVPN database) throws DatabaseException {
|
||||||
|
if(database.settingsDocument.listIndexes().first() == null) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Created index for settings collection!");
|
||||||
|
database.settingsDocument.createIndex(Indexes.ascending("ip"));
|
||||||
|
database.settingsDocument.createIndex(Indexes.ascending("setting"));
|
||||||
|
}
|
||||||
|
var versionCollect = database.antivpnDatabase.getCollection("version");
|
||||||
|
|
||||||
|
versionCollect.insertOne(new Document("version", versionNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int versionNumber() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsUpdate(MongoVPN database) {
|
||||||
|
var versionCollect = database.antivpnDatabase.getCollection("version");
|
||||||
|
|
||||||
|
return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.mongo.version;
|
||||||
|
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import com.mongodb.client.model.Indexes;
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.types.Decimal128;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class MongoSecond implements Version<MongoVPN> {
|
||||||
|
@Override
|
||||||
|
public void update(MongoVPN database) throws DatabaseException {
|
||||||
|
List<Document> backup = new ArrayList<>();
|
||||||
|
database.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.exists("ip")))
|
||||||
|
.forEach((Consumer<? super Document>) doc -> {
|
||||||
|
backup.add(new Document(doc));
|
||||||
|
|
||||||
|
String ip = doc.getString("ip");
|
||||||
|
|
||||||
|
try {
|
||||||
|
var cidr = new CIDRUtils(ip + "/32");
|
||||||
|
|
||||||
|
doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt())));
|
||||||
|
doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())));
|
||||||
|
doc.append("cidr_string", cidr.toString());
|
||||||
|
doc.remove("ip");
|
||||||
|
|
||||||
|
database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
rollback(backup, database);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
database.settingsDocument.createIndex(Indexes.compoundIndex(Indexes.ascending("ip_start"), Indexes.ascending("ip_end")));
|
||||||
|
database.settingsDocument.createIndex(Indexes.ascending("cidr_string"));
|
||||||
|
var versionCollect = database.antivpnDatabase.getCollection("version");
|
||||||
|
versionCollect.insertOne(new Document("version", versionNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rollback(List<Document> toRollback, MongoVPN database) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
|
||||||
|
toRollback.forEach(doc -> database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc));
|
||||||
|
toRollback.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int versionNumber() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsUpdate(MongoVPN database) {
|
||||||
|
var versionCollect = database.antivpnDatabase.getCollection("version");
|
||||||
|
|
||||||
|
return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.mongo.version;
|
||||||
|
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
import dev.brighten.antivpn.utils.CIDRUtils;
|
||||||
|
import dev.brighten.antivpn.utils.MiscUtils;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.types.Decimal128;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class MongoThird implements Version<MongoVPN> {
|
||||||
|
@Override
|
||||||
|
public void update(MongoVPN database) throws DatabaseException {
|
||||||
|
List<CIDRUtils> ipRanges = new ArrayList<>();
|
||||||
|
List<CIDRUtils> rangesToInsert = new ArrayList<>();
|
||||||
|
List<BigInteger[]> rangesToRemove = new ArrayList<>();
|
||||||
|
database.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string")))
|
||||||
|
.forEach((Consumer<? super Document>) doc -> {
|
||||||
|
BigInteger start = doc.get("ip_start", Decimal128.class).bigDecimalValue().toBigInteger();
|
||||||
|
BigInteger end = doc.get("ip_end", Decimal128.class).bigDecimalValue().toBigInteger();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var range = MiscUtils.rangeToCidrs(start, end);
|
||||||
|
|
||||||
|
if(range.size() > 1) {
|
||||||
|
rangesToRemove.add(new BigInteger[]{start, end});
|
||||||
|
rangesToInsert.addAll(range);
|
||||||
|
AntiVPN.getInstance().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end);
|
||||||
|
} else ipRanges.addAll(range);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(
|
||||||
|
String.format("Could not convert ip range to CIDR! %s, %s", start, end), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!rangesToInsert.isEmpty()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size());
|
||||||
|
var documentsToInsert = rangesToInsert.stream().map(cidr -> {
|
||||||
|
Document doc = new Document("setting", "whitelist");
|
||||||
|
doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt())));
|
||||||
|
doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())));
|
||||||
|
doc.append("cidr_string", cidr.getCidr());
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
database.settingsDocument.insertMany(documentsToInsert);
|
||||||
|
}
|
||||||
|
if(!rangesToRemove.isEmpty()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size());
|
||||||
|
rangesToRemove.forEach(range -> database.settingsDocument
|
||||||
|
.deleteMany(Filters.and(
|
||||||
|
Filters.gte("ip_start", new Decimal128(new BigDecimal(range[0]))),
|
||||||
|
Filters.lte("ip_end", new Decimal128(new BigDecimal(range[1]))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!ipRanges.isEmpty()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Updating %s CIDRs in database with proper notation...", ipRanges.size());
|
||||||
|
|
||||||
|
ipRanges.forEach(cidr -> database.settingsDocument
|
||||||
|
.updateMany(Filters.and(Filters.eq("setting", "whitelist"),
|
||||||
|
Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))),
|
||||||
|
Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))),
|
||||||
|
new Document("$set", new Document("cidr_string", cidr.getCidr()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionCollect = database.antivpnDatabase.getCollection("version");
|
||||||
|
versionCollect.insertOne(new Document("version", versionNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int versionNumber() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsUpdate(MongoVPN database) {
|
||||||
|
var versionCollect = database.antivpnDatabase.getCollection("version");
|
||||||
|
|
||||||
|
return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.sql;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.local.H2VPN;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.MySQL;
|
||||||
|
import dev.brighten.antivpn.database.version.Version;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class MySqlVPN extends H2VPN {
|
||||||
|
|
||||||
|
public MySqlVPN() {
|
||||||
|
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
|
||||||
|
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
|
||||||
|
|
||||||
|
//Refreshing whitelisted players
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted().clear();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelisted()
|
||||||
|
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
|
||||||
|
|
||||||
|
//Refreshing whitlisted IPs
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
|
||||||
|
AntiVPN.getInstance().getExecutor().getWhitelistedIps()
|
||||||
|
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
|
||||||
|
}, 2, 30, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled())
|
||||||
|
return;
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Initializing MySQL...");
|
||||||
|
MySQL.init();
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Checking for updates...");
|
||||||
|
|
||||||
|
//Running check for old table types to update
|
||||||
|
try {
|
||||||
|
for (Version<MySqlVPN> version : Version.mysqlVersions) {
|
||||||
|
if(version.needsUpdate(this)) {
|
||||||
|
version.update(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not complete version setup due to SQL error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
-29
@@ -1,53 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.database.sql.utils;
|
package dev.brighten.antivpn.database.sql.utils;
|
||||||
|
|
||||||
import dev.brighten.antivpn.utils.MiscUtils;
|
import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ExecutableStatement {
|
public class ExecutableStatement implements AutoCloseable {
|
||||||
private PreparedStatement statement;
|
@Getter
|
||||||
|
private final PreparedStatement statement;
|
||||||
private int pos = 1;
|
private int pos = 1;
|
||||||
|
|
||||||
public ExecutableStatement(PreparedStatement statement) {
|
public ExecutableStatement(PreparedStatement statement) {
|
||||||
this.statement = statement;
|
this.statement = statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
public int execute() throws SQLException {
|
||||||
public Integer execute() {
|
return statement.executeUpdate();
|
||||||
try {
|
|
||||||
return statement.executeUpdate();
|
|
||||||
} finally {
|
|
||||||
MiscUtils.close(statement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
public void execute(ResultSetIterator iterator) throws SQLException {
|
||||||
public void execute(ResultSetIterator iterator) {
|
try(var rs = statement.executeQuery()) {
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
rs = statement.executeQuery();
|
|
||||||
while (rs.next()) iterator.next(rs);
|
while (rs.next()) iterator.next(rs);
|
||||||
} finally {
|
|
||||||
MiscUtils.close(statement, rs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
public int[] executeBatch() throws SQLException {
|
||||||
public void executeSingle(ResultSetIterator iterator) {
|
return statement.executeBatch();
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
rs = statement.executeQuery();
|
|
||||||
if (rs.next()) iterator.next(rs);
|
|
||||||
else iterator.next(null);
|
|
||||||
} finally {
|
|
||||||
MiscUtils.close(statement, rs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
public ResultSet executeQuery() throws SQLException {
|
||||||
public ResultSet executeQuery() {
|
|
||||||
return statement.executeQuery();
|
return statement.executeQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,4 +133,15 @@ public class ExecutableStatement {
|
|||||||
statement.setBytes(pos++, obj);
|
statement.setBytes(pos++, obj);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public ExecutableStatement addBatch() {
|
||||||
|
statement.addBatch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
statement.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.sql.utils;
|
||||||
|
|
||||||
|
import com.mysql.cj.jdbc.Driver;
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import org.h2.jdbc.JdbcSQLFeatureNotSupportedException;
|
||||||
|
import org.h2.jdbc.JdbcSQLNonTransientConnectionException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLSyntaxErrorException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class MySQL {
|
||||||
|
private static Connection conn;
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
try {
|
||||||
|
if (conn == null || conn.isClosed()) {
|
||||||
|
String url = "jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp()
|
||||||
|
+ ":" + AntiVPN.getInstance().getVpnConfig().getPort()
|
||||||
|
+ "/?useSSL=true&autoReconnect=true";
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername());
|
||||||
|
properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword());
|
||||||
|
|
||||||
|
conn = new Driver().connect(url, properties);
|
||||||
|
if (conn == null) {
|
||||||
|
throw new SQLException("MySQL driver did not accept URL: " + url);
|
||||||
|
}
|
||||||
|
conn.setAutoCommit(true);
|
||||||
|
Query.use(conn);
|
||||||
|
String databaseName = AntiVPN.getInstance().getVpnConfig().getDatabaseName();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Query.prepare("CREATE DATABASE IF NOT EXISTS `" + databaseName + "`").execute();
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
if (!isDatabaseCreationPermissionIssue(ex)) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log(
|
||||||
|
"No permission to create MySQL database `" + databaseName
|
||||||
|
+ "`. Attempting to use the existing database instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Query.prepare("USE `" + databaseName + "`").execute();
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Failed to load mysql: " + e.getMessage(), e);
|
||||||
|
throw new RuntimeException("Could not initialize MySQL connection", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDatabaseCreationPermissionIssue(SQLException ex) {
|
||||||
|
return ex instanceof SQLSyntaxErrorException
|
||||||
|
&& ex.getMessage() != null
|
||||||
|
&& ex.getMessage().contains("Access denied");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initH2() {
|
||||||
|
initH2(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initH2(boolean allowRetry) {
|
||||||
|
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
|
||||||
|
if (!dataFolder.exists() && dataFolder.mkdirs()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Created database directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
File dbFile = new File(dataFolder, "database.mv.db");
|
||||||
|
|
||||||
|
File databaseFile = new File(dataFolder, "database");
|
||||||
|
try {
|
||||||
|
conn = new NonClosableConnection(new org.h2.jdbc.JdbcConnection("jdbc:h2:file:" +
|
||||||
|
databaseFile.getAbsolutePath(),
|
||||||
|
new Properties(), AntiVPN.getInstance().getVpnConfig().getUsername(),
|
||||||
|
AntiVPN.getInstance().getVpnConfig().getPassword(), false));
|
||||||
|
conn.setAutoCommit(true);
|
||||||
|
Query.use(conn);
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Connection to H2 has been established.");
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("H2 exception on initialize", ex);
|
||||||
|
if(ex instanceof JdbcSQLFeatureNotSupportedException
|
||||||
|
|| ex instanceof JdbcSQLNonTransientConnectionException) {
|
||||||
|
AntiVPN.getInstance().getExecutor()
|
||||||
|
.log("H2 database file is incompatible with this version of AntiVPN. " +
|
||||||
|
"Backing up old database file...");
|
||||||
|
shutdown();
|
||||||
|
if (allowRetry && backupOldDB(dbFile, dataFolder)) {
|
||||||
|
initH2(false);
|
||||||
|
} else {
|
||||||
|
AntiVPN.getInstance().getExecutor().log(
|
||||||
|
"Could not back up and remove the incompatible H2 database file automatically.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + ex.getCause().toString(), ex);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + e.getMessage(), e);
|
||||||
|
AntiVPN.getInstance().getExecutor().log(Level.INFO, "TIP: Try deleting the plugin folder and restarting your server!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean backupOldDB(File dbFile, File dataFolder) {
|
||||||
|
if (!dbFile.exists()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dbFile.isFile()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Skipping backup for non-file path: " + dbFile.getAbsolutePath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
File backupDir = new File(dataFolder, "backups");
|
||||||
|
if(backupDir.mkdirs()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Created backup directory");
|
||||||
|
} else if (backupDir.exists()) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Backup directory already exists");
|
||||||
|
} else {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis());
|
||||||
|
Files.copy(dbFile.toPath(), backupFile.toPath());
|
||||||
|
|
||||||
|
if (!dbFile.delete()) {
|
||||||
|
dbFile.deleteOnExit();
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file");
|
||||||
|
return true;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void use() {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
} catch (Exception e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void shutdown() {
|
||||||
|
try {
|
||||||
|
if(conn != null && !conn.isClosed()) {
|
||||||
|
if(conn instanceof NonClosableConnection) {
|
||||||
|
((NonClosableConnection)conn).shutdown();
|
||||||
|
} else conn.close();
|
||||||
|
conn = null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isClosed() {
|
||||||
|
if(conn == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return conn.isClosed();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-19
@@ -1,26 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of LuckPerms, licensed under the MIT License.
|
* Copyright 2026 Dawson Hessler
|
||||||
*
|
*
|
||||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* Copyright (c) contributors
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* copies or substantial portions of the Software.
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
*
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* See the License for the specific language governing permissions and
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* limitations under the License.
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.database.sql.utils;
|
package dev.brighten.antivpn.database.sql.utils;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.sql.utils;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.intellij.lang.annotations.Language;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class Query {
|
||||||
|
@Getter
|
||||||
|
private static Connection conn;
|
||||||
|
|
||||||
|
public static void use(Connection conn) {
|
||||||
|
Query.conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SqlSourceToSinkFlow")
|
||||||
|
public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException {
|
||||||
|
return new ExecutableStatement(conn.prepareStatement(sql));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.sql.utils;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public interface ResultSetIterator {
|
||||||
|
void next(ResultSet rs) throws SQLException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.sql.version;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.VPNDatabase;
|
||||||
|
import dev.brighten.antivpn.database.local.version.First;
|
||||||
|
import dev.brighten.antivpn.database.sql.utils.Query;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class MySQLFirst extends First {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(VPNDatabase database) throws DatabaseException {
|
||||||
|
try(var statement = Query.prepare("select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " +
|
||||||
|
"WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';")) {
|
||||||
|
statement.execute(set -> {
|
||||||
|
if(set.getObject("DATA_TYPE").toString().contains("varchar")) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Using old database format for storing responses! " +
|
||||||
|
"Dropping table and creating a new one...");
|
||||||
|
try(var state = Query.prepare("drop table `responses`")) {
|
||||||
|
if(state.execute() > 0) {
|
||||||
|
AntiVPN.getInstance().getExecutor().log("Successfully dropped table!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException("Could not update MySQL database", e);
|
||||||
|
}
|
||||||
|
super.update(database);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.database.version;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.database.DatabaseException;
|
||||||
|
import dev.brighten.antivpn.database.local.H2VPN;
|
||||||
|
import dev.brighten.antivpn.database.local.version.First;
|
||||||
|
import dev.brighten.antivpn.database.local.version.Second;
|
||||||
|
import dev.brighten.antivpn.database.local.version.Third;
|
||||||
|
import dev.brighten.antivpn.database.mongo.MongoVPN;
|
||||||
|
import dev.brighten.antivpn.database.mongo.version.MongoFirst;
|
||||||
|
import dev.brighten.antivpn.database.mongo.version.MongoSecond;
|
||||||
|
import dev.brighten.antivpn.database.mongo.version.MongoThird;
|
||||||
|
import dev.brighten.antivpn.database.sql.MySqlVPN;
|
||||||
|
import dev.brighten.antivpn.database.sql.version.MySQLFirst;
|
||||||
|
|
||||||
|
|
||||||
|
public interface Version<DB> {
|
||||||
|
void update(DB database) throws DatabaseException;
|
||||||
|
int versionNumber();
|
||||||
|
boolean needsUpdate(DB database);
|
||||||
|
|
||||||
|
Version<MongoVPN>[] mongoDbVersions = new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()};
|
||||||
|
Version<MySqlVPN>[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()};
|
||||||
|
Version<H2VPN>[] h2Versions = new Version[] {new First(), new Second(), new Third()};
|
||||||
|
}
|
||||||
@@ -0,0 +1,864 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.depends;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.utils.NonnullByDefault;
|
||||||
|
import dev.brighten.antivpn.utils.Supplier;
|
||||||
|
import dev.brighten.antivpn.utils.Suppliers;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.objectweb.asm.AnnotationVisitor;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.FieldVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.RecordComponentVisitor;
|
||||||
|
import org.objectweb.asm.commons.ClassRemapper;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.jar.JarOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves {@link MavenLibrary} annotations for a class, and loads the dependency
|
||||||
|
* into the classloader.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("CallToPrintStackTrace")
|
||||||
|
@NonnullByDefault
|
||||||
|
public final class LibraryLoader {
|
||||||
|
private static final int RELOCATION_FORMAT_VERSION = 5;
|
||||||
|
private static final String RELOCATION_METADATA_PATH = "META-INF/antivpn-relocation.properties";
|
||||||
|
|
||||||
|
@SuppressWarnings("Guava")
|
||||||
|
private static final Supplier<URLClassLoaderAccess> URL_INJECTOR = AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader ?
|
||||||
|
Suppliers.memoize(() ->
|
||||||
|
URLClassLoaderAccess.create((URLClassLoader) AntiVPN.getInstance().getClass().getClassLoader()))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
public static void loadAll(Object object) {
|
||||||
|
if(URL_INJECTOR == null)
|
||||||
|
return;
|
||||||
|
loadAll(object.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadAll(Class<?> clazz) {
|
||||||
|
if(URL_INJECTOR == null)
|
||||||
|
return;
|
||||||
|
MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class);
|
||||||
|
|
||||||
|
for (MavenLibrary lib : libs) {
|
||||||
|
// Create relocations map if any are defined
|
||||||
|
Map<String, String> relocations = new HashMap<>();
|
||||||
|
for (Relocate relocate : lib.relocations()) {
|
||||||
|
relocations.put(relocate.from().replace("\\", ""), relocate.to());
|
||||||
|
}
|
||||||
|
|
||||||
|
load(lib.groupId().replace("\\", ""), lib.artifactId(), lib.version(), lib.repo().url(), relocations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(String groupId, String artifactId, String version, String repoUrl,
|
||||||
|
Map<String, String> relocations) {
|
||||||
|
load(new Dependency(groupId, artifactId, version, repoUrl), relocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(Dependency d, Map<String, String> relocations) {
|
||||||
|
System.out.printf("Loading dependency %s:%s:%s from %s%n",
|
||||||
|
d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getRepoUrl());
|
||||||
|
String name = d.getArtifactId() + "-" + d.getVersion();
|
||||||
|
|
||||||
|
// If we have relocations, add a suffix to identify the relocated version
|
||||||
|
String fileName = name + ".jar";
|
||||||
|
if (!relocations.isEmpty()) {
|
||||||
|
fileName = name + "-relocated.jar";
|
||||||
|
}
|
||||||
|
|
||||||
|
File saveLocation = new File(getLibFolder(), fileName);
|
||||||
|
File originalJar = new File(getLibFolder(), name + ".jar");
|
||||||
|
|
||||||
|
// Download the original jar if it doesn't exist
|
||||||
|
if (!originalJar.exists()) {
|
||||||
|
try {
|
||||||
|
System.out.println("Dependency '" + name +
|
||||||
|
"' is not already in the libraries folder. Attempting to download...");
|
||||||
|
URL url = d.getUrl();
|
||||||
|
|
||||||
|
try (InputStream is = url.openStream()) {
|
||||||
|
Files.copy(is, originalJar.toPath());
|
||||||
|
}
|
||||||
|
System.out.println("Dependency '" + name + "' successfully downloaded.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException("Unable to download dependency: " + d, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild relocated jars when the relocation format changes or the cached jar is stale.
|
||||||
|
if (!relocations.isEmpty() && shouldRebuildRelocatedJar(saveLocation, relocations)) {
|
||||||
|
try {
|
||||||
|
System.out.println("Relocating packages for " + name + "...");
|
||||||
|
relocateJar(originalJar, saveLocation, relocations);
|
||||||
|
System.out.println("Successfully relocated packages for " + name);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException("Failed to relocate packages for dependency: " + d, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the appropriate jar (original or relocated)
|
||||||
|
File jarToLoad = relocations.isEmpty() ? originalJar : saveLocation;
|
||||||
|
|
||||||
|
if (!jarToLoad.exists()) {
|
||||||
|
throw new RuntimeException("Unable to find dependency jar: " + jarToLoad.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
URL_INJECTOR.get().addURL(jarToLoad.toURI().toURL());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to load dependency: " + jarToLoad, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Loaded dependency '" + name + "' successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void relocateJar(File sourceJar, File targetJar, Map<String, String> relocations)
|
||||||
|
throws IOException {
|
||||||
|
// Track service files to avoid duplicates
|
||||||
|
Map<String, StringBuilder> serviceFiles = new HashMap<>();
|
||||||
|
|
||||||
|
Files.deleteIfExists(targetJar.toPath());
|
||||||
|
|
||||||
|
try (JarFile jar = new JarFile(sourceJar);
|
||||||
|
JarOutputStream jos = new JarOutputStream(Files.newOutputStream(targetJar.toPath()))) {
|
||||||
|
|
||||||
|
Enumeration<JarEntry> entries = jar.entries();
|
||||||
|
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
|
||||||
|
// Skip directories
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream is = jar.getInputStream(entry)) {
|
||||||
|
if (name.startsWith("META-INF/services/")) {
|
||||||
|
// Process service files but don't write yet
|
||||||
|
processServiceFile(name, is, serviceFiles, relocations);
|
||||||
|
} else if (name.endsWith(".class")) {
|
||||||
|
// Relocate class file path as well as content
|
||||||
|
String relocatedPath = relocateClassPath(name, relocations);
|
||||||
|
|
||||||
|
JarEntry newEntry = new JarEntry(relocatedPath);
|
||||||
|
jos.putNextEntry(newEntry);
|
||||||
|
|
||||||
|
byte[] classBytes = readAllBytes(is);
|
||||||
|
byte[] relocatedBytes = relocateClass(name, classBytes, relocations);
|
||||||
|
jos.write(relocatedBytes);
|
||||||
|
jos.closeEntry();
|
||||||
|
} else {
|
||||||
|
// Relocate package-scoped resources so ResourceBundle lookups follow relocated packages.
|
||||||
|
String relocatedPath = relocateResourcePath(name, relocations);
|
||||||
|
|
||||||
|
JarEntry newEntry = new JarEntry(relocatedPath);
|
||||||
|
jos.putNextEntry(newEntry);
|
||||||
|
copyStream(is, jos);
|
||||||
|
jos.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now write all service files after processing
|
||||||
|
for (Map.Entry<String, StringBuilder> entry : serviceFiles.entrySet()) {
|
||||||
|
try {
|
||||||
|
JarEntry serviceEntry = new JarEntry(entry.getKey());
|
||||||
|
jos.putNextEntry(serviceEntry);
|
||||||
|
jos.write(entry.getValue().toString().getBytes(StandardCharsets.UTF_8));
|
||||||
|
jos.closeEntry();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Log but continue with other service files
|
||||||
|
System.out.println("Warning: Could not write service file " +
|
||||||
|
entry.getKey() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeRelocationMetadata(jos, relocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRelocatedJar(targetJar, relocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldRebuildRelocatedJar(File relocatedJar, Map<String, String> relocations) {
|
||||||
|
if (!relocatedJar.exists()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (JarFile jar = new JarFile(relocatedJar)) {
|
||||||
|
JarEntry metadataEntry = jar.getJarEntry(RELOCATION_METADATA_PATH);
|
||||||
|
if (metadataEntry == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties metadata = new Properties();
|
||||||
|
try (InputStream is = jar.getInputStream(metadataEntry)) {
|
||||||
|
metadata.load(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!String.valueOf(RELOCATION_FORMAT_VERSION).equals(metadata.getProperty("formatVersion"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||||
|
String key = "relocation." + relocation.getKey();
|
||||||
|
if (!relocation.getValue().equals(metadata.getProperty(key))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.toString(relocations.size()).equals(metadata.getProperty("relocationCount"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeRelocationMetadata(JarOutputStream jos, Map<String, String> relocations)
|
||||||
|
throws IOException {
|
||||||
|
Properties metadata = new Properties();
|
||||||
|
metadata.setProperty("formatVersion", Integer.toString(RELOCATION_FORMAT_VERSION));
|
||||||
|
metadata.setProperty("relocationCount", Integer.toString(relocations.size()));
|
||||||
|
|
||||||
|
Map<String, String> sortedRelocations = new TreeMap<>(relocations);
|
||||||
|
for (Map.Entry<String, String> relocation : sortedRelocations.entrySet()) {
|
||||||
|
metadata.setProperty("relocation." + relocation.getKey(), relocation.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
metadata.store(buffer, "AntiVPN relocation metadata");
|
||||||
|
|
||||||
|
JarEntry metadataEntry = new JarEntry(RELOCATION_METADATA_PATH);
|
||||||
|
jos.putNextEntry(metadataEntry);
|
||||||
|
jos.write(buffer.toByteArray());
|
||||||
|
jos.closeEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processServiceFile(String name, InputStream is,
|
||||||
|
Map<String, StringBuilder> serviceFiles,
|
||||||
|
Map<String, String> relocations) throws IOException {
|
||||||
|
// Read service file content
|
||||||
|
String content = new String(readAllBytes(is));
|
||||||
|
StringBuilder contentBuilder = serviceFiles.computeIfAbsent(name, k -> new StringBuilder());
|
||||||
|
|
||||||
|
// Process and relocate service implementations
|
||||||
|
for (String line : content.split("\n")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (!trimmed.isEmpty() && !trimmed.startsWith("#")) {
|
||||||
|
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||||
|
if (trimmed.startsWith(relocation.getKey())) {
|
||||||
|
trimmed = relocation.getValue() +
|
||||||
|
trimmed.substring(relocation.getKey().length());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentBuilder.append(trimmed).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] relocateClass(String entryName, byte[] classBytes, Map<String, String> relocations) {
|
||||||
|
try {
|
||||||
|
// Convert to slash notation for ASM
|
||||||
|
Remapper prefixRemapper = getPrefixRemapper(relocations);
|
||||||
|
|
||||||
|
// Create custom ClassWriter to handle missing classes
|
||||||
|
ClassReader reader = new ClassReader(classBytes);
|
||||||
|
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS) {
|
||||||
|
@Override
|
||||||
|
protected String getCommonSuperClass(String type1, String type2) {
|
||||||
|
try {
|
||||||
|
return super.getCommonSuperClass(type1, type2);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Fall back to Object when classes can't be loaded
|
||||||
|
return "java/lang/Object";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ClassVisitor visitor = createStringRelocationVisitor(new ClassRemapper(writer, prefixRemapper), relocations);
|
||||||
|
visitor = createMySqlUtilFallbackVisitor(entryName, visitor);
|
||||||
|
|
||||||
|
// Process class with remapper
|
||||||
|
reader.accept(visitor, 0);
|
||||||
|
|
||||||
|
return relocateUtf8Constants(writer.toByteArray(), relocations);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to relocate class entry " + entryName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String relocateReflectiveClassName(String className) {
|
||||||
|
if (className == null || className.startsWith("dev.brighten.antivpn.shaded.")) {
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (className.startsWith("com.mysql.cj") || className.startsWith("com.mysql.jdbc")) {
|
||||||
|
return "dev.brighten.antivpn.shaded." + className;
|
||||||
|
}
|
||||||
|
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] relocateUtf8Constants(byte[] classBytes, Map<String, String> relocations) throws IOException {
|
||||||
|
Map<String, String> dotMappings = new HashMap<>();
|
||||||
|
Map<String, String> slashMappings = new HashMap<>();
|
||||||
|
for (Map.Entry<String, String> entry : relocations.entrySet()) {
|
||||||
|
dotMappings.put(entry.getKey(), entry.getValue());
|
||||||
|
slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes));
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(classBytes.length + 256);
|
||||||
|
DataOutputStream out = new DataOutputStream(baos);
|
||||||
|
|
||||||
|
out.writeInt(in.readInt());
|
||||||
|
out.writeShort(in.readUnsignedShort());
|
||||||
|
out.writeShort(in.readUnsignedShort());
|
||||||
|
|
||||||
|
int constantPoolCount = in.readUnsignedShort();
|
||||||
|
out.writeShort(constantPoolCount);
|
||||||
|
|
||||||
|
for (int i = 1; i < constantPoolCount; i++) {
|
||||||
|
int tag = in.readUnsignedByte();
|
||||||
|
out.writeByte(tag);
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
case 1 -> {
|
||||||
|
String value = in.readUTF();
|
||||||
|
String relocated = relocateStringValue(value, dotMappings, slashMappings);
|
||||||
|
out.writeUTF(relocated);
|
||||||
|
}
|
||||||
|
case 3, 4 -> out.writeInt(in.readInt());
|
||||||
|
case 5, 6 -> {
|
||||||
|
out.writeLong(in.readLong());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
case 7, 8, 16, 19, 20 -> out.writeShort(in.readUnsignedShort());
|
||||||
|
case 9, 10, 11, 12, 17, 18 -> {
|
||||||
|
out.writeShort(in.readUnsignedShort());
|
||||||
|
out.writeShort(in.readUnsignedShort());
|
||||||
|
}
|
||||||
|
case 15 -> {
|
||||||
|
out.writeByte(in.readUnsignedByte());
|
||||||
|
out.writeShort(in.readUnsignedShort());
|
||||||
|
}
|
||||||
|
default -> throw new IOException("Unknown constant pool tag " + tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyStream(in, out);
|
||||||
|
out.flush();
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Remapper getPrefixRemapper(Map<String, String> relocations) {
|
||||||
|
Map<String, String> slashMappings = new HashMap<>();
|
||||||
|
Map<String, String> dotMappings = new HashMap<>();
|
||||||
|
for (Map.Entry<String, String> entry : relocations.entrySet()) {
|
||||||
|
dotMappings.put(entry.getKey(), entry.getValue());
|
||||||
|
String fromSlash = entry.getKey().replace('.', '/');
|
||||||
|
String toSlash = entry.getValue().replace('.', '/');
|
||||||
|
slashMappings.put(fromSlash, toSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create customized remapper for package prefixes
|
||||||
|
return new Remapper() {
|
||||||
|
@Override
|
||||||
|
public String map(String typeName) {
|
||||||
|
if (typeName == null) return null;
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : slashMappings.entrySet()) {
|
||||||
|
String from = entry.getKey();
|
||||||
|
String to = entry.getValue();
|
||||||
|
|
||||||
|
if (typeName.startsWith(from)) {
|
||||||
|
return to + typeName.substring(from.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object mapValue(Object value) {
|
||||||
|
if (value instanceof String stringValue) {
|
||||||
|
return relocateStringValue(stringValue, dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
return super.mapValue(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassVisitor createMySqlUtilFallbackVisitor(String entryName, ClassVisitor delegate) {
|
||||||
|
if (!"com/mysql/cj/util/Util.class".equals(entryName)) {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClassVisitor(Opcodes.ASM9, delegate) {
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
|
||||||
|
String[] exceptions) {
|
||||||
|
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
if (visitor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!"getInstance".equals(name)
|
||||||
|
|| !"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;Lcom/mysql/cj/exceptions/ExceptionInterceptor;)Ljava/lang/Object;".equals(descriptor)) {
|
||||||
|
return visitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MethodVisitor(Opcodes.ASM9, visitor) {
|
||||||
|
@Override
|
||||||
|
public void visitCode() {
|
||||||
|
super.visitCode();
|
||||||
|
super.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
|
super.visitMethodInsn(Opcodes.INVOKESTATIC,
|
||||||
|
"dev/brighten/antivpn/depends/LibraryLoader",
|
||||||
|
"relocateReflectiveClassName",
|
||||||
|
"(Ljava/lang/String;)Ljava/lang/String;",
|
||||||
|
false);
|
||||||
|
super.visitVarInsn(Opcodes.ASTORE, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassVisitor createStringRelocationVisitor(ClassVisitor delegate,
|
||||||
|
Map<String, String> relocations) {
|
||||||
|
Map<String, String> dotMappings = new HashMap<>();
|
||||||
|
Map<String, String> slashMappings = new HashMap<>();
|
||||||
|
for (Map.Entry<String, String> entry : relocations.entrySet()) {
|
||||||
|
dotMappings.put(entry.getKey(), entry.getValue());
|
||||||
|
slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClassVisitor(Opcodes.ASM9, delegate) {
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||||
|
String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
|
||||||
|
RecordComponentVisitor visitor = super.visitRecordComponent(name, descriptor, signature);
|
||||||
|
if (visitor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new RecordComponentVisitor(Opcodes.ASM9, visitor) {
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||||
|
String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||||
|
FieldVisitor visitor = super.visitField(access, name, descriptor, signature,
|
||||||
|
relocateAsmValue(value, dotMappings, slashMappings));
|
||||||
|
if (visitor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new FieldVisitor(Opcodes.ASM9, visitor) {
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||||
|
String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
|
||||||
|
String[] exceptions) {
|
||||||
|
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
if (visitor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new MethodVisitor(Opcodes.ASM9, visitor) {
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotationDefault() {
|
||||||
|
return wrapAnnotationVisitor(super.visitAnnotationDefault(), dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||||
|
String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor,
|
||||||
|
boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitParameterAnnotation(parameter, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitInsnAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||||
|
String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitInsnAnnotation(typeRef, typePath, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitTryCatchAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||||
|
String descriptor, boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
|
||||||
|
org.objectweb.asm.TypePath typePath,
|
||||||
|
org.objectweb.asm.Label[] start,
|
||||||
|
org.objectweb.asm.Label[] end,
|
||||||
|
int[] index, String descriptor,
|
||||||
|
boolean visible) {
|
||||||
|
return wrapAnnotationVisitor(
|
||||||
|
super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible),
|
||||||
|
dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLdcInsn(Object value) {
|
||||||
|
super.visitLdcInsn(relocateAsmValue(value, dotMappings, slashMappings));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInvokeDynamicInsn(String name, String descriptor, org.objectweb.asm.Handle bootstrapMethodHandle,
|
||||||
|
Object... bootstrapMethodArguments) {
|
||||||
|
Object[] relocatedArgs = new Object[bootstrapMethodArguments.length];
|
||||||
|
for (int i = 0; i < bootstrapMethodArguments.length; i++) {
|
||||||
|
relocatedArgs[i] = relocateAsmValue(bootstrapMethodArguments[i], dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, relocatedArgs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnnotationVisitor wrapAnnotationVisitor(AnnotationVisitor delegate,
|
||||||
|
Map<String, String> dotMappings,
|
||||||
|
Map<String, String> slashMappings) {
|
||||||
|
if (delegate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AnnotationVisitor(Opcodes.ASM9, delegate) {
|
||||||
|
@Override
|
||||||
|
public void visit(String name, Object value) {
|
||||||
|
super.visit(name, relocateAsmValue(value, dotMappings, slashMappings));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
|
||||||
|
return wrapAnnotationVisitor(super.visitAnnotation(name, descriptor), dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitArray(String name) {
|
||||||
|
return wrapAnnotationVisitor(super.visitArray(name), dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object relocateAsmValue(Object value, Map<String, String> dotMappings,
|
||||||
|
Map<String, String> slashMappings) {
|
||||||
|
if (value instanceof String stringValue) {
|
||||||
|
return relocateStringValue(stringValue, dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String relocateStringValue(String value, Map<String, String> dotMappings,
|
||||||
|
Map<String, String> slashMappings) {
|
||||||
|
for (Map.Entry<String, String> entry : dotMappings.entrySet()) {
|
||||||
|
String from = entry.getKey();
|
||||||
|
String relocated = relocateByPrefixes(value, from, entry.getValue(), '.', '$');
|
||||||
|
if (!relocated.equals(value)) {
|
||||||
|
return relocated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : slashMappings.entrySet()) {
|
||||||
|
String from = entry.getKey();
|
||||||
|
String to = entry.getValue();
|
||||||
|
|
||||||
|
String relocated = relocateByPrefixes(value, from, to, '/', '$');
|
||||||
|
if (!relocated.equals(value)) {
|
||||||
|
return relocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
relocated = relocateByPrefixes(value, "/" + from, "/" + to, '/', '$');
|
||||||
|
if (!relocated.equals(value)) {
|
||||||
|
return relocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
relocated = relocateByPrefixes(value, "L" + from, "L" + to, '/', '$', ';');
|
||||||
|
if (!relocated.equals(value)) {
|
||||||
|
return relocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
relocated = relocateByPrefixes(value, "[L" + from, "[L" + to, '/', '$', ';');
|
||||||
|
if (!relocated.equals(value)) {
|
||||||
|
return relocated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String relocateByPrefixes(String value, String from, String to, char... delimiters) {
|
||||||
|
if (value.equals(from)) {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (char delimiter : delimiters) {
|
||||||
|
if (value.startsWith(from + delimiter)) {
|
||||||
|
return to + value.substring(from.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateRelocatedJar(File targetJar, Map<String, String> relocations) throws IOException {
|
||||||
|
Set<String> relocatedPrefixes = new HashSet<>();
|
||||||
|
Map<String, String> dotMappings = new HashMap<>();
|
||||||
|
Map<String, String> slashMappings = new HashMap<>();
|
||||||
|
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||||
|
relocatedPrefixes.add(relocation.getValue().replace('.', '/') + "/");
|
||||||
|
dotMappings.put(relocation.getKey(), relocation.getValue());
|
||||||
|
slashMappings.put(relocation.getKey().replace('.', '/'), relocation.getValue().replace('.', '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try (JarFile jar = new JarFile(targetJar)) {
|
||||||
|
Enumeration<JarEntry> entries = jar.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean shouldValidate = false;
|
||||||
|
for (String relocatedPrefix : relocatedPrefixes) {
|
||||||
|
if (entry.getName().startsWith(relocatedPrefix)) {
|
||||||
|
shouldValidate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldValidate) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream is = jar.getInputStream(entry)) {
|
||||||
|
findUnrelocatedConstant(entry.getName(), readAllBytes(is), dotMappings, slashMappings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void findUnrelocatedConstant(String entryName, byte[] classBytes, Map<String, String> dotMappings,
|
||||||
|
Map<String, String> slashMappings) throws IOException {
|
||||||
|
DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes));
|
||||||
|
in.readInt();
|
||||||
|
in.readUnsignedShort();
|
||||||
|
in.readUnsignedShort();
|
||||||
|
int constantPoolCount = in.readUnsignedShort();
|
||||||
|
|
||||||
|
for (int i = 1; i < constantPoolCount; i++) {
|
||||||
|
int tag = in.readUnsignedByte();
|
||||||
|
switch (tag) {
|
||||||
|
case 1 -> {
|
||||||
|
String value = in.readUTF();
|
||||||
|
String relocated = relocateStringValue(value, dotMappings, slashMappings);
|
||||||
|
if (!value.equals(relocated)) {
|
||||||
|
throw new IOException("Relocated jar still contains original reference '" + value
|
||||||
|
+ "' in class entry " + entryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 3, 4 -> in.readInt();
|
||||||
|
case 5, 6 -> {
|
||||||
|
in.readLong();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
case 7, 8, 16, 19, 20 -> in.readUnsignedShort();
|
||||||
|
case 9, 10, 11, 12, 17, 18 -> {
|
||||||
|
in.readUnsignedShort();
|
||||||
|
in.readUnsignedShort();
|
||||||
|
}
|
||||||
|
case 15 -> {
|
||||||
|
in.readUnsignedByte();
|
||||||
|
in.readUnsignedShort();
|
||||||
|
}
|
||||||
|
default -> throw new IOException("Unknown constant pool tag " + tag + " while validating " + entryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String relocateClassPath(String path, Map<String, String> relocations) {
|
||||||
|
// Convert path to package format (replacing / with .)
|
||||||
|
String packagePath = path.substring(0, path.length() - 6).replace('/', '.');
|
||||||
|
|
||||||
|
// Apply relocations
|
||||||
|
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||||
|
if (packagePath.startsWith(relocation.getKey())) {
|
||||||
|
packagePath = relocation.getValue() + packagePath.substring(relocation.getKey().length());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert back to path format
|
||||||
|
return packagePath.replace('.', '/') + ".class";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String relocateResourcePath(String path, Map<String, String> relocations) {
|
||||||
|
if (path.startsWith("META-INF/")) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||||
|
String fromPath = relocation.getKey().replace('.', '/');
|
||||||
|
String toPath = relocation.getValue().replace('.', '/');
|
||||||
|
|
||||||
|
if (path.startsWith(fromPath + "/")) {
|
||||||
|
return toPath + path.substring(fromPath.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readAllBytes(InputStream is) throws IOException {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
int bytesRead;
|
||||||
|
byte[] data = new byte[1024];
|
||||||
|
while ((bytesRead = is.read(data, 0, data.length)) != -1) {
|
||||||
|
buffer.write(data, 0, bytesRead);
|
||||||
|
}
|
||||||
|
return buffer.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copyStream(InputStream is, OutputStream os) throws IOException {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = is.read(buffer)) != -1) {
|
||||||
|
os.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getLibFolder() {
|
||||||
|
File pluginDataFolder = AntiVPN.getInstance().getPluginFolder();
|
||||||
|
File libs = new File(pluginDataFolder, "libraries");
|
||||||
|
if(libs.mkdirs()) {
|
||||||
|
System.out.println("Created libraries folder!");
|
||||||
|
}
|
||||||
|
return libs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NonnullByDefault
|
||||||
|
// Fix the Dependency class to preserve original groupId for downloading
|
||||||
|
public static final class Dependency {
|
||||||
|
private final String groupId;
|
||||||
|
private final String artifactId;
|
||||||
|
private final String version;
|
||||||
|
private final String repoUrl;
|
||||||
|
// Keep the original groupId/artifactId for Maven downloads
|
||||||
|
private final String originalGroupId;
|
||||||
|
private final String originalArtifactId;
|
||||||
|
|
||||||
|
public Dependency(String groupId, String artifactId, String version, String repoUrl) {
|
||||||
|
this.originalGroupId = Objects.requireNonNull(groupId, "groupId");
|
||||||
|
this.originalArtifactId = Objects.requireNonNull(artifactId, "artifactId");
|
||||||
|
this.groupId = this.originalGroupId;
|
||||||
|
this.artifactId = this.originalArtifactId;
|
||||||
|
this.version = Objects.requireNonNull(version, "version");
|
||||||
|
this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getUrl() throws MalformedURLException {
|
||||||
|
String repo = this.repoUrl;
|
||||||
|
if (!repo.endsWith("/")) {
|
||||||
|
repo += "/";
|
||||||
|
}
|
||||||
|
repo += "%s/%s/%s/%s-%s.jar";
|
||||||
|
|
||||||
|
// Always use original groupId for Maven repository URL
|
||||||
|
String url = String.format(repo, this.originalGroupId.replace(".", "/"),
|
||||||
|
this.originalArtifactId, this.version, this.originalArtifactId, this.version);
|
||||||
|
return new URL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rest of the class unchanged
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.depends;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to indicate the required libraries for a class.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface MavenLibraries {
|
||||||
|
|
||||||
|
MavenLibrary[] value() default {};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.depends;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to indicate a required library for a class.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Repeatable(MavenLibraries.class)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface MavenLibrary {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The group id of the library
|
||||||
|
*
|
||||||
|
* @return the group id of the library
|
||||||
|
*/
|
||||||
|
String groupId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The artifact id of the library
|
||||||
|
*
|
||||||
|
* @return the artifact id of the library
|
||||||
|
*/
|
||||||
|
String artifactId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version of the library
|
||||||
|
*
|
||||||
|
* @return the version of the library
|
||||||
|
*/
|
||||||
|
String version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The repo where the library can be obtained from
|
||||||
|
*
|
||||||
|
* @return the repo where the library can be obtained from
|
||||||
|
*/
|
||||||
|
Repository repo() default @Repository(url = "https://repo1.maven.org/maven2");
|
||||||
|
|
||||||
|
Relocate[] relocations() default {}; // Add this line
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.depends;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({})
|
||||||
|
public @interface Relocate {
|
||||||
|
String from();
|
||||||
|
String to();
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.depends;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a maven repository.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target(ElementType.LOCAL_VARIABLE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Repository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the base url of the repository.
|
||||||
|
*
|
||||||
|
* @return the base url of the repository
|
||||||
|
*/
|
||||||
|
String url();
|
||||||
|
|
||||||
|
}
|
||||||
+10
-19
@@ -1,26 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of helper, licensed under the MIT License.
|
* Copyright 2026 Dawson Hessler
|
||||||
*
|
*
|
||||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* Copyright (c) contributors
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* copies or substantial portions of the Software.
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
*
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* See the License for the specific language governing permissions and
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* limitations under the License.
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.depends;
|
package dev.brighten.antivpn.depends;
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.message;
|
package dev.brighten.antivpn.message;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.message;
|
package dev.brighten.antivpn.message;
|
||||||
|
|
||||||
import dev.brighten.antivpn.api.APIPlayer;
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that enables to get an IP range from CIDR specification. It supports
|
||||||
|
* both IPv4 and IPv6.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class CIDRUtils {
|
||||||
|
private final String cidr;
|
||||||
|
|
||||||
|
private final InetAddress inetAddress;
|
||||||
|
|
||||||
|
private InetAddress startAddress;
|
||||||
|
private BigInteger startIpInt, endIpInt;
|
||||||
|
private InetAddress endAddress;
|
||||||
|
private final int prefixLength;
|
||||||
|
|
||||||
|
|
||||||
|
public CIDRUtils(String cidr) throws UnknownHostException {
|
||||||
|
|
||||||
|
this.cidr = cidr;
|
||||||
|
|
||||||
|
/* split CIDR to address and prefix part */
|
||||||
|
if (this.cidr.contains("/")) {
|
||||||
|
int index = this.cidr.indexOf("/");
|
||||||
|
String addressPart = this.cidr.substring(0, index);
|
||||||
|
String networkPart = this.cidr.substring(index + 1);
|
||||||
|
|
||||||
|
inetAddress = InetAddress.getByName(addressPart);
|
||||||
|
prefixLength = Integer.parseInt(networkPart);
|
||||||
|
|
||||||
|
calculate();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("not an valid CIDR format!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void calculate() throws UnknownHostException {
|
||||||
|
|
||||||
|
ByteBuffer maskBuffer;
|
||||||
|
int targetSize;
|
||||||
|
if (inetAddress.getAddress().length == 4) {
|
||||||
|
maskBuffer =
|
||||||
|
ByteBuffer
|
||||||
|
.allocate(4)
|
||||||
|
.putInt(-1);
|
||||||
|
targetSize = 4;
|
||||||
|
} else {
|
||||||
|
maskBuffer = ByteBuffer.allocate(16)
|
||||||
|
.putLong(-1L)
|
||||||
|
.putLong(-1L);
|
||||||
|
targetSize = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength);
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress());
|
||||||
|
BigInteger ipVal = new BigInteger(1, buffer.array());
|
||||||
|
|
||||||
|
BigInteger startIp = ipVal.and(mask);
|
||||||
|
this.startIpInt = startIp;
|
||||||
|
BigInteger endIp = startIp.add(mask.not());
|
||||||
|
this.endIpInt = endIp;
|
||||||
|
|
||||||
|
byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize);
|
||||||
|
byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize);
|
||||||
|
|
||||||
|
this.startAddress = InetAddress.getByAddress(startIpArr);
|
||||||
|
this.endAddress = InetAddress.getByAddress(endIpArr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] toBytes(byte[] array, int targetSize) {
|
||||||
|
int counter = 0;
|
||||||
|
List<Byte> newArr = new ArrayList<Byte>();
|
||||||
|
while (counter < targetSize && (array.length - 1 - counter >= 0)) {
|
||||||
|
newArr.add(0, array[array.length - 1 - counter]);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = newArr.size();
|
||||||
|
for (int i = 0; i < (targetSize - size); i++) {
|
||||||
|
|
||||||
|
newArr.add(0, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] ret = new byte[newArr.size()];
|
||||||
|
for (int i = 0; i < newArr.size(); i++) {
|
||||||
|
ret[i] = newArr.get(i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInRange(String ipAddress) throws UnknownHostException {
|
||||||
|
InetAddress address = InetAddress.getByName(ipAddress);
|
||||||
|
BigInteger start = new BigInteger(1, this.startAddress.getAddress());
|
||||||
|
BigInteger end = new BigInteger(1, this.endAddress.getAddress());
|
||||||
|
BigInteger target = new BigInteger(1, address.getAddress());
|
||||||
|
|
||||||
|
int st = start.compareTo(target);
|
||||||
|
int te = target.compareTo(end);
|
||||||
|
|
||||||
|
return (st < 0 || st == 0) && (te < 0 || te == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.utils;
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
import dev.brighten.antivpn.AntiVPN;
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class EvictingMap<K, V> extends LinkedHashMap<K, V> {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||||
|
return size() >= size;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder for extra methods of {@code Objects} only in web. Intended to be empty for regular
|
||||||
|
* version.
|
||||||
|
*/
|
||||||
|
abstract class ExtraObjectsMethodsForWeb {}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class IpUtils {
|
||||||
|
public static Optional<BigDecimal> getIpDecimal(String address) {
|
||||||
|
try {
|
||||||
|
InetAddress inet = InetAddress.getByName(address);
|
||||||
|
|
||||||
|
if(inet instanceof Inet4Address) {
|
||||||
|
return Optional.of(BigDecimal.valueOf(ipv4ToLong(address)));
|
||||||
|
} return Optional.of(new BigDecimal(ipv6ToDecimalFormat(address)));
|
||||||
|
} catch(Exception e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long ipv4ToLong(String address) {
|
||||||
|
String[] addrArray = address.split("\\.");
|
||||||
|
|
||||||
|
long ipDecimal = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < addrArray.length; i++) {
|
||||||
|
|
||||||
|
int power = 3 - i;
|
||||||
|
ipDecimal += ((Integer.parseInt(addrArray[i]) % 256 * Math.pow(256, power)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipDecimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIpv4(long ip) {
|
||||||
|
StringBuilder sb = new StringBuilder(15);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
sb.insert(0, ip & 0xff);
|
||||||
|
|
||||||
|
if (i < 3) {
|
||||||
|
sb.insert(0, '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
ip >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isIpv4(BigDecimal ip) {
|
||||||
|
return ip.compareTo(BigDecimal.valueOf(4294967295L)) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isIpv6(BigDecimal ip) {
|
||||||
|
return ip.compareTo(BigDecimal.valueOf(4294967295L)) > 0;
|
||||||
|
}
|
||||||
|
public static boolean isIpv4(String ip) {
|
||||||
|
return ip.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNotIp(String ip) {
|
||||||
|
return !isIpv4(ip) && !isIpv6(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isIpv6(String ip) {
|
||||||
|
return ip.matches("^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)$|^(([0-9a-fA-F]{1,4}:){0,6}([0-9a-fA-F]{1,4}|:))?(::([0-9a-fA-F]{1,4}:){0,5}([0-9a-fA-F]{1,4}|:))?$");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIpv4(BigDecimal ip) {
|
||||||
|
try {
|
||||||
|
return Inet4Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIpv6(BigDecimal ip) {
|
||||||
|
try {
|
||||||
|
return Inet6Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger ipv6ToDecimalFormat(String ipAddress) throws UnknownHostException {
|
||||||
|
return new BigInteger(1, Inet6Address.getByName(ipAddress).getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
import dev.brighten.antivpn.AntiVPN;
|
||||||
|
import dev.brighten.antivpn.utils.json.JSONException;
|
||||||
|
import dev.brighten.antivpn.utils.json.JSONObject;
|
||||||
|
import dev.brighten.antivpn.utils.json.JsonReader;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class MiscUtils {
|
||||||
|
|
||||||
|
private static final Pattern ipv4 = Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
|
||||||
|
private static final String DEFAULT_FUNKEMUNKY_UUID_ENDPOINT = "https://funkemunky.cc/mojang/uuid?name=";
|
||||||
|
private static final String DEFAULT_MOJANG_UUID_ENDPOINT = "https://api.mojang.com/users/profiles/minecraft/";
|
||||||
|
private static volatile String funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT;
|
||||||
|
private static volatile String mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT;
|
||||||
|
|
||||||
|
public static void close(Closeable... closeables) {
|
||||||
|
try {
|
||||||
|
for (Closeable closeable : closeables) if (closeable != null) closeable.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void close(AutoCloseable... closeables) {
|
||||||
|
try {
|
||||||
|
for (AutoCloseable closeable : closeables) if (closeable != null) closeable.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copy(InputStream in, File file) {
|
||||||
|
try {
|
||||||
|
OutputStream out = new FileOutputStream(file);
|
||||||
|
int lenght;
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
|
||||||
|
while ((lenght = in.read(buf)) > 0)
|
||||||
|
{
|
||||||
|
out.write(buf, 0, lenght);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
in.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ThreadFactory createThreadFactory(String threadName) {
|
||||||
|
return r -> {
|
||||||
|
Thread thread = new Thread(r);
|
||||||
|
thread.setName(threadName);
|
||||||
|
return thread;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CIDRUtils> rangeToCidrs(BigInteger start, BigInteger end) throws UnknownHostException {
|
||||||
|
List<CIDRUtils> cidrs = new ArrayList<>();
|
||||||
|
|
||||||
|
while (start.compareTo(end) <= 0) {
|
||||||
|
// Find the number of trailing zero bits — this determines max block size alignment
|
||||||
|
int trailingZeros = start.equals(BigInteger.ZERO)
|
||||||
|
? 128 // handle the edge case
|
||||||
|
: start.getLowestSetBit();
|
||||||
|
|
||||||
|
// Find the largest block that fits
|
||||||
|
BigInteger remaining = end.subtract(start).add(BigInteger.ONE);
|
||||||
|
int maxBits = remaining.bitLength() - 1;
|
||||||
|
|
||||||
|
int blockBits = Math.min(trailingZeros, maxBits);
|
||||||
|
int prefixLen = 32 - blockBits; // use 128 for IPv6
|
||||||
|
|
||||||
|
// Build the CIDR string
|
||||||
|
byte[] addrBytes = toFixedLengthBytes(start); // use 16 for IPv6
|
||||||
|
String cidr = InetAddress.getByAddress(addrBytes).getHostAddress() + "/" + prefixLen;
|
||||||
|
cidrs.add(new CIDRUtils(cidr));
|
||||||
|
|
||||||
|
// Advance past this block
|
||||||
|
start = start.add(BigInteger.ONE.shiftLeft(blockBits));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cidrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] toFixedLengthBytes(BigInteger value) {
|
||||||
|
byte[] raw = value.toByteArray();
|
||||||
|
byte[] result = new byte[4];
|
||||||
|
int srcPos = Math.max(0, raw.length - 4);
|
||||||
|
int destPos = Math.max(0, 4 - raw.length);
|
||||||
|
System.arraycopy(raw, srcPos, result, destPos, Math.min(raw.length, 4));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UUID lookupUUID(String playername) {
|
||||||
|
try {
|
||||||
|
UUID uuid = lookupUuidFromUrl(funkemunkyUuidEndpoint + playername);
|
||||||
|
if (uuid != null) {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
} catch (IOException | JSONException | URISyntaxException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playername + "! Falling back to Mojang API", e);
|
||||||
|
return lookupMojangUuid(playername);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UUID lookupUuidFromUrl(String url) throws IOException, JSONException, URISyntaxException {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection();
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
connection.setInstanceFollowRedirects(true);
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode >= 500) {
|
||||||
|
throw new IOException("Server returned HTTP " + responseCode + " for " + url);
|
||||||
|
}
|
||||||
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream inputStream = connection.getInputStream()) {
|
||||||
|
JSONObject object = new JSONObject(JsonReader.readAll(new InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8)));
|
||||||
|
if (object.has("uuid")) {
|
||||||
|
return parseUuid(object.getString("uuid"));
|
||||||
|
}
|
||||||
|
if (object.has("id")) {
|
||||||
|
return parseUuid(object.getString("id"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UUID parseUuid(String value) {
|
||||||
|
if (value.length() == 32) {
|
||||||
|
value = value.replaceFirst(
|
||||||
|
"([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})",
|
||||||
|
"$1-$2-$3-$4-$5"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return UUID.fromString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UUID lookupMojangUuid(String playerName) {
|
||||||
|
try {
|
||||||
|
return lookupUuidFromUrl(mojangUuidEndpoint + playerName);
|
||||||
|
} catch (IOException | JSONException | URISyntaxException e) {
|
||||||
|
AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playerName + " from Mojang!:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setLookupEndpointsForTesting(String funkemunkyEndpoint, String mojangEndpoint) {
|
||||||
|
funkemunkyUuidEndpoint = funkemunkyEndpoint;
|
||||||
|
mojangUuidEndpoint = mojangEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resetLookupEndpointsForTesting() {
|
||||||
|
funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT;
|
||||||
|
mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT;
|
||||||
|
}
|
||||||
|
public static boolean isIpv4(String ip)
|
||||||
|
{
|
||||||
|
return ipv4.matcher(ip).matches();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface NonnullByDefault {
|
||||||
|
|
||||||
|
}
|
||||||
+10
-8
@@ -1,15 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021 The Guava Authors
|
* Copyright 2026 Dawson Hessler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* in compliance with the License. You may obtain a copy of the License at
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* or implied. See the License for the specific language governing permissions and limitations under
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* the License.
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.utils;
|
package dev.brighten.antivpn.utils;
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// Source code recreated from a .class file by IntelliJ IDEA
|
// Source code recreated from a .class file by IntelliJ IDEA
|
||||||
// (powered by FernFlower decompiler)
|
// (powered by FernFlower decompiler)
|
||||||
+18
-6
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.utils;
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
import dev.brighten.antivpn.api.APIPlayer;
|
import dev.brighten.antivpn.api.APIPlayer;
|
||||||
@@ -12,15 +28,11 @@ public class StringUtil {
|
|||||||
return "&m-----------------------------------------------------";
|
return "&m-----------------------------------------------------";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String lineNoStrike(String color) {
|
|
||||||
return color + "-----------------------------------------------------";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String varReplace(String input, APIPlayer player, VPNResponse result) {
|
public static String varReplace(String input, APIPlayer player, VPNResponse result) {
|
||||||
return input.replace("%player%", player.getName())
|
return translateAlternateColorCodes('&', input.replace("%player%", player.getName())
|
||||||
.replace("%reason%", result.getMethod())
|
.replace("%reason%", result.getMethod())
|
||||||
.replace("%country%", result.getCountryName())
|
.replace("%country%", result.getCountryName())
|
||||||
.replace("%city%", result.getCity());
|
.replace("%city%", result.getCity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
|
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Source code recreated from a .class file by IntelliJ IDEA
|
||||||
|
// (powered by FernFlower decompiler)
|
||||||
|
//
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Supplier<T> extends java.util.function.Supplier<T> {
|
||||||
|
T get();
|
||||||
|
}
|
||||||
+10
-8
@@ -1,15 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2007 The Guava Authors
|
* Copyright 2026 Dawson Hessler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* in compliance with the License. You may obtain a copy of the License at
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* or implied. See the License for the specific language governing permissions and limitations under
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* the License.
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.utils;
|
package dev.brighten.antivpn.utils;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.brighten.antivpn.utils;
|
||||||
|
|
||||||
|
public record Tuple<F, S>(F first, S second) {
|
||||||
|
|
||||||
|
}
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.utils.config;
|
package dev.brighten.antivpn.utils.config;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.utils.config;
|
package dev.brighten.antivpn.utils.config;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
+16
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package dev.brighten.antivpn.utils.config;
|
package dev.brighten.antivpn.utils.config;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
+15
-23
@@ -1,28 +1,20 @@
|
|||||||
package dev.brighten.antivpn.utils.json;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright (c) 2002 JSON.org
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
package dev.brighten.antivpn.utils.json;
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
The Software shall be used for Good, not Evil.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This provides static methods to convert comma delimited text into a
|
* This provides static methods to convert comma delimited text into a
|
||||||
+15
-23
@@ -1,28 +1,20 @@
|
|||||||
package dev.brighten.antivpn.utils.json;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright (c) 2002 JSON.org
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
package dev.brighten.antivpn.utils.json;
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
The Software shall be used for Good, not Evil.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a web browser cookie specification to a JSONObject and back.
|
* Convert a web browser cookie specification to a JSONObject and back.
|
||||||
+15
-23
@@ -1,28 +1,20 @@
|
|||||||
package dev.brighten.antivpn.utils.json;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright (c) 2002 JSON.org
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
package dev.brighten.antivpn.utils.json;
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
The Software shall be used for Good, not Evil.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
+15
-23
@@ -1,28 +1,20 @@
|
|||||||
package dev.brighten.antivpn.utils.json;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright (c) 2002 JSON.org
|
* Copyright 2026 Dawson Hessler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
package dev.brighten.antivpn.utils.json;
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
The Software shall be used for Good, not Evil.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user