mirror of
https://github.com/funkemunky/AntiVPN.git
synced 2026-05-31 09:31:54 +00:00
Add Discord and Slack webhook format support with configurable format option
Co-authored-by: funkemunky <30784509+funkemunky@users.noreply.github.com>
This commit is contained in:
@@ -45,10 +45,9 @@ public class VPNConfig {
|
||||
defaultAlertMsg = new ConfigDefault<>("&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy" +
|
||||
" &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)", "alerts.message",
|
||||
AntiVPN.getInstance()),
|
||||
defaultWebhookUrl = new ConfigDefault<>("", "webhooks.url",
|
||||
AntiVPN.getInstance()),
|
||||
defaultWebhookAuthToken = new ConfigDefault<>("", "webhooks.authToken",
|
||||
AntiVPN.getInstance());
|
||||
defaultWebhookUrl = new ConfigDefault<>("", "webhooks.url", AntiVPN.getInstance()),
|
||||
defaultWebhookAuthToken = new ConfigDefault<>("", "webhooks.authToken", AntiVPN.getInstance()),
|
||||
defaultWebhookFormat = new ConfigDefault<>("discord", "webhooks.format", AntiVPN.getInstance());
|
||||
private final ConfigDefault<Boolean> cacheResultsDefault = new ConfigDefault<>(true,
|
||||
"cachedResults", AntiVPN.getInstance()),
|
||||
defaultUseCredentials = new ConfigDefault<>(true,
|
||||
@@ -62,14 +61,11 @@ public class VPNConfig {
|
||||
defaultWhitelistCountries = new ConfigDefault<>(true, "countries.whitelist",
|
||||
AntiVPN.getInstance()),
|
||||
defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance()),
|
||||
defaultWebhookEnabled = new ConfigDefault<>(false, "webhooks.enabled",
|
||||
AntiVPN.getInstance()),
|
||||
defaultWebhookUseAuth = new ConfigDefault<>(false, "webhooks.useAuthentication",
|
||||
AntiVPN.getInstance());
|
||||
defaultWebhookEnabled = new ConfigDefault<>(false, "webhooks.enabled", AntiVPN.getInstance()),
|
||||
defaultWebhookUseAuth = new ConfigDefault<>(false, "webhooks.useAuthentication", AntiVPN.getInstance());
|
||||
private final ConfigDefault<Integer>
|
||||
defaultPort = new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance()),
|
||||
defaultWebhookTimeout = new ConfigDefault<>(5, "webhooks.timeout",
|
||||
AntiVPN.getInstance());
|
||||
defaultWebhookTimeout = new ConfigDefault<>(5, "webhooks.timeout", AntiVPN.getInstance());
|
||||
private final ConfigDefault<List<String>> prefixWhitelistsDefault = new ConfigDefault<>(new ArrayList<>(),
|
||||
"prefixWhitelists", AntiVPN.getInstance()), defaultCommands = new ConfigDefault<>(
|
||||
Collections.singletonList("kick %player% VPNs are not allowed on our server!"), "commands.execute",
|
||||
@@ -79,23 +75,6 @@ public class VPNConfig {
|
||||
defCountrylist = new ConfigDefault<>(new ArrayList<>(), "countries.list",
|
||||
AntiVPN.getInstance());
|
||||
|
||||
private String license, kickMessage, databaseType, databaseName, mongoURL, username, password, ip, alertMsg,
|
||||
countryVanillaKickReason, webhookUrl, webhookAuthToken;
|
||||
private List<String> prefixWhitelists, commands, countryList, countryKickCommands;
|
||||
private int port, webhookTimeout;
|
||||
private boolean cacheResults, databaseEnabled, useCredentials, commandsEnabled, kickPlayers, alertToStaff,
|
||||
metrics, whitelistCountries, webhookEnabled, webhookUseAuth;
|
||||
|
||||
/**
|
||||
* License from https://funkemunky.cc/shop to be used for more queries.
|
||||
* @return String
|
||||
*/
|
||||
public String getLicense() {
|
||||
return license;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, results will be cached to reduce queries to https://funkemunky.cc
|
||||
@Getter
|
||||
private String license;
|
||||
@Getter
|
||||
@@ -133,6 +112,18 @@ public class VPNConfig {
|
||||
private boolean alertToStaff;
|
||||
private boolean metrics;
|
||||
private boolean whitelistCountries;
|
||||
@Getter
|
||||
private String webhookUrl;
|
||||
@Getter
|
||||
private String webhookAuthToken;
|
||||
@Getter
|
||||
private String webhookFormat;
|
||||
@Getter
|
||||
private boolean webhookEnabled;
|
||||
@Getter
|
||||
private boolean webhookUseAuth;
|
||||
@Getter
|
||||
private int webhookTimeout;
|
||||
|
||||
/**
|
||||
* If true, results will be cached to reduce queries to <a href="https://funkemunky.cc">...</a>
|
||||
@@ -220,48 +211,6 @@ public class VPNConfig {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, webhook notifications will be sent when a VPN is detected.
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean webhookEnabled() {
|
||||
return webhookEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* The webhook URL to send POST requests to when a VPN is detected.
|
||||
* @return String
|
||||
*/
|
||||
public String webhookUrl() {
|
||||
return webhookUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, an authentication header will be included in webhook requests.
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean webhookUseAuth() {
|
||||
return webhookUseAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* The authentication token to use for webhook requests.
|
||||
* Note: Token is stored in memory as plaintext. Ensure proper file system
|
||||
* permissions are set on config.yml to protect sensitive authentication tokens.
|
||||
* @return String
|
||||
*/
|
||||
public String webhookAuthToken() {
|
||||
return webhookAuthToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timeout in seconds for webhook requests.
|
||||
* @return int
|
||||
*/
|
||||
public int webhookTimeout() {
|
||||
return webhookTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs all information from the config.yml
|
||||
*/
|
||||
@@ -294,6 +243,7 @@ public class VPNConfig {
|
||||
webhookUseAuth = defaultWebhookUseAuth.get();
|
||||
webhookAuthToken = defaultWebhookAuthToken.get();
|
||||
webhookTimeout = defaultWebhookTimeout.get();
|
||||
webhookFormat = defaultWebhookFormat.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,8 +80,6 @@ public abstract class VPNExecutor {
|
||||
// Send webhook notification if enabled
|
||||
WebhookNotifier.sendWebhookNotification(player, result);
|
||||
|
||||
if (AntiVPN.getInstance().getVpnConfig().alertToStaff()) AntiVPN.getInstance().getPlayerExecutor()
|
||||
|
||||
//Ensuring kick task is always running
|
||||
if(kickTask == null || kickTask.isDone() || kickTask.isCancelled()) {
|
||||
startKickChecks();
|
||||
|
||||
@@ -26,11 +26,11 @@ public class WebhookNotifier {
|
||||
* @param result The check result containing VPN information
|
||||
*/
|
||||
public static void sendWebhookNotification(APIPlayer player, CheckResult result) {
|
||||
if (!AntiVPN.getInstance().getVpnConfig().webhookEnabled()) {
|
||||
if (!AntiVPN.getInstance().getVpnConfig().isWebhookEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String webhookUrl = AntiVPN.getInstance().getVpnConfig().webhookUrl();
|
||||
String webhookUrl = AntiVPN.getInstance().getVpnConfig().getWebhookUrl();
|
||||
if (webhookUrl == null || webhookUrl.trim().isEmpty()) {
|
||||
AntiVPN.getInstance().getExecutor().log(Level.WARNING,
|
||||
"Webhook is enabled but no URL is configured. Please set webhooks.url in config.yml");
|
||||
@@ -44,7 +44,7 @@ public class WebhookNotifier {
|
||||
} catch (Exception e) {
|
||||
AntiVPN.getInstance().getExecutor().logException("Failed to send webhook notification", e);
|
||||
}
|
||||
}, dev.brighten.antivpn.api.VPNExecutor.threadExecutor);
|
||||
}, AntiVPN.getInstance().getExecutor().getThreadExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,16 +67,16 @@ public class WebhookNotifier {
|
||||
connection.setRequestProperty("User-Agent", "AntiVPN-Webhook/1.0");
|
||||
|
||||
// Add authentication header if configured
|
||||
if (AntiVPN.getInstance().getVpnConfig().webhookUseAuth()) {
|
||||
String token = AntiVPN.getInstance().getVpnConfig().webhookAuthToken();
|
||||
if (AntiVPN.getInstance().getVpnConfig().isWebhookUseAuth()) {
|
||||
String token = AntiVPN.getInstance().getVpnConfig().getWebhookAuthToken();
|
||||
if (token != null && !token.trim().isEmpty()) {
|
||||
connection.setRequestProperty("Authorization", "Bearer " + token);
|
||||
}
|
||||
}
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(AntiVPN.getInstance().getVpnConfig().webhookTimeout() * 1000);
|
||||
connection.setReadTimeout(AntiVPN.getInstance().getVpnConfig().webhookTimeout() * 1000);
|
||||
connection.setConnectTimeout(AntiVPN.getInstance().getVpnConfig().getWebhookTimeout() * 1000);
|
||||
connection.setReadTimeout(AntiVPN.getInstance().getVpnConfig().getWebhookTimeout() * 1000);
|
||||
|
||||
// Create JSON payload
|
||||
JSONObject payload = createPayload(player, result);
|
||||
@@ -113,6 +113,141 @@ public class WebhookNotifier {
|
||||
* @throws JSONException If there's an error creating the JSON
|
||||
*/
|
||||
private static JSONObject createPayload(APIPlayer player, CheckResult result) throws JSONException {
|
||||
String format = AntiVPN.getInstance().getVpnConfig().getWebhookFormat().toLowerCase();
|
||||
|
||||
switch (format) {
|
||||
case "discord":
|
||||
return createDiscordPayload(player, result);
|
||||
case "slack":
|
||||
return createSlackPayload(player, result);
|
||||
default:
|
||||
return createGenericPayload(player, result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Discord-formatted webhook payload with rich embeds.
|
||||
*
|
||||
* @param player The player information
|
||||
* @param result The check result
|
||||
* @return JSONObject containing the Discord-formatted payload
|
||||
* @throws JSONException If there's an error creating the JSON
|
||||
*/
|
||||
private static JSONObject createDiscordPayload(APIPlayer player, CheckResult result) throws JSONException {
|
||||
JSONObject payload = new JSONObject();
|
||||
|
||||
// Create embed
|
||||
JSONObject embed = new JSONObject();
|
||||
|
||||
// Set title and color based on result type
|
||||
if (result.resultType().name().equals("DENIED_PROXY")) {
|
||||
embed.put("title", "🚫 VPN/Proxy Detection");
|
||||
embed.put("color", 15158332); // Red color
|
||||
} else if (result.resultType().name().equals("DENIED_COUNTRY")) {
|
||||
embed.put("title", "🌍 Country Blocked");
|
||||
embed.put("color", 15105570); // Orange color
|
||||
}
|
||||
|
||||
// Add description
|
||||
embed.put("description", "A player attempted to join using a VPN/proxy or from a blocked country.");
|
||||
|
||||
// Add fields with player and detection information
|
||||
JSONObject[] fields = new JSONObject[0];
|
||||
if (result.response() != null) {
|
||||
fields = new JSONObject[] {
|
||||
createDiscordField("Player", player.getName(), true),
|
||||
createDiscordField("UUID", player.getUuid().toString(), true),
|
||||
createDiscordField("IP Address", player.getIp().getHostAddress(), true),
|
||||
createDiscordField("Country", result.response().getCountryName() + " (" + result.response().getCountryCode() + ")", true),
|
||||
createDiscordField("City", result.response().getCity(), true),
|
||||
createDiscordField("ISP", result.response().getIsp(), true),
|
||||
createDiscordField("ASN", result.response().getAsn(), true),
|
||||
createDiscordField("Detection Method", result.response().getMethod() != null ? result.response().getMethod() : "N/A", true),
|
||||
createDiscordField("Proxy Status", result.response().isProxy() ? "✓ Detected" : "✗ Not Detected", true)
|
||||
};
|
||||
} else {
|
||||
fields = new JSONObject[] {
|
||||
createDiscordField("Player", player.getName(), true),
|
||||
createDiscordField("UUID", player.getUuid().toString(), true),
|
||||
createDiscordField("IP Address", player.getIp().getHostAddress(), true)
|
||||
};
|
||||
}
|
||||
|
||||
embed.put("fields", fields);
|
||||
|
||||
// Add timestamp in ISO 8601 format
|
||||
java.time.Instant instant = java.time.Instant.ofEpochMilli(System.currentTimeMillis());
|
||||
embed.put("timestamp", instant.toString());
|
||||
|
||||
// Add footer
|
||||
JSONObject footer = new JSONObject();
|
||||
footer.put("text", "AntiVPN Detection System");
|
||||
embed.put("footer", footer);
|
||||
|
||||
// Add embed to payload
|
||||
payload.put("embeds", new JSONObject[] { embed });
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a Discord embed field.
|
||||
*
|
||||
* @param name Field name
|
||||
* @param value Field value
|
||||
* @param inline Whether the field should be inline
|
||||
* @return JSONObject containing the field
|
||||
* @throws JSONException If there's an error creating the JSON
|
||||
*/
|
||||
private static JSONObject createDiscordField(String name, String value, boolean inline) throws JSONException {
|
||||
JSONObject field = new JSONObject();
|
||||
field.put("name", name);
|
||||
field.put("value", value != null ? value : "N/A");
|
||||
field.put("inline", inline);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Slack-formatted webhook payload.
|
||||
*
|
||||
* @param player The player information
|
||||
* @param result The check result
|
||||
* @return JSONObject containing the Slack-formatted payload
|
||||
* @throws JSONException If there's an error creating the JSON
|
||||
*/
|
||||
private static JSONObject createSlackPayload(APIPlayer player, CheckResult result) throws JSONException {
|
||||
JSONObject payload = new JSONObject();
|
||||
|
||||
// Build text message
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append("*VPN/Proxy Detection Alert*\n");
|
||||
text.append("Player: ").append(player.getName()).append("\n");
|
||||
text.append("IP: ").append(player.getIp().getHostAddress()).append("\n");
|
||||
|
||||
if (result.response() != null) {
|
||||
text.append("Country: ").append(result.response().getCountryName())
|
||||
.append(" (").append(result.response().getCountryCode()).append(")\n");
|
||||
text.append("City: ").append(result.response().getCity()).append("\n");
|
||||
text.append("ISP: ").append(result.response().getIsp()).append("\n");
|
||||
if (result.response().getMethod() != null) {
|
||||
text.append("Method: ").append(result.response().getMethod()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
payload.put("text", text.toString());
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic JSON payload (original format).
|
||||
*
|
||||
* @param player The player information
|
||||
* @param result The check result
|
||||
* @return JSONObject containing the generic payload
|
||||
* @throws JSONException If there's an error creating the JSON
|
||||
*/
|
||||
private static JSONObject createGenericPayload(APIPlayer player, CheckResult result) throws JSONException {
|
||||
JSONObject payload = new JSONObject();
|
||||
|
||||
// Basic event information
|
||||
|
||||
@@ -46,6 +46,11 @@ webhooks:
|
||||
enabled: false
|
||||
# The webhook URL to send POST requests to when a VPN is detected
|
||||
url: ''
|
||||
# Webhook format type: 'discord', 'slack', or 'generic'
|
||||
# - discord: Formats payload for Discord webhooks with rich embeds
|
||||
# - slack: Formats payload for Slack webhooks
|
||||
# - generic: Sends raw JSON payload (for custom integrations)
|
||||
format: 'discord'
|
||||
# Optional: Set to true to include authentication header (Authorization: Bearer <token>)
|
||||
useAuthentication: false
|
||||
# The authentication token to use when useAuthentication is true
|
||||
|
||||
Reference in New Issue
Block a user