Initial Library Work done

This commit is contained in:
2023-05-21 19:58:15 +02:00
commit ffb5800924
63 changed files with 4523 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
.idea

4
HEADER Normal file
View File

@@ -0,0 +1,4 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/

36
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,36 @@
pipeline {
agent {
label "master"
}
tools {
jdk "JAVA17"
}
stages {
stage("Notify Discord") {
steps {
discordSend webhookURL: env.FDD_WH_ADMIN,
title: "Build Started: SDLink-Core #${BUILD_NUMBER}",
link: env.BUILD_URL,
description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})"
}
}
stage("Publish") {
steps {
sh "chmod +x ./gradlew"
sh "./gradlew clean spotlessCheck publish"
}
}
}
post {
always {
sh "./gradlew --stop"
deleteDir()
discordSend webhookURL: env.FDD_WH_ADMIN,
title: "Build Finished: SDLink-Core #${BUILD_NUMBER}",
link: env.BUILD_URL,
result: currentBuild.currentResult,
description: "Build: [${BUILD_NUMBER}](${env.BUILD_URL})\nStatus: ${currentBuild.currentResult}"
}
}
}

152
build.gradle Normal file
View File

@@ -0,0 +1,152 @@
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '7.0.0'
id 'maven-publish'
id "com.diffplug.spotless" version "6.13.0"
}
apply plugin: 'java'
java.toolchain.languageVersion = JavaLanguageVersion.of(16)
group 'com.hypherionmc.sdlink'
version = "${version_major}.${version_minor}.${version_patch}"
configurations {
shaded
shaded.transitive = true
implementation.extendsFrom(shaded)
}
repositories {
mavenCentral()
maven { url "https://maven.firstdarkdev.xyz/releases" }
maven { url "https://maven.firstdarkdev.xyz/snapshots" }
maven { url "https://m2.dv8tion.net/releases" }
maven { url "https://nexus.velocitypowered.com/repository/maven-public/" }
}
dependencies {
// Core dependencies, Shaded
// Discord
shaded("pw.chew:jda-chewtils:${chewtils}") {
exclude group: 'org.apache.commons'
}
shaded("net.dv8tion:JDA:${jda}") {
exclude module: 'opus-java'
exclude group: 'org.apache.commons'
}
shaded("club.minnced:discord-webhooks:${webhooks}")
// Utilities
shaded("org.apache.commons:commons-collections4:${commons4}")
shaded("com.github.oshi:oshi-core:${oshi}")
shaded("org.jasypt:jasypt:${jasypt}:lite")
shaded("io.jsondb:jsondb-core:${json_db}")
// Config
shaded("me.hypherionmc.moon-config:core:${moonconfig}")
shaded("me.hypherionmc.moon-config:toml:${moonconfig}")
// Compile Only, Not Shaded
// Logging
implementation("org.apache.logging.log4j:log4j-api:${log4j}")
implementation("org.apache.logging.log4j:log4j-core:${log4j}")
implementation("org.apache.logging.log4j:log4j-slf4j18-impl:${log4j}")
// Utilities
implementation("org.apache.commons:commons-lang3:${commons}")
implementation("commons-io:commons-io:${commonsio}")
implementation("com.google.code.gson:gson:${gson}")
implementation("com.google.guava:guava:31.1-jre")
}
shadowJar {
configurations = [project.configurations.shaded]
dependencies {
exclude(dependency('org.apache.logging.log4j:log4j-core:.*'))
exclude(dependency('org.apache.logging.log4j:log4j-core:.*'))
exclude(dependency('org.apache.logging.log4j:log4j-slf4j18-impl:.*'))
exclude(dependency('org.apache.commons:commons-lang3:.*'))
exclude(dependency('com.google.code.gson:.*'))
exclude(dependency('javax:.*'))
exclude(dependency('org.jetbrains:.*'))
exclude(dependency('net.java.dev.jna:.*'))
exclude(dependency('org.slf4j:.*'))
exclude 'org/slf4j/**'
exclude 'META-INF/versions/9/**'
exclude 'module-info.class'
exclude 'org/apache/commons/lang3/**'
relocate 'org.apache.commons.collections4', shade_group + 'apache.commons.collections4'
relocate 'javax.annotation', shade_group + 'javax.annotation'
relocate 'gnu.trove', shade_group + 'gnu.trove'
relocate 'com.fasterxml', shade_group + 'fasterxml'
relocate 'club.minnced', shade_group + 'club.minnced'
relocate 'com.iwebpp', shade_group + 'iwebpp'
relocate 'com.jagrosh', shade_group + 'jagrosh'
relocate 'com.neovisionaries', shade_group + 'neovisionaries'
relocate 'me.hypherionmc.moonconfig', shade_group + 'moonconfig'
relocate 'me.hypherionmc.jqlite', shade_group + 'jqlite'
relocate 'net.dv8tion', shade_group + 'dv8tion'
relocate 'okhttp3', shade_group + 'okhttp3'
relocate 'okio', shade_group + 'okio'
relocate 'org.json', shade_group + 'json'
relocate 'org.sqlite', shade_group + 'sqlite'
relocate 'pw.chew', shade_group + 'chew'
relocate 'oshi', shade_group + 'oshi'
relocate 'kotlin', shade_group + 'kotlin'
relocate 'org.jasypt', shade_group + 'jasypt'
relocate 'com.google', shade_group + 'google'
relocate 'edu', shade_group + 'edu'
relocate 'io', shade_group + 'io'
relocate 'javassist', shade_group + 'javassist'
relocate 'net', shade_group + 'net'
relocate 'org.apache.commons.beanutils', shade_group + 'org.apache.commons.beanutils'
relocate 'org.apache.commons.collections', shade_group + 'org.apache.commons.collections'
relocate 'org.apache.commons.jxpath', shade_group + 'org.apache.commons.jxpath'
relocate 'org.apache.commons.logging', shade_group + 'org.apache.commons.logging'
relocate 'org.reflections', shade_group + 'org.reflections'
}
exclude 'META-INF/**'
setArchiveClassifier('')
}
publishing {
publications {
mavenJava(MavenPublication) {
artifact(shadowJar) {
builtBy shadowJar
}
}
}
repositories {
maven {
url System.getenv("MAVEN_URL")
credentials {
username System.getenv("MAVEN_USER")
password System.getenv("MAVEN_PASS")
}
}
}
}
spotless {
java {
licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-")
}
}
tasks.withType(JavaCompile).configureEach {
it.options.encoding = 'UTF-8'
it.options.release = 16
}
tasks.withType(GenerateModuleMetadata) {
enabled = false
}
build.finalizedBy(shadowJar)

21
gradle.properties Normal file
View File

@@ -0,0 +1,21 @@
version_major=0
version_minor=0
version_patch=2
shade_group=com.hypherionmc.sdlink.shaded.
# Core Dependencies
jda=5.0.0-beta.9
chewtils=2.0-SNAPSHOT
webhooks=0.7.5
commons4=4.4
oshi=5.8.5
moonconfig=1.0.9
jasypt=1.9.3
json_db=1.0.106
# Optional Dependencies
log4j=2.17.2
commons=3.12.0
commonsio=2.11.0
gson=2.10.1

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Sat May 20 21:34:44 SAST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Normal file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

21
license.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-2023 HypherionSA and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
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 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.

21
readme.md Normal file
View File

@@ -0,0 +1,21 @@
### SDLink Core
Minecraft independent code used by Simple Discord Link. This library contains all the core discord code, and code that is not
dependent on Minecraft.
Requires JAVA 16 and Above!
***
#### Building Instructions (For contributors)
```gradle
// Build a jar
gradlew build
// To Publish
gradlew publish
// Publish to mavenLocal()
gradlew publishToMavenLocal
```

2
settings.gradle Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name = 'sdlink-core'

View File

@@ -0,0 +1,61 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.accounts;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Represents a Message Author for messages sent from Minecraft to Discord
*/
public class DiscordAuthor {
// User used for Server Messages
public static final DiscordAuthor SERVER = new DiscordAuthor(sdLinkConfig.channelsAndWebhooks.serverName, sdLinkConfig.channelsAndWebhooks.serverAvatar, true);
private final String username;
private final String avatar;
private final boolean isServer;
/**
* Internal. Use {@link #of(String, String)}
* @param username The Username of the Author
* @param avatar The avatar URL of the Author
* @param isServer Is the Author the Minecraft Server
*/
private DiscordAuthor(String username, String avatar, boolean isServer) {
this.username = username;
this.avatar = avatar;
this.isServer = isServer;
}
/**
* Create a new Discord Author
* @param username The name/Username of the Author
* @param uuid The Mojang UUID of the Author
* @return A constructed {@link DiscordAuthor}
*/
public static DiscordAuthor of(String username, String uuid) {
return new DiscordAuthor(
username,
SDLinkPlatform.minecraftHelper.isOnlineMode() ? sdLinkConfig.chatConfig.playerAvatarType.resolve(uuid) : username,
false
);
}
public String getUsername() {
return username;
}
public boolean isServer() {
return isServer;
}
public String getAvatar() {
return avatar;
}
}

View File

@@ -0,0 +1,344 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.accounts;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.managers.RoleManager;
import com.hypherionmc.sdlink.core.messaging.Result;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.UserSnowflake;
import org.apache.commons.lang3.tuple.Pair;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Represents a Minecraft Account. Used for communication between this library and minecraft
*/
public class MinecraftAccount {
private final String username;
private final UUID uuid;
private final boolean isOffline;
private final boolean isValid;
/**
* Internal. Use {@link #standard(String)} or {@link #offline(String)}
* @param username The Username of the Player
* @param uuid The UUID of the player
* @param isOffline Is this an OFFLINE/Unauthenticated Account
* @param isValid Is the account valid
*/
private MinecraftAccount(String username, UUID uuid, boolean isOffline, boolean isValid) {
this.username = username;
this.uuid = uuid;
this.isOffline = isOffline;
this.isValid = isValid;
}
/**
* Tries to convert a Username to an online user account. If it can not, it will return an offline user
* @param username The username to search for
*/
public static MinecraftAccount standard(String username) {
Pair<String, UUID> player = fetchPlayer(username);
if (player.getRight() == null) {
return offline(username);
}
return new MinecraftAccount(
player.getLeft(),
player.getRight(),
false,
player.getRight() != null
);
}
/**
* Convert a username to an offline account
* @param username The Username to search for
*/
public static MinecraftAccount offline(String username) {
Pair<String, UUID> player = offlinePlayer(username);
return new MinecraftAccount(
player.getLeft(),
player.getRight(),
true,
true
);
}
public String getUsername() {
return username;
}
public UUID getUuid() {
return uuid;
}
public boolean isValid() {
return isValid;
}
public boolean isOffline() {
return isOffline;
}
/**
* Link a Minecraft account to a discord account
* @param member The discord user
* @param guild The server the command is run from
*/
public Result linkAccount(Member member, Guild guild) {
if (getStoredAccount() == null) {
return Result.error("We couldn't link your Minecraft and Discord Accounts together. If this error persists, please ask a staff member for help");
}
SDLinkAccount account = getStoredAccount();
account.setDiscordID(member.getId());
account.setAccountLinkCode("");
try {
sdlinkDatabase.upsert(account);
String suffix = " [MC: " + this.username + "]";
int availableChars = 32 - suffix.length();
String nickname = member.getEffectiveName();
if (nickname.length() > availableChars) {
nickname = nickname.substring(0, availableChars - 3) + "...";
}
nickname += suffix;
try {
member.modifyNickname(nickname).queue();
if (RoleManager.getLinkedRole() != null) {
guild.addRoleToMember(UserSnowflake.fromId(member.getId()), RoleManager.getLinkedRole()).queue();
}
} catch (Exception e) {
if (sdLinkConfig.generalConfig.debugging) {
e.printStackTrace();
}
}
return Result.success("Your Discord and MC accounts have been linked");
} catch (Exception e) {
e.printStackTrace();
}
return Result.error("Failed to complete account linking. Please inform the server owner");
}
/**
* Unlink a previously linked Discord and Minecraft Account
*/
public Result unlinkAccount() {
SDLinkAccount account = getStoredAccount();
if (account == null)
return Result.error("No such account found in database");
try {
sdlinkDatabase.remove(account, SDLinkAccount.class);
return Result.success("Your discord and Minecraft accounts are no longer linked");
} catch (Exception e) {
e.printStackTrace();
}
return Result.error("We could not unlink your discord and Minecraft accounts. Please inform the server owner");
}
/**
* Check if account database contains linking information
* and a valid discord user for this account
*/
public boolean isAccountLinked() {
SDLinkAccount account = getStoredAccount();
if (account == null)
return false;
User discordUser = getDiscordUser();
return discordUser != null;
}
/**
* Whitelist a Player on Minecraft and store the info the database
* @param member The Discord Member that executed the command
* @param guild The Discord Server the command was executed in
*/
public Result whitelistAccount(Member member, Guild guild) {
if (getStoredAccount() == null) {
return Result.error("We couldn't link your Minecraft and Discord Accounts together. If this error persists, please ask a staff member for help");
}
SDLinkAccount account = getStoredAccount();
account.setDiscordID(member.getId());
account.setWhitelistCode("");
try {
if (!SDLinkPlatform.minecraftHelper.whitelistPlayer(MinecraftAccount.standard(account.getUsername())).isError()) {
account.setWhitelisted(true);
sdlinkDatabase.upsert(account);
// Auto Linking is enabled, so we link the Discord and MC accounts
if (sdLinkConfig.whitelistingAndLinking.whitelisting.linkedWhitelist) {
this.linkAccount(member, guild);
}
}
return Result.success("Your account has been whitelisted");
} catch (Exception e) {
e.printStackTrace();
}
return Result.error("Failed to complete whitelisting. Please inform the server owner");
}
/**
* Remove a previously whitelisted account from Minecraft and the database
*/
public Result unwhitelistAccount() {
SDLinkAccount account = getStoredAccount();
if (account == null)
return Result.error("No such account found in database");
try {
MinecraftAccount minecraftAccount = MinecraftAccount.standard(account.getUsername());
Result whitelistResult = SDLinkPlatform.minecraftHelper.unWhitelistPlayer(minecraftAccount);
if (whitelistResult.isError()) {
return whitelistResult;
} else {
sdlinkDatabase.remove(account, SDLinkAccount.class);
// Auto Linking is enabled. So we unlink the account
if (sdLinkConfig.whitelistingAndLinking.whitelisting.linkedWhitelist) {
this.unlinkAccount();
}
return Result.success("Your account has been removed from the whitelist");
}
} catch (Exception e) {
e.printStackTrace();
}
return Result.error("We could not unwhitelist your account. Please inform the server owner");
}
/**
* Check if the player is whitelisted on the MC server and if the database
* contains an entry for this player
*/
public boolean isAccountWhitelisted() {
SDLinkAccount account = getStoredAccount();
if (account == null)
return false;
return !SDLinkPlatform.minecraftHelper.isPlayerWhitelisted(MinecraftAccount.standard(account.getUsername())).isError() && account.isWhitelisted();
}
/**
* Retrieve the stored account from the database
*/
public SDLinkAccount getStoredAccount() {
return sdlinkDatabase.findById(this.uuid.toString(), SDLinkAccount.class);
}
/**
* Construct a new Database Entry for this account.
* Must only be used when a new entry is required
*/
public SDLinkAccount newDBEntry() {
SDLinkAccount account = new SDLinkAccount();
account.setOffline(this.isOffline);
account.setUUID(this.uuid.toString());
account.setWhitelisted(false);
account.setUsername(this.username);
return account;
}
/**
* Get the Discord Account name this player is linked to
*/
public String getDiscordName() {
SDLinkAccount storedAccount = sdlinkDatabase.findById(this.uuid, SDLinkAccount.class);
if (storedAccount == null || storedAccount.getDiscordID() == null || storedAccount.getDiscordID().isEmpty())
return "Unlinked";
User discordUser = BotController.INSTANCE.getJDA().getUserById(storedAccount.getDiscordID());
return discordUser == null ? "Unlinked" : discordUser.getName();
}
/**
* Get the Discord User this player is linked to
*/
public User getDiscordUser() {
SDLinkAccount storedAccount = sdlinkDatabase.findById(this.uuid, SDLinkAccount.class);
if (storedAccount == null || storedAccount.getDiscordID() == null || storedAccount.getDiscordID().isEmpty())
return null;
return BotController.INSTANCE.getJDA().getUserById(storedAccount.getDiscordID());
}
// Helper Methods
private static Pair<String, UUID> fetchPlayer(String name) {
try {
BufferedReader read = new BufferedReader(new InputStreamReader(new URL("https://api.mojang.com/users/profiles/minecraft/" + name).openStream()));
JSONObject obj = new JSONObject(new JSONTokener(read));
String uuid = "";
String returnname = name;
if (!obj.getString("name").isEmpty()) {
returnname = obj.getString("name");
}
if (!obj.getString("id").isEmpty()) {
uuid = obj.getString("id");
}
read.close();
return Pair.of(returnname, uuid.isEmpty() ? null : mojangIdToUUID(uuid));
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return Pair.of("", null);
}
private static UUID mojangIdToUUID(String id) {
final List<String> strings = new ArrayList<>();
strings.add(id.substring(0, 8));
strings.add(id.substring(8, 12));
strings.add(id.substring(12, 16));
strings.add(id.substring(16, 20));
strings.add(id.substring(20, 32));
return UUID.fromString(String.join("-", strings));
}
private static Pair<String, UUID> offlinePlayer(String offlineName) {
return Pair.of(offlineName, UUID.nameUUIDFromBytes(("OfflinePlayer:" + offlineName).getBytes(StandardCharsets.UTF_8)));
}
}

View File

@@ -0,0 +1,31 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config;
/**
* @author HypherionSA
* The type of User Icon/Avatar that will be used for Discord Messages
*/
public enum AvatarType {
AVATAR("https://mc-heads.net/avatar/{uuid}/512"),
HEAD("https://mc-heads.net/head/{uuid}/512"),
BODY("https://mc-heads.net/body/{uuid}"),
COMBO("https://mc-heads.net/combo/{uuid}/512");
private final String url;
AvatarType(String url) {
this.url = url;
}
@Override
public String toString() {
return this.url;
}
public String resolve(String uuid) {
return this.url.replace("{uuid}", uuid);
}
}

View File

@@ -0,0 +1,154 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config;
import me.hypherionmc.moonconfig.core.CommentedConfig;
import me.hypherionmc.moonconfig.core.Config;
import me.hypherionmc.moonconfig.core.conversion.ObjectConverter;
import me.hypherionmc.moonconfig.core.file.CommentedFileConfig;
import com.hypherionmc.sdlink.core.util.EncryptionUtil;
import java.io.File;
/**
* @author HypherionSA
* Main Config class for Loading, Saving and Upgrading configs
*/
public class ConfigController {
// Private internal variables
private final File configFile;
public static int configVer = 1;
// Instance of the currently loaded config
public static SDLinkConfig sdLinkConfig;
public ConfigController() {
File path = new File("config/");
if (!path.exists())
path.mkdirs();
this.configFile = new File(path.getAbsolutePath() + File.separator + "simple-discord-link.toml");
initConfig();
}
/**
* Set up the Config File as needed.
* This will either Create, Upgrade or load an existing config file
*/
private void initConfig() {
Config.setInsertionOrderPreserved(true);
if (!configFile.exists() || configFile.length() < 10) {
SDLinkConfig config = new SDLinkConfig();
saveConfig(config);
performEncryption();
} else {
configUpgrade();
performEncryption();
}
loadConfig();
}
/**
* Serialize an existing config file into an instance of {@link SDLinkConfig}
*/
private void loadConfig() {
ObjectConverter converter = new ObjectConverter();
CommentedFileConfig config = CommentedFileConfig.builder(configFile).build();
config.load();
sdLinkConfig = converter.toObject(config, SDLinkConfig::new);
config.close();
}
/**
* Serialize an instance of {@link SDLinkConfig} to the config file
* @param conf An instance of the config to save
*/
public void saveConfig(Object conf) {
ObjectConverter converter = new ObjectConverter();
CommentedFileConfig config = CommentedFileConfig.builder(configFile).build();
converter.toConfig(conf, config);
config.save();
config.close();
}
/**
* Handle config structure changes between version changes
*/
private void configUpgrade() {
CommentedFileConfig oldConfig = CommentedFileConfig.builder(configFile).build();
CommentedFileConfig newConfig = CommentedFileConfig.builder(configFile).build();
newConfig.load();
newConfig.clear();
oldConfig.load();
if (oldConfig.getInt("general.configVersion") == configVer) {
newConfig.close();
oldConfig.close();
return;
}
ObjectConverter objectConverter = new ObjectConverter();
objectConverter.toConfig(new SDLinkConfig(), newConfig);
oldConfig.valueMap().forEach((key, value) -> {
if (value instanceof CommentedConfig commentedConfig) {
commentedConfig.valueMap().forEach((subKey, subValue) -> {
if (newConfig.contains(key + "." + subKey)) {
newConfig.set(key + "." + subKey, subValue);
}
});
} else {
if (newConfig.contains(key)) {
newConfig.set(key, value);
}
}
});
configFile.renameTo(new File(configFile.getAbsolutePath().replace(".toml", ".old")));
newConfig.set("general.configVersion", configVer);
newConfig.save();
newConfig.close();
oldConfig.close();
}
/**
* Apply encryption to Bot-Token and Webhook URLS
*/
private void performEncryption() {
CommentedFileConfig oldConfig = CommentedFileConfig.builder(configFile).build();
oldConfig.load();
String botToken = oldConfig.getOrElse("botConfig.botToken", "");
String chatWebhook = oldConfig.getOrElse("channelsAndWebhooks.webhooks.chatWebhook", "");
String eventsWebhook = oldConfig.getOrElse("channelsAndWebhooks.webhooks.eventsWebhook", "");
String consoleWebhook = oldConfig.getOrElse("channelsAndWebhooks.webhooks.consoleWebhook", "");
if (!botToken.isEmpty()) {
botToken = EncryptionUtil.INSTANCE.encrypt(botToken);
oldConfig.set("botConfig.botToken", botToken);
}
if (!chatWebhook.isEmpty()) {
chatWebhook = EncryptionUtil.INSTANCE.encrypt(chatWebhook);
oldConfig.set("channelsAndWebhooks.webhooks.chatWebhook", chatWebhook);
}
if (!eventsWebhook.isEmpty()) {
eventsWebhook = EncryptionUtil.INSTANCE.encrypt(eventsWebhook);
oldConfig.set("channelsAndWebhooks.webhooks.eventsWebhook", eventsWebhook);
}
if (!consoleWebhook.isEmpty()) {
consoleWebhook = EncryptionUtil.INSTANCE.encrypt(consoleWebhook);
oldConfig.set("channelsAndWebhooks.webhooks.consoleWebhook", consoleWebhook);
}
oldConfig.save();
oldConfig.close();
}
}

View File

@@ -0,0 +1,52 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
import com.hypherionmc.sdlink.core.config.impl.*;
/**
* @author HypherionSA
* The main mod config Structure
*/
public class SDLinkConfig {
@Path("general")
@SpecComment("General Mod Config")
public GeneralConfigSettings generalConfig = new GeneralConfigSettings();
@Path("botConfig")
@SpecComment("Config specific to the discord bot")
public BotConfigSettings botConfig = new BotConfigSettings();
@Path("channelsAndWebhooks")
@SpecComment("Config relating to the discord channels and webhooks to use with the mod")
public ChannelWebhookConfig channelsAndWebhooks = new ChannelWebhookConfig();
@Path("chat")
@SpecComment("Configure which types of messages are delivered to Minecraft/Discord")
public ChatSettingsConfig chatConfig = new ChatSettingsConfig();
@Path("messageFormatting")
@SpecComment("Change the format in which messages are displayed")
public MessageFormatting messageFormatting = new MessageFormatting();
@Path("messageDestinations")
@SpecComment("Change in which channel messages appear")
public MessageChannelConfig messageDestinations = new MessageChannelConfig();
@Path("whitelistingAndLinking")
@SpecComment("Configure Whitelisting and Account Linking through the bot")
public LinkAndWhitelistConfigSettings whitelistingAndLinking = new LinkAndWhitelistConfigSettings();
@Path("botCommands")
@SpecComment("Enable or Disable certain bot commands")
public BotCommandsConfig botCommands = new BotCommandsConfig();
@Path("linkedCommands")
@SpecComment("Execute Minecraft commands in Discord")
public LinkedCommandsConfig linkedCommands = new LinkedCommandsConfig();
}

View File

@@ -0,0 +1,28 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
/**
* @author HypherionSA
* Config Structure to allow disabling some bot commands
*/
public class BotCommandsConfig {
@Path("allowPlayerList")
@SpecComment("Enable/Disable the Player List command")
public boolean allowPlayerList = true;
@Path("allowServerStatus")
@SpecComment("Enable/Disable the Server Status command")
public boolean allowServerStatus = true;
@Path("allowHelpCommand")
@SpecComment("Enable/Disable the Help command")
public boolean allowHelpCommand = true;
}

View File

@@ -0,0 +1,75 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
import net.dv8tion.jda.api.entities.Activity;
/**
* @author HypherionSA
* Config Structure for the Core bot settings
*/
public class BotConfigSettings {
@Path("botToken")
@SpecComment("The token of the Discord Bot to use. This will be encrypted on first load. See https://sdlink.fdd-docs.com/initial-setup/ to find this")
public String botToken = "";
@Path("statusUpdateInterval")
@SpecComment("How often the Bot Status will update on Discord (in Seconds)")
public int statusUpdateInterval = 30;
@Path("staffRole")
@SpecComment("Role Name or ID that is allowed to use Staff Functions. If not defined, it will default back to Admin/Kick Perms")
public String staffRole = "";
@Path("botStatus")
@SpecComment("Control what the Discord Bot will display as it's status message")
public BotStatus botStatus = new BotStatus();
@Path("topicUpdates")
@SpecComment("Define how the bot should handle channel topic updates on the chat channel")
public ChannelTopic channelTopic = new ChannelTopic();
@Path("invite")
@SpecComment("Configure the in-game Discord Invite command")
public DiscordInvite invite = new DiscordInvite();
public static class BotStatus {
@Path("status")
@SpecComment("Do not add Playing. A status to display on the bot. You can use %players% and %maxplayers% to show the number of players on the server")
public String botStatus = "Minecraft";
@Path("botStatusType")
@SpecComment("The type of the status displayed on the bot. Valid entries are: PLAYING, STREAMING, WATCHING, LISTENING")
public Activity.ActivityType botStatusType = Activity.ActivityType.PLAYING;
@Path("botStatusStreamingURL")
@SpecComment("The URL that will be used when the \"botStatusType\" is set to \"STREAMING\", required to display as \"streaming\".")
public String botStatusStreamingURL = "https://twitch.tv/twitch";
}
public static class ChannelTopic {
@Path("doTopicUpdates")
@SpecComment("Should the bot update the topic of your chat channel automatically every 6 Minutes")
public boolean doTopicUpdates = true;
@Path("channelTopic")
@SpecComment("A topic for the Chat Relay channel. You can use %player%, %maxplayers%, %uptime% or just leave it empty.")
public String channelTopic = "Playing Minecraft with %players%/%maxplayers% people | Uptime: %uptime%";
}
public static class DiscordInvite {
@Path("inviteLink")
@SpecComment("If this is defined, it will enable the in-game Discord command")
public String inviteLink = "";
@Path("inviteMessage")
@SpecComment("The message to show when someone uses /discord command. You can use %inviteurl%")
public String inviteMessage = "Hey, check out our discord server here -> %inviteurl%";
}
}

View File

@@ -0,0 +1,64 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
/**
* @author HypherionSA
* Config Structure to control Channels and Webhooks used by the bot
*/
public class ChannelWebhookConfig {
@Path("serverAvatar")
@SpecComment("A DIRECT link to an image to use as the avatar for server messages. Also used for embeds")
public String serverAvatar = "";
@Path("serverName")
@SpecComment("The name to display for Server messages when using Webhooks")
public String serverName = "Minecraft Server";
@Path("channels")
@SpecComment("Config relating to the discord channels to use with the mod")
public Channels channels = new Channels();
@Path("webhooks")
@SpecComment("Config relating to the discord Webhooks to use with the mod")
public Webhooks webhooks = new Webhooks();
public static class Channels {
@Path("chatChannelID")
@SpecComment("REQUIRED! The ID of the channel to post in and relay messages from. This is still needed, even in webhook mode")
public long chatChannelID = 0;
@Path("eventsChannelID")
@SpecComment("If this ID is set, event messages will be posted in this channel instead of the chat channel")
public long eventsChannelID = 0;
@Path("consoleChannelID")
@SpecComment("If this ID is set, console messages sent after the bot started will be relayed here, and you can execute minecraft commands here")
public long consoleChannelID = 0;
}
public static class Webhooks {
@Path("enabled")
@SpecComment("Prefer Webhook Messages over Standard Bot Messages")
public boolean enabled = false;
@Path("chatWebhook")
@SpecComment("The URL of the channel webhook to use for Chat Messages. Will be encrypted on first run")
public String chatWebhook = "";
@Path("eventsWebhook")
@SpecComment("The URL of the channel webhook to use for Server Messages Will be encrypted on first run")
public String eventsWebhook = "";
@Path("consoleWebhook")
@SpecComment("The URL of the channel webhook to use for Console Messages Will be encrypted on first run")
public String consoleWebhook = "";
}
}

View File

@@ -0,0 +1,92 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
import com.hypherionmc.sdlink.core.config.AvatarType;
import java.util.ArrayList;
import java.util.List;
/**
* @author HypherionSA
* Config Structure to control what types of messages are supported by the mod
*/
public class ChatSettingsConfig {
@Path("formatting")
@SpecComment("Convert Discord to MC, and MC to Discord Formatting")
public boolean formatting = true;
@Path("sendConsoleMessages")
@SpecComment("Should console messages be sent to the Console Channel")
public boolean sendConsoleMessages = false;
@Path("playerAvatarType")
@SpecComment("The type of image to use as the player icon in messages. Valid entries are: AVATAR, HEAD, BODY, COMBO")
public AvatarType playerAvatarType = AvatarType.HEAD;
@Path("relayTellRaw")
@SpecComment("Should messages sent with TellRaw be sent to discord as a chat? (Experimental)")
public boolean relayTellRaw = true;
@Path("relayFullCommands")
@SpecComment("Should the entire command executed be relayed to discord, or only the name of the command")
public boolean relayFullCommands = false;
@Path("ignoreBots")
@SpecComment("Should messages from bots be relayed")
public boolean ignoreBots = true;
@Path("serverStarting")
@SpecComment("Should SERVER STARTING messages be shown")
public boolean serverStarting = true;
@Path("serverStarted")
@SpecComment("Should SERVER STARTED messages be shown")
public boolean serverStarted = true;
@Path("serverStopping")
@SpecComment("Should SERVER STOPPING messages be shown")
public boolean serverStopping = true;
@Path("serverStopped")
@SpecComment("Should SERVER STOPPED messages be shown")
public boolean serverStopped = true;
@Path("playerMessages")
@SpecComment("Should the chat be relayed")
public boolean playerMessages = true;
@Path("playerJoin")
@SpecComment("Should Player Join messages be posted")
public boolean playerJoin = true;
@Path("playerLeave")
@SpecComment("Should Player Leave messages be posted")
public boolean playerLeave = true;
@Path("advancementMessages")
@SpecComment("Should Advancement messages be posted")
public boolean advancementMessages = true;
@Path("deathMessages")
@SpecComment("Should Death Announcements be posted")
public boolean deathMessages = true;
@Path("sendSayCommand")
@SpecComment("Should Messages from the /say command be posted")
public boolean sendSayCommand = true;
@Path("broadcastCommands")
@SpecComment("Should commands be posted to discord")
public boolean broadcastCommands = true;
@Path("ignoredCommands")
@SpecComment("Commands that should not be broadcasted to discord")
public List<String> ignoredCommands = new ArrayList<String>() {{ add("particle"); add("login"); add("execute"); }};
}

View File

@@ -0,0 +1,28 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
import com.hypherionmc.sdlink.core.config.ConfigController;
/**
* @author HypherionSA
* General Mod Settings config Structure
*/
public class GeneralConfigSettings {
@Path("enabled")
@SpecComment("Should the mod be enabled or not")
public boolean enabled = true;
@Path("debugging")
@SpecComment("Enable Additional Logging. Used for Fault Finding. WARNING: CAUSES LOG SPAM!")
public boolean debugging = false;
@Path("configVersion")
@SpecComment("Internal version control. DO NOT TOUCH!")
public int configVersion = ConfigController.configVer;
}

View File

@@ -0,0 +1,55 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
/**
* @author HypherionSA
* Config Structure to control Whitelisting and Account Linking
*/
public class LinkAndWhitelistConfigSettings {
@Path("whiteListing")
@SpecComment("Control how the bot handles Whitelisting Players, if at all")
public Whitelisting whitelisting = new Whitelisting();
@Path("accountLinking")
@SpecComment("Control how the bot handles Discord -> MC Account Linking, if at all")
public AccountLinking accountLinking = new AccountLinking();
public static class AccountLinking {
@Path("accountlinking")
@SpecComment("Allow users to Link their MC and Discord accounts")
public boolean accountLinking = false;
@Path("linkedRole")
@SpecComment("If a role ID (or name) is defined here, it will be assigned to players when their MC and Discord accounts are linked")
public String linkedRole = "";
@Path("requireLinking")
@SpecComment("Require users to link their Discord and Minecraft accounts before joining the server")
public boolean requireLinking = false;
}
public static class Whitelisting {
@Path("whitelisting")
@SpecComment("Should the bot be allowed to whitelist/un-whitelist players.")
public boolean whitelisting = false;
@Path("linkedWhitelist")
@SpecComment("Automatically link Minecraft and Discord Accounts when a user is whitelisted")
public boolean linkedWhitelist = false;
@Path("staffOnlyWhitelist")
@SpecComment("Should only staff be allowed to whitelist players")
public boolean staffOnlyWhitelist = false;
@Path("autoWhitelistRole")
@SpecComment("If a role ID (or name) is defined here, it will be assigned to players when they are whitelisted")
public String autoWhitelistRole = "";
}
}

View File

@@ -0,0 +1,41 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
import java.util.ArrayList;
import java.util.List;
/**
* @author HypherionSA
* Main Config Structure to control Discord -> MC Commands
*/
public class LinkedCommandsConfig {
@Path("enabled")
@SpecComment("Should linked commands be enabled")
public boolean enabled = false;
@Path("commands")
@SpecComment("Commands to be linked")
public List<Command> commands = new ArrayList<>();
public static class Command {
@Path("mcCommand")
@SpecComment("The Minecraft Command. Use %args% and %args(1-9)% (for example %args1%) to pass everything after the discordCommand to Minecraft")
public String mcCommand;
@Path("discordCommand")
@SpecComment("The command slug in discord. To be used as /mc slug or ~mc slug")
public String discordCommand;
@Path("discordRole")
@SpecComment("Discord Role Name of ID of the role that is allowed to execute this command. If empty, all players will be allowed to use this command")
public String discordRole;
}
}

View File

@@ -0,0 +1,59 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
import com.hypherionmc.sdlink.core.messaging.MessageDestination;
/**
* @author HypherionSA
* Config Structure to control the destinations of messages
*/
public class MessageChannelConfig {
@Path("chat")
@SpecComment("Control where CHAT messages are delivered")
public DestinationObject chat = DestinationObject.of(MessageDestination.CHAT, false);
@Path("startStop")
@SpecComment("Control where START/STOP messages are delivered")
public DestinationObject startStop = DestinationObject.of(MessageDestination.EVENT, false);
@Path("joinLeave")
@SpecComment("Control where JOIN/LEAVE messages are delivered")
public DestinationObject joinLeave = DestinationObject.of(MessageDestination.EVENT, false);
@Path("advancements")
@SpecComment("Control where ADVANCEMENT messages are delivered")
public DestinationObject advancements = DestinationObject.of(MessageDestination.EVENT, false);
@Path("death")
@SpecComment("Control where DEATH messages are delivered")
public DestinationObject death = DestinationObject.of(MessageDestination.EVENT, false);
@Path("commands")
@SpecComment("Control where COMMAND messages are delivered")
public DestinationObject commands = DestinationObject.of(MessageDestination.EVENT, false);
public static class DestinationObject {
@Path("channel")
@SpecComment("The Channel the message will be delivered to. Valid entries are CHAT, EVENT, CONSOLE")
public MessageDestination channel;
@Path("useEmbed")
@SpecComment("Should the message be sent using EMBED style messages")
public boolean useEmbed;
DestinationObject(MessageDestination destination, boolean useEmbed) {
this.channel = destination;
this.useEmbed = useEmbed;
}
public static DestinationObject of(MessageDestination destination, boolean useEmbed) {
return new DestinationObject(destination, useEmbed);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.config.impl;
import me.hypherionmc.moonconfig.core.conversion.Path;
import me.hypherionmc.moonconfig.core.conversion.SpecComment;
/**
* @author HypherionSA
* Config Structure to control Discord/MC Message Formatting
*/
public class MessageFormatting {
@Path("mcPrefix")
@SpecComment("Prefix to add to Minecraft when a message is relayed from Discord. Supports MC formatting. Use %user% for the Discord Username")
public String mcPrefix = "\u00A7e[Discord]\u00A7r %user%: ";
@Path("serverStarting")
@SpecComment("Server Starting Message")
public String serverStarting = "*Server is starting...*";
@Path("serverStarted")
@SpecComment("Server Started Message")
public String serverStarted = "*Server has started. Enjoy!*";
@Path("serverStopping")
@SpecComment("Server Stopping Message")
public String serverStopping = "*Server is stopping...*";
@Path("serverStopped")
@SpecComment("Server Stopped Message")
public String serverStopped = "*Server has stopped...*";
@Path("playerJoined")
@SpecComment("Player Joined Message. Use %player% to display the player name")
public String playerJoined = "*%player% has joined the server!*";
@Path("playerLeft")
@SpecComment("Player Left Message. Use %player% to display the player name")
public String playerLeft = "*%player% has left the server!*";
@Path("achievements")
@SpecComment("Achievement Messages. Available variables: %player%, %title%, %description%")
public String achievements = "*%player% has made the advancement [%title%]: %description%*";
@Path("chat")
@SpecComment("Chat Messages. THIS DOES NOT APPLY TO EMBED OR WEBHOOK MESSAGES. Available variables: %player%, %message%")
public String chat = "%player%: %message%";
@Path("commands")
@SpecComment("Command Messages. Available variables: %player%, %command%")
public String commands = "%player% **executed command**: *%command%*";
}

View File

@@ -0,0 +1,102 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.database;
import io.jsondb.annotation.Document;
import io.jsondb.annotation.Id;
/**
* @author HypherionSA
* JSON based database to hold accounts the bot has interacted with.
* This is used for Account Linking and Whitelisting
*/
@Document(collection = "accounts", schemaVersion = "1.0")
public class SDLinkAccount {
@Id
private String UUID;
private String username;
private String addedBy;
private String discordID;
private String accountLinkCode;
private String whitelistCode;
private boolean isWhitelisted;
private boolean isOffline;
public String getAccountLinkCode() {
if (accountLinkCode == null)
return "";
return accountLinkCode;
}
public boolean isOffline() {
return isOffline;
}
public boolean isWhitelisted() {
return isWhitelisted;
}
public String getDiscordID() {
if (discordID == null)
return "";
return discordID;
}
public String getAddedBy() {
if (addedBy == null)
return "";
return addedBy;
}
public String getUsername() {
if (username == null)
return "";
return username;
}
public String getUUID() {
if (UUID == null)
return "";
return UUID;
}
public String getWhitelistCode() {
if (whitelistCode == null)
return "";
return whitelistCode;
}
public void setAccountLinkCode(String accountLinkCode) {
this.accountLinkCode = accountLinkCode;
}
public void setAddedBy(String addedBy) {
this.addedBy = addedBy;
}
public void setDiscordID(String discordID) {
this.discordID = discordID;
}
public void setOffline(boolean offline) {
isOffline = offline;
}
public void setUsername(String username) {
this.username = username;
}
public void setUUID(String UUID) {
this.UUID = UUID;
}
public void setWhitelistCode(String whitelistCode) {
this.whitelistCode = whitelistCode;
}
public void setWhitelisted(boolean whitelisted) {
isWhitelisted = whitelisted;
}
}

View File

@@ -0,0 +1,195 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord;
import com.hypherionmc.sdlink.core.config.ConfigController;
import com.hypherionmc.sdlink.core.discord.commands.CommandManager;
import com.hypherionmc.sdlink.core.discord.events.DiscordEventHandler;
import com.hypherionmc.sdlink.core.managers.DatabaseManager;
import com.hypherionmc.sdlink.core.managers.WebhookManager;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import com.hypherionmc.sdlink.core.util.EncryptionUtil;
import com.hypherionmc.sdlink.core.util.ThreadedEventManager;
import com.jagrosh.jdautilities.command.CommandClient;
import com.jagrosh.jdautilities.command.CommandClientBuilder;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.ChunkingFilter;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
import org.slf4j.Logger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* The main Discord Bot class. This controls everything surrounding the bot itself
*/
public class BotController {
// Public instance of this class that can be called anywhere
public static BotController INSTANCE;
// Thread Execution Manager
public static final ScheduledExecutorService taskManager = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
// Required Variables
private JDA _jda;
private final EventWaiter eventWaiter = new EventWaiter();
private final Logger logger;
/**
* Construct a new instance of this class
* @param logger A constructed {@link Logger} that the bot will use
*/
public static void newInstance(Logger logger) {
if (INSTANCE != null) {
INSTANCE.shutdownBot(false);
}
new BotController(logger);
}
/**
* INTERNAL
* @param logger A constructed {@link Logger} that the bot will use
*/
private BotController(Logger logger) {
INSTANCE = this;
this.logger = logger;
new ConfigController();
DatabaseManager.initialize();
// Initialize Webhook Clients
WebhookManager.init();
}
/**
* Start the bot and handle all the startup work
*/
public void initializeBot() {
if (sdLinkConfig == null) {
logger.error("Failed to load config. Check your log for errors");
return;
}
if (sdLinkConfig.botConfig.botToken.isEmpty()) {
logger.error("Missing bot token. Mod will be disabled");
return;
}
if (!sdLinkConfig.generalConfig.enabled)
return;
try {
String token = EncryptionUtil.INSTANCE.decrypt(sdLinkConfig.botConfig.botToken);
_jda = JDABuilder.createLight(
token,
GatewayIntent.GUILD_MEMBERS,
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_MESSAGE_REACTIONS
)
.setMemberCachePolicy(MemberCachePolicy.ALL)
.setChunkingFilter(ChunkingFilter.ALL)
.setBulkDeleteSplittingEnabled(true)
.setEventManager(new ThreadedEventManager())
.build();
// Setup Commands
CommandClientBuilder clientBuilder = new CommandClientBuilder();
clientBuilder.setOwnerId("354707828298088459");
clientBuilder.setHelpWord("help");
clientBuilder.useHelpBuilder(false);
//clientBuilder.forceGuildOnly(750990873311051786L);
CommandClient commandClient = clientBuilder.build();
CommandManager.INSTANCE.register(commandClient);
// Register Event Handlers
_jda.addEventListener(commandClient, eventWaiter, new DiscordEventHandler());
_jda.setAutoReconnect(true);
} catch (Exception e) {
logger.error("Failed to connect to discord", e);
}
}
/**
* Check if the bot is in a state to send messages to discord
*/
public boolean isBotReady() {
if (sdLinkConfig == null)
return false;
if (!sdLinkConfig.generalConfig.enabled)
return false;
if (_jda == null)
return false;
if (_jda.getStatus() == JDA.Status.SHUTTING_DOWN || _jda.getStatus() == JDA.Status.SHUTDOWN)
return false;
return _jda.getStatus() == JDA.Status.CONNECTED;
}
/**
* Shutdown the Bot, without forcing a shutdown
*/
public void shutdownBot() {
this.shutdownBot(true);
}
/**
* Shutdown the Bot, optionally forcing a shutdown
* @param forced Should the shutdown be forced
*/
public void shutdownBot(boolean forced) {
if (_jda != null) {
_jda.shutdown();
}
WebhookManager.shutdown();
if (forced) {
// Workaround for Bot thread hanging after server shutdown
taskManager.schedule(() -> {
taskManager.shutdownNow();
System.exit(1);
}, 10, TimeUnit.SECONDS);
}
}
/**
* Ensure that whitelisting is set up properly, so the bot can use the feature
*/
public void checkWhiteListing() {
if (!sdLinkConfig.whitelistingAndLinking.whitelisting.whitelisting)
return;
if (SDLinkPlatform.minecraftHelper.checkWhitelisting().isError()) {
getLogger().error("SDLink Whitelisting is enabled, but server side whitelisting is disabled");
}
}
public Logger getLogger() {
return logger;
}
public JDA getJDA() {
return this._jda;
}
public EventWaiter getEventWaiter() {
return eventWaiter;
}
}

View File

@@ -0,0 +1,84 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands;
import com.hypherionmc.sdlink.core.discord.commands.slash.general.HelpSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.general.PlayerListSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.general.ServerStatusSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.linking.ConfirmAccountLinkSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.linking.LinkAccountCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.linking.UnlinkAccountSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.linking.ViewLinkedAccountsCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.whitelist.ConfirmWhitelistSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.whitelist.UnWhitelistAccountSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.whitelist.ViewWhitelistedAccountsSlashCommand;
import com.hypherionmc.sdlink.core.discord.commands.slash.whitelist.WhitelistAccountCommand;
import com.jagrosh.jdautilities.command.CommandClient;
import com.jagrosh.jdautilities.command.SlashCommand;
import java.util.HashSet;
import java.util.Set;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Command Manager class to control how commands are registered to discord
*/
public class CommandManager {
public static final CommandManager INSTANCE = new CommandManager();
private final Set<SlashCommand> commands = new HashSet<>();
private CommandManager() {
this.addCommands();
}
private void addCommands() {
// Register Account Linking commands, if linking is enabled
if (sdLinkConfig.whitelistingAndLinking.accountLinking.accountLinking) {
commands.add(new LinkAccountCommand());
commands.add(new ConfirmAccountLinkSlashCommand());
commands.add(new UnlinkAccountSlashCommand());
commands.add(new ViewLinkedAccountsCommand());
}
// Register Whitelist commands, if whitelisting is enabled
if (sdLinkConfig.whitelistingAndLinking.whitelisting.whitelisting) {
commands.add(new WhitelistAccountCommand());
commands.add(new ConfirmWhitelistSlashCommand());
commands.add(new ViewWhitelistedAccountsSlashCommand());
commands.add(new UnWhitelistAccountSlashCommand());
}
// Enable the Server Status command
if (sdLinkConfig.botCommands.allowServerStatus) {
commands.add(new ServerStatusSlashCommand());
}
// Enable the Player List command
if (sdLinkConfig.botCommands.allowPlayerList) {
commands.add(new PlayerListSlashCommand());
}
// Enable the Help command
if (sdLinkConfig.botCommands.allowHelpCommand) {
commands.add(new HelpSlashCommand());
}
}
/**
* INTERNAL. Used to register slash commands
* @param client The Discord Command Client instance
*/
public void register(CommandClient client) {
commands.forEach(client::addSlashCommand);
}
public Set<SlashCommand> getCommands() {
return commands;
}
}

View File

@@ -0,0 +1 @@
// TODO Don't Forget This

View File

@@ -0,0 +1,28 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash;
import com.hypherionmc.sdlink.core.managers.RoleManager;
import com.jagrosh.jdautilities.command.SlashCommand;
import net.dv8tion.jda.api.Permission;
/**
* @author HypherionSA
* Extention of {@link SlashCommand} to implement our Permission handling
*/
public abstract class SDLinkSlashCommand extends SlashCommand {
public SDLinkSlashCommand(boolean requiresPerms) {
if (requiresPerms) {
if (RoleManager.getStaffRole() != null) {
this.requiredRole = RoleManager.getStaffRole().getName();
} else {
this.userPermissions = new Permission[] { Permission.ADMINISTRATOR, Permission.KICK_MEMBERS };
}
}
this.guildOnly = true;
}
}

View File

@@ -0,0 +1,39 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.general;
import com.hypherionmc.sdlink.core.discord.commands.CommandManager;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.jagrosh.jdautilities.command.SlashCommand;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import net.dv8tion.jda.api.EmbedBuilder;
import java.awt.*;
import java.util.Set;
/**
* @author HypherionSA
* The Help Command for the bot
*/
public class HelpSlashCommand extends SDLinkSlashCommand {
public HelpSlashCommand() {
super(false);
this.name = "help";
this.help = "Bot commands and help";
}
@Override
protected void execute(SlashCommandEvent event) {
Set<SlashCommand> commands = CommandManager.INSTANCE.getCommands();
EmbedBuilder builder = new EmbedBuilder();
builder.setTitle("Bot commands");
builder.setColor(Color.BLUE);
commands.forEach(cmd -> builder.addField(cmd.getName(), cmd.getHelp(), false));
event.replyEmbeds(builder.build()).setEphemeral(true).queue();
}
}

View File

@@ -0,0 +1,82 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.general;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import com.hypherionmc.sdlink.core.util.MessageUtil;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import com.jagrosh.jdautilities.menu.EmbedPaginator;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author HypherionSA
* Command to view a list of online players currently on the server
*/
public class PlayerListSlashCommand extends SDLinkSlashCommand {
public PlayerListSlashCommand() {
super(false);
this.name = "playerlist";
this.help = "List currently online players on the server";
this.guildOnly = true;
}
@Override
protected void execute(SlashCommandEvent event) {
List<MinecraftAccount> players = SDLinkPlatform.minecraftHelper.getOnlinePlayers();
EmbedBuilder builder = new EmbedBuilder();
List<MessageEmbed> pages = new ArrayList<>();
AtomicInteger count = new AtomicInteger();
if (players.isEmpty()) {
builder.setTitle("Online Players");
builder.setColor(Color.RED);
builder.setDescription("There are currently no players online");
event.replyEmbeds(builder.build()).setEphemeral(true).queue();
return;
}
EmbedPaginator.Builder paginator = MessageUtil.defaultPaginator(event);
/**
* Use Pagination to avoid message limits
*/
MessageUtil.listBatches(players, 10).forEach(p -> {
StringBuilder sb = new StringBuilder();
count.getAndIncrement();
builder.clear();
builder.setTitle("Online Players - Page " + count.get() + "/" + (int)Math.ceil(((float)players.size() / 10)));
builder.setColor(Color.GREEN);
p.forEach(account -> {
sb.append("`").append(account.getUsername()).append("`");
if (account.getDiscordUser() != null) {
sb.append(" - ").append(account.getDiscordUser().getAsMention());
}
sb.append("\r\n");
});
builder.setDescription(sb.toString());
pages.add(builder.build());
});
paginator.setItems(pages);
EmbedPaginator embedPaginator = paginator.build();
event.replyEmbeds(pages.get(0)).setEphemeral(false).queue(success ->
success.retrieveOriginal().queue(msg -> embedPaginator.paginate(msg, 1)));
}
}

View File

@@ -0,0 +1,109 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.general;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import com.hypherionmc.sdlink.core.util.SystemUtils;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.HardwareAbstractionLayer;
/**
* @author HypherionSA
* Informational command to give you a quick overview of the hardware/player
* status of your server
*/
public class ServerStatusSlashCommand extends SDLinkSlashCommand {
public ServerStatusSlashCommand() {
super(true);
this.name = "status";
this.help = "View information about your server";
this.guildOnly = true;
}
@Override
protected void execute(SlashCommandEvent event) {
Button refreshBtn = Button.danger("sdrefreshbtn", "Refresh");
event.replyEmbeds(runStatusCommand()).addActionRow(refreshBtn).queue();
}
public static MessageEmbed runStatusCommand() {
SystemInfo systemInfo = new SystemInfo();
HardwareAbstractionLayer hal = systemInfo.getHardware();
CentralProcessor cpu = hal.getProcessor();
EmbedBuilder builder = new EmbedBuilder();
builder.setTitle("Server Information / Status");
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("**__System Information__**\r\n\r\n");
stringBuilder
.append("**CPU:**\r\n```\r\n")
.append(cpu.toString())
.append("```")
.append("\r\n");
try {
stringBuilder
.append("**Memory:**\r\n```\r\n")
.append(SystemUtils.byteToHuman(hal.getMemory().getAvailable()))
.append(" free of ")
.append(SystemUtils.byteToHuman(hal.getMemory().getTotal()))
.append("```\r\n");
} catch (Exception e) {
}
stringBuilder
.append("**OS:**\r\n```\r\n")
.append(systemInfo.getOperatingSystem().toString())
.append(" (")
.append(systemInfo.getOperatingSystem().getBitness())
.append(" bit)\r\n")
.append("Version: ")
.append(systemInfo.getOperatingSystem().getVersionInfo().getVersion())
.append("```\r\n");
stringBuilder
.append("**System Uptime:**\r\n```\r\n")
.append(SystemUtils.secondsToTimestamp(systemInfo.getOperatingSystem().getSystemUptime()))
.append("```\r\n");
stringBuilder.append("**__Minecraft Information__**\r\n\r\n");
stringBuilder
.append("**Server Uptime:**\r\n```\r\n")
.append(SystemUtils.secondsToTimestamp(SDLinkPlatform.minecraftHelper.getServerUptime()))
.append("```\r\n");
stringBuilder
.append("**Server Version:**\r\n```\r\n")
.append(SDLinkPlatform.minecraftHelper.getServerVersion())
.append("```\r\n");
stringBuilder
.append("**Players Online:**\r\n```\r\n")
.append(SDLinkPlatform.minecraftHelper.getPlayerCounts().getLeft())
.append("/")
.append(SDLinkPlatform.minecraftHelper.getPlayerCounts().getRight())
.append("```\r\n");
stringBuilder
.append("**Whitelisting:**\r\n```\r\n")
.append(!SDLinkPlatform.minecraftHelper.checkWhitelisting().isError() ? "Enabled" : "Disabled")
.append("```\r\n");
builder.setDescription(stringBuilder.toString());
return builder.build();
}
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.linking;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.messaging.Result;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import java.util.Collections;
import java.util.List;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Command to complete Discord -> MC Account Linking
*/
public class ConfirmAccountLinkSlashCommand extends SDLinkSlashCommand {
public ConfirmAccountLinkSlashCommand() {
super(false);
this.name = "confirmlink";
this.help = "Confirm your Minecraft Account to complete account linking";
this.options = Collections.singletonList(new OptionData(OptionType.INTEGER, "code", "The verification code from the Minecraft Kick Message").setRequired(true));
}
@Override
protected void execute(SlashCommandEvent event) {
int mcCode = event.getOption("code") != null ? event.getOption("code").getAsInt() : 0;
if (mcCode == 0) {
event.reply("You need to provide a verification code").setEphemeral(true).queue();
return;
}
List<SDLinkAccount> accounts = sdlinkDatabase.findAll(SDLinkAccount.class);
if (accounts.isEmpty()) {
event.reply("Sorry, but this server does not contain any stored players in its database").setEphemeral(true).queue();
return;
}
for (SDLinkAccount account : accounts) {
if (account.getAccountLinkCode().equalsIgnoreCase(String.valueOf(mcCode))) {
MinecraftAccount minecraftAccount = MinecraftAccount.standard(account.getUsername());
Result result = minecraftAccount.linkAccount(event.getMember(), event.getGuild());
event.reply(result.getMessage()).setEphemeral(true).queue();
return;
}
}
event.reply("Sorry, we could not verify your Minecraft account. Please try again").setEphemeral(true).queue();
}
}

View File

@@ -0,0 +1,78 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.linking;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.util.SystemUtils;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import io.jsondb.InvalidJsonDbApiUsageException;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import java.util.Collections;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Command to start the Linking process of a Discord and MC Account
* This will generate the verification code the player needs to enter, to
* verify the account belongs to them
*/
public class LinkAccountCommand extends SDLinkSlashCommand {
public LinkAccountCommand() {
super(false);
this.name = "linkaccount";
this.help = "Start the process of linking your Discord and MC Accounts";
this.options = Collections.singletonList(new OptionData(OptionType.STRING, "mcname", "Your Minecraft Username").setRequired(true));
}
@Override
protected void execute(SlashCommandEvent event) {
String mcName = event.getOption("mcname") != null ? event.getOption("mcname").getAsString() : "";
if (mcName.isEmpty()) {
event.reply("You need to supply your Minecraft username").setEphemeral(true).queue();
return;
}
MinecraftAccount minecraftAccount = MinecraftAccount.standard(mcName);
String confirmCode = String.valueOf(SystemUtils.generateRandomJoinCode());
SDLinkAccount account = minecraftAccount.getStoredAccount();
if (account == null) {
account = minecraftAccount.newDBEntry();
account.setAccountLinkCode(confirmCode);
try {
sdlinkDatabase.insert(account);
event.reply("Please join the Minecraft server and check the Kick Message for your account link code. Then, run the command /confirmlink codehere to finish linking your accounts").setEphemeral(true).queue();
} catch (InvalidJsonDbApiUsageException e) {
e.printStackTrace();
event.reply("Could not start account linking process. Please notify the server owner").setEphemeral(true).queue();
}
} else {
if (account.getDiscordID() != null || !account.getDiscordID().isEmpty()) {
event.reply("Sorry, this Minecraft account is already linked to a discord account").setEphemeral(true).queue();
return;
}
account.setAccountLinkCode(confirmCode);
try {
sdlinkDatabase.upsert(account);
event.reply("Please join the Minecraft server and check the Kick Message for your account link code. Then, run the command /confirmlink codehere to finish linking your accounts").setEphemeral(true).queue();
} catch (InvalidJsonDbApiUsageException e) {
e.printStackTrace();
event.reply("Could not start account linking process. Please notify the server owner").setEphemeral(true).queue();
}
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.linking;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.messaging.Result;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import java.util.List;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Command to unlink a discord and minecraft account, that was previously linked
*/
public class UnlinkAccountSlashCommand extends SDLinkSlashCommand {
public UnlinkAccountSlashCommand() {
super(false);
this.name = "unlinkaccount";
this.help = "Unlink your previously linked Discord and Minecraft accounts";
}
@Override
protected void execute(SlashCommandEvent event) {
List<SDLinkAccount> accounts = sdlinkDatabase.findAll(SDLinkAccount.class);
if (accounts.isEmpty()) {
event.reply("Sorry, but this server does not contain any stored players in its database").setEphemeral(true).queue();
return;
}
for (SDLinkAccount account : accounts) {
if (account.getDiscordID() != null && account.getDiscordID().equalsIgnoreCase(event.getMember().getId())) {
MinecraftAccount minecraftAccount = MinecraftAccount.standard(account.getUsername());
Result result = minecraftAccount.unlinkAccount();
event.reply(result.getMessage()).setEphemeral(true).queue();
break;
}
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.linking;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.util.MessageUtil;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import com.jagrosh.jdautilities.menu.EmbedPaginator;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Staff Command to view a list of Linked Minecraft and Discord accounts
*/
public class ViewLinkedAccountsCommand extends SDLinkSlashCommand {
public ViewLinkedAccountsCommand() {
super(true);
this.name = "linkedaccounts";
this.help = "View a list of linked Discord and MC accounts";
}
@Override
protected void execute(SlashCommandEvent event) {
EmbedPaginator.Builder paginator = MessageUtil.defaultPaginator(event);
List<SDLinkAccount> accounts = sdlinkDatabase.findAll(SDLinkAccount.class);
EmbedBuilder builder = new EmbedBuilder();
ArrayList<MessageEmbed> pages = new ArrayList<>();
AtomicInteger count = new AtomicInteger();
if (accounts.isEmpty()) {
event.reply("There are no linked accounts for this discord").setEphemeral(true).queue();
return;
}
MessageUtil.listBatches(accounts, 10).forEach(itm -> {
count.getAndIncrement();
builder.clear();
builder.setTitle("Linked Accounts - Page " + count + "/" + (int)Math.ceil(((float)accounts.size() / 10)));
builder.setColor(Color.GREEN);
StringBuilder sBuilder = new StringBuilder();
itm.forEach(v -> {
Member member = null;
if (v.getDiscordID() != null && !v.getDiscordID().isEmpty()) {
member = event.getGuild().getMemberById(v.getDiscordID());
}
sBuilder.append(v.getUsername()).append(" -> ").append(member == null ? "Unlinked" : member.getAsMention()).append("\r\n");
});
builder.setDescription(sBuilder);
pages.add(builder.build());
});
paginator.setItems(pages);
EmbedPaginator embedPaginator = paginator.build();
event.replyEmbeds(pages.get(0)).setEphemeral(false).queue(success -> success.retrieveOriginal().queue(msg -> embedPaginator.paginate(msg, 1)));
}
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.whitelist;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.messaging.Result;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import java.util.Collections;
import java.util.List;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Command to confirm a Whitelist request
*/
public class ConfirmWhitelistSlashCommand extends SDLinkSlashCommand {
public ConfirmWhitelistSlashCommand() {
super(false);
this.name = "whitelistconfirm";
this.help = "Confirm your Minecraft Account to complete whitelisting";
this.options = Collections.singletonList(new OptionData(OptionType.INTEGER, "code", "The verification code from the Minecraft Kick Message").setRequired(true));
}
@Override
protected void execute(SlashCommandEvent event) {
int mcCode = event.getOption("code") != null ? event.getOption("code").getAsInt() : 0;
if (mcCode == 0) {
event.reply("You need to provide a verification code").setEphemeral(true).queue();
return;
}
List<SDLinkAccount> accounts = sdlinkDatabase.findAll(SDLinkAccount.class);
if (accounts.isEmpty()) {
event.reply("Sorry, but this server does not contain any stored players in its database").setEphemeral(true).queue();
return;
}
for (SDLinkAccount account : accounts) {
if (account.getWhitelistCode().equalsIgnoreCase(String.valueOf(mcCode))) {
MinecraftAccount minecraftAccount = MinecraftAccount.standard(account.getUsername());
Result result = minecraftAccount.whitelistAccount(event.getMember(), event.getGuild());
event.reply(result.getMessage()).setEphemeral(true).queue();
return;
}
}
event.reply("Sorry, we could not verify your Minecraft account. Please try again").setEphemeral(true).queue();
}
}

View File

@@ -0,0 +1,53 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.whitelist;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.messaging.Result;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import java.util.List;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Remove a player from the whitelist, that was previously whitelisted through the bot
*/
public class UnWhitelistAccountSlashCommand extends SDLinkSlashCommand {
public UnWhitelistAccountSlashCommand() {
super(false);
this.name = "unwhitelist";
this.help = "Remove your previously whitelisted Minecraft account";
}
@Override
protected void execute(SlashCommandEvent event) {
List<SDLinkAccount> accounts = sdlinkDatabase.findAll(SDLinkAccount.class);
if (accounts.isEmpty()) {
event.reply("Sorry, but this server does not contain any stored players in its database").setEphemeral(true).queue();
return;
}
for (SDLinkAccount account : accounts) {
if (account.getDiscordID().equalsIgnoreCase(event.getMember().getId())) {
MinecraftAccount minecraftAccount = MinecraftAccount.standard(account.getUsername());
if (SDLinkPlatform.minecraftHelper.isPlayerWhitelisted(minecraftAccount).isError()) {
event.reply("Your account is not whitelisted in Minecraft. Cannot remove your account").setEphemeral(true).queue();
} else {
Result result = minecraftAccount.unwhitelistAccount();
event.reply(result.getMessage()).setEphemeral(true).queue();
}
break;
}
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.whitelist;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.util.MessageUtil;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import com.jagrosh.jdautilities.menu.EmbedPaginator;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Staff command to view whitelisted players on their server
* This list only contains players whitelisted using the bot
*/
public class ViewWhitelistedAccountsSlashCommand extends SDLinkSlashCommand {
public ViewWhitelistedAccountsSlashCommand() {
super(true);
this.name = "whitelisted";
this.help = "View a list of Whitelisted MC accounts";
}
@Override
protected void execute(SlashCommandEvent event) {
EmbedPaginator.Builder paginator = MessageUtil.defaultPaginator(event);
List<SDLinkAccount> accounts = sdlinkDatabase.findAll(SDLinkAccount.class).stream().filter(SDLinkAccount::isWhitelisted).toList();
EmbedBuilder builder = new EmbedBuilder();
ArrayList<MessageEmbed> pages = new ArrayList<>();
AtomicInteger count = new AtomicInteger();
if (accounts.isEmpty()) {
event.reply("There are no whitelisted accounts for this discord").setEphemeral(true).queue();
return;
}
MessageUtil.listBatches(accounts, 10).forEach(itm -> {
count.getAndIncrement();
builder.clear();
builder.setTitle("Whitelisted Accounts - Page " + count + "/" + (int)Math.ceil(((float)accounts.size() / 10)));
builder.setColor(Color.GREEN);
StringBuilder sBuilder = new StringBuilder();
itm.forEach(v -> {
Member member = null;
if (v.getDiscordID() != null && !v.getDiscordID().isEmpty()) {
member = event.getGuild().getMemberById(v.getDiscordID());
}
sBuilder.append(v.getUsername()).append(" -> ").append(member == null ? "Unlinked" : member.getAsMention()).append("\r\n");
});
builder.setDescription(sBuilder);
pages.add(builder.build());
});
paginator.setItems(pages);
EmbedPaginator embedPaginator = paginator.build();
event.replyEmbeds(pages.get(0)).setEphemeral(false).queue(success -> success.retrieveOriginal().queue(msg -> embedPaginator.paginate(msg, 1)));
}
}

View File

@@ -0,0 +1,81 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.commands.slash.whitelist;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import com.hypherionmc.sdlink.core.discord.commands.slash.SDLinkSlashCommand;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import com.hypherionmc.sdlink.core.util.SystemUtils;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import io.jsondb.InvalidJsonDbApiUsageException;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import java.util.Collections;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
import static com.hypherionmc.sdlink.core.managers.DatabaseManager.sdlinkDatabase;
/**
* @author HypherionSA
* Command to start the Whitelisting process
* This will generate the verification code the player needs to enter, to
* verify the account belongs to them
*/
public class WhitelistAccountCommand extends SDLinkSlashCommand {
public WhitelistAccountCommand() {
super(sdLinkConfig.whitelistingAndLinking.whitelisting.staffOnlyWhitelist);
this.name = "whitelist";
this.help = "Start the process of Whitelisting your Minecraft Account";
this.options = Collections.singletonList(new OptionData(OptionType.STRING, "mcname", "Your Minecraft Username").setRequired(true));
}
@Override
protected void execute(SlashCommandEvent event) {
String mcName = event.getOption("mcname") != null ? event.getOption("mcname").getAsString() : "";
if (mcName.isEmpty()) {
event.reply("You need to supply your Minecraft username").setEphemeral(true).queue();
return;
}
MinecraftAccount minecraftAccount = MinecraftAccount.standard(mcName);
String confirmCode = String.valueOf(SystemUtils.generateRandomJoinCode());
SDLinkAccount account = minecraftAccount.getStoredAccount();
if (account == null) {
account = minecraftAccount.newDBEntry();
account.setWhitelistCode(confirmCode);
try {
sdlinkDatabase.insert(account);
event.reply("Please join the Minecraft server and check the Kick Message for your account whitelist code. Then, run the command /whitelistconfirm codehere to finish whitelisting your account").setEphemeral(true).queue();
} catch (InvalidJsonDbApiUsageException e) {
e.printStackTrace();
event.reply("Could not start account whitelisting process. Please notify the server owner").setEphemeral(true).queue();
}
} else {
if (!account.getDiscordID().isEmpty() || SDLinkPlatform.minecraftHelper.isPlayerWhitelisted(minecraftAccount).isError()) {
event.reply("Sorry, this Minecraft account is already whitelisted").setEphemeral(true).queue();
return;
}
account.setWhitelistCode(confirmCode);
try {
sdlinkDatabase.upsert(account);
event.reply("Please join the Minecraft server and check the Kick Message for your account whitelist code. Then, run the command /whitelistconfirm codehere to finish whitelisting your account").setEphemeral(true).queue();
} catch (InvalidJsonDbApiUsageException e) {
e.printStackTrace();
event.reply("Could not start account whitelisting process. Please notify the server owner").setEphemeral(true).queue();
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.events;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.discord.commands.slash.general.ServerStatusSlashCommand;
import com.hypherionmc.sdlink.core.discord.hooks.BotReadyHooks;
import com.hypherionmc.sdlink.core.discord.hooks.DiscordMessageHooks;
import com.hypherionmc.sdlink.core.managers.ChannelManager;
import com.hypherionmc.sdlink.core.managers.PermissionChecker;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.session.ReadyEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
/**
* @author HypherionSA
* Class to provide Hooks for Discord Events, such as message received, and login
* NOTE TO DEVELOPERS: Don't add ANY LOGIC IN HERE. Rather implement it in a seperate class,
* and use these hooks to trigger that code
*/
public class DiscordEventHandler extends ListenerAdapter {
/**
* The bot received a message
*/
@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
if (event.isWebhookMessage())
return;
if (event.getAuthor() == event.getJDA().getSelfUser())
return;
if (!event.isFromGuild())
return;
DiscordMessageHooks.discordMessageEvent(event);
}
/**
* The bot is connected to discord and ready to begin sending messages
*/
@Override
public void onReady(@NotNull ReadyEvent event) {
if (event.getJDA().getStatus() == JDA.Status.CONNECTED) {
BotController.INSTANCE.getLogger().info("Successfully connected to discord");
PermissionChecker.checkBotSetup();
ChannelManager.loadChannels();
BotReadyHooks.startActivityUpdates(event);
BotReadyHooks.startTopicUpdates();
}
}
/**
* A button was clicked.
*/
@Override
public void onButtonInteraction(@NotNull ButtonInteractionEvent event) {
if (event.getComponentId().equals("sdrefreshbtn")) {
event.getMessage().editMessageEmbeds(ServerStatusSlashCommand.runStatusCommand()).queue();
event.reply("Success!").setEphemeral(true).queue();
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.hooks;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.managers.ChannelManager;
import com.hypherionmc.sdlink.core.messaging.MessageDestination;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import com.hypherionmc.sdlink.core.util.SystemUtils;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel;
import net.dv8tion.jda.api.events.session.ReadyEvent;
import java.util.concurrent.TimeUnit;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Hooks to run when the bot is ready
*/
public class BotReadyHooks {
/**
* Update the bot activity
* @param event The {@link ReadyEvent}
*/
public static void startActivityUpdates(ReadyEvent event) {
BotController.taskManager.scheduleAtFixedRate(() -> {
try {
if (event.getJDA().getStatus() == JDA.Status.CONNECTED) {
Activity act = Activity.of(sdLinkConfig.botConfig.botStatus.botStatusType, sdLinkConfig.botConfig.botStatus.botStatus
.replace("%players%", String.valueOf(SDLinkPlatform.minecraftHelper.getPlayerCounts().getLeft()))
.replace("%maxplayers%", String.valueOf(SDLinkPlatform.minecraftHelper.getPlayerCounts().getRight())));
if (sdLinkConfig.botConfig.botStatus.botStatusType == Activity.ActivityType.STREAMING) {
act = Activity.of(sdLinkConfig.botConfig.botStatus.botStatusType, sdLinkConfig.botConfig.botStatus.botStatus
.replace("%players%", String.valueOf(SDLinkPlatform.minecraftHelper.getPlayerCounts().getLeft()))
.replace("%maxplayers%", String.valueOf(SDLinkPlatform.minecraftHelper.getPlayerCounts().getRight())),
sdLinkConfig.botConfig.botStatus.botStatusStreamingURL);
}
event.getJDA().getPresence().setActivity(act);
}
} catch (Exception e) {
if (sdLinkConfig.generalConfig.debugging) {
BotController.INSTANCE.getLogger().info(e.getMessage());
}
}
}, sdLinkConfig.botConfig.statusUpdateInterval, sdLinkConfig.botConfig.statusUpdateInterval, TimeUnit.SECONDS);
}
/**
* Update the Chat Channel topic, if enabled
*/
public static void startTopicUpdates() {
if (!sdLinkConfig.botConfig.channelTopic.doTopicUpdates)
return;
BotController.taskManager.scheduleAtFixedRate(() -> {
try {
if (BotController.INSTANCE.isBotReady() && (sdLinkConfig.botConfig.channelTopic.channelTopic != null && !sdLinkConfig.botConfig.channelTopic.channelTopic.isEmpty())) {
StandardGuildMessageChannel channel = ChannelManager.getDestinationChannel(MessageDestination.CHAT);
if (channel != null) {
String topic = sdLinkConfig.botConfig.channelTopic.channelTopic
.replace("%players%", String.valueOf(SDLinkPlatform.minecraftHelper.getPlayerCounts().getLeft()))
.replace("%maxplayers%", String.valueOf(SDLinkPlatform.minecraftHelper.getPlayerCounts().getRight()))
.replace("%uptime%", SystemUtils.secondsToTimestamp(SDLinkPlatform.minecraftHelper.getServerUptime()));
channel.getManager().setTopic(topic).queue();
}
}
} catch (Exception e) {
if (sdLinkConfig.generalConfig.debugging) {
BotController.INSTANCE.getLogger().info(e.getMessage());
}
}
}, 6, 6, TimeUnit.MINUTES);
}
}

View File

@@ -0,0 +1,43 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.discord.hooks;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.managers.ChannelManager;
import com.hypherionmc.sdlink.core.messaging.MessageDestination;
import com.hypherionmc.sdlink.core.services.SDLinkPlatform;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Hook class to handle messages the bot receives
*/
public class DiscordMessageHooks {
/**
* Chat messages to be sent back to discord
*/
public static void discordMessageEvent(MessageReceivedEvent event) {
try {
if (event.getChannel().getIdLong() != ChannelManager.getDestinationChannel(MessageDestination.CHAT).getIdLong())
return;
if (event.getAuthor().isBot() && sdLinkConfig.chatConfig.ignoreBots)
return;
if (sdLinkConfig.generalConfig.debugging) {
BotController.INSTANCE.getLogger().info("Sending Message from {}: {}", event.getAuthor().getName(), event.getMessage().getContentStripped());
}
SDLinkPlatform.minecraftHelper.discordMessageReceived(event.getMember().getEffectiveName(), event.getMessage().getContentRaw());
} catch (Exception e) {
if (sdLinkConfig.generalConfig.debugging) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.managers;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.messaging.MessageDestination;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel;
import org.apache.commons.lang3.tuple.Pair;
import java.util.HashMap;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Load and Cache configured channels for later use
*/
public class ChannelManager {
private static StandardGuildMessageChannel consoleChannel;
private static final HashMap<MessageDestination, Pair<StandardGuildMessageChannel, Boolean>> channelMap = new HashMap<>();
/**
* Load configured channel, while always defaulting back to ChatChannel for channels that aren't configured
*/
public static void loadChannels() {
channelMap.clear();
JDA jda = BotController.INSTANCE.getJDA();
StandardGuildMessageChannel chatChannel = jda.getChannelById(StandardGuildMessageChannel.class, sdLinkConfig.channelsAndWebhooks.channels.chatChannelID);
StandardGuildMessageChannel eventChannel = jda.getChannelById(StandardGuildMessageChannel.class, sdLinkConfig.channelsAndWebhooks.channels.eventsChannelID);
consoleChannel = jda.getChannelById(StandardGuildMessageChannel.class, sdLinkConfig.channelsAndWebhooks.channels.consoleChannelID);
if (chatChannel != null) {
channelMap.put(MessageDestination.CHAT, Pair.of(chatChannel, false));
}
channelMap.put(MessageDestination.EVENT, eventChannel != null ? Pair.of(eventChannel, false) : Pair.of(chatChannel, false));
channelMap.put(MessageDestination.CONSOLE, consoleChannel != null ? Pair.of(consoleChannel, true) : Pair.of(chatChannel, false));
}
public static StandardGuildMessageChannel getConsoleChannel() {
return consoleChannel;
}
public static StandardGuildMessageChannel getDestinationChannel(MessageDestination destination) {
return channelMap.get(destination).getLeft();
}
}

View File

@@ -0,0 +1,23 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.managers;
import com.hypherionmc.sdlink.core.database.SDLinkAccount;
import io.jsondb.JsonDBTemplate;
/**
* @author HypherionSA
* Helper class to initialize the JSON database
*/
public class DatabaseManager {
public static final JsonDBTemplate sdlinkDatabase = new JsonDBTemplate("sdlinkstorage", "com.hypherionmc.sdlink.core.database");
public static void initialize() {
if (!sdlinkDatabase.collectionExists(SDLinkAccount.class)) {
sdlinkDatabase.createCollection(SDLinkAccount.class);
}
}
}

View File

@@ -0,0 +1,187 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.managers;
import com.hypherionmc.sdlink.core.discord.BotController;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Startup permission checker hook to check if the bot has all the required permissions to function
*/
public class PermissionChecker {
// Invite URL for bot shown in server logs
private static final String DISCORD_INVITE = "https://discord.com/api/oauth2/authorize?client_id={bot_id}&permissions=3154463760&scope=bot%20applications.commands";
// Base Permissions required by the bot to operate
private static final List<Permission> BOT_PERMS = new ArrayList<>() {{
add(Permission.NICKNAME_CHANGE);
add(Permission.NICKNAME_MANAGE);
add(Permission.MANAGE_WEBHOOKS);
add(Permission.MESSAGE_SEND);
add(Permission.MESSAGE_EMBED_LINKS);
add(Permission.MESSAGE_HISTORY);
add(Permission.MESSAGE_EXT_EMOJI);
add(Permission.MANAGE_ROLES);
add(Permission.MESSAGE_MANAGE);
}};
// Basic channel permissions required by all channels
private static final List<Permission> BASE_CHANNEL_PERMS = new ArrayList<>() {{
add(Permission.VIEW_CHANNEL);
add(Permission.MESSAGE_SEND);
add(Permission.MESSAGE_EMBED_LINKS);
add(Permission.MANAGE_WEBHOOKS);
}};
/**
* Run the permission checker to see if the bot has all the required permissions
*/
public static void checkBotSetup() {
StringBuilder builder = new StringBuilder();
builder.append("\r\n").append("******************* Simple Discord Link Errors *******************").append("\r\n");
AtomicInteger errCount = new AtomicInteger();
BotController controller = BotController.INSTANCE;
if (!controller.isBotReady())
return;
controller.getLogger().info("Discord Invite Link for Bot: {}", DISCORD_INVITE.replace("{bot_id}", controller.getJDA().getSelfUser().getId()));
if (controller.getJDA().getGuilds().isEmpty()) {
errCount.incrementAndGet();
builder.append(errCount.get())
.append(") ")
.append("Bot does not appear to be in any servers. You need to invite the bot to your discord server before chat relays will work. Use link ")
.append(DISCORD_INVITE.replace("{bot_id}", controller.getJDA().getSelfUser().getId())).append(" to invite the bot.")
.append("\r\n");
} else {
if (controller.getJDA().getGuilds().size() > 1) {
errCount.incrementAndGet();
builder.append(errCount.get())
.append(") ")
.append("Bot appears to be in multiple discord servers. This mod is only designed to work with a single discord server")
.append("\r\n");
} else {
Guild guild = controller.getJDA().getGuilds().get(0);
if (guild != null) {
Member bot = guild.getMemberById(controller.getJDA().getSelfUser().getIdLong());
EnumSet<Permission> botPerms = bot.getPermissionsExplicit();
RoleManager.loadRequiredRoles(errCount, builder);
if (!botPerms.contains(Permission.ADMINISTRATOR)) {
checkBotPerms(errCount, builder, botPerms);
checkChannelPerms(
sdLinkConfig.channelsAndWebhooks.channels.chatChannelID,
"Chat Channel",
errCount,
builder,
bot,
true,
true
);
checkChannelPerms(
sdLinkConfig.channelsAndWebhooks.channels.eventsChannelID,
"Events Channel",
errCount,
builder,
bot,
false,
false
);
checkChannelPerms(
sdLinkConfig.channelsAndWebhooks.channels.consoleChannelID,
"Console Channel",
errCount,
builder,
bot,
false,
false
);
}
}
}
}
if (errCount.get() > 0) {
builder.append("\r\n").append("******************* Simple Discord Link Errors *******************").append("\r\n");
controller.getLogger().info(builder.toString());
}
}
private static void checkBotPerms(AtomicInteger errCount, StringBuilder builder, EnumSet<Permission> permissions) {
BOT_PERMS.forEach(perm -> {
if (!permissions.contains(perm)) {
errCount.incrementAndGet();
builder.append(errCount.get())
.append(") ")
.append("Missing Bot Permission: ")
.append(perm.getName())
.append("\r\n");
}
});
}
private static void checkChannelPerms(Long channelID, String channelName, AtomicInteger errCount, StringBuilder builder, Member bot, boolean channelRequired, boolean isChat) {
if (channelRequired && channelID == 0) {
errCount.incrementAndGet();
builder.append(errCount.get())
.append(") ")
.append(channelName)
.append(" ID is not set.... This value is required")
.append("\r\n");
return;
}
StandardGuildMessageChannel channel = BotController.INSTANCE.getJDA().getChannelById(StandardGuildMessageChannel.class, channelID);
if (channel == null) {
errCount.incrementAndGet();
builder.append(errCount.get())
.append(") ")
.append(channelName)
.append(" ID does not point to a valid Discord Channel. Please double check this")
.append("\r\n");
} else {
EnumSet<Permission> chatPerms = bot.getPermissionsExplicit(channel);
BASE_CHANNEL_PERMS.forEach(perm -> {
if (!chatPerms.contains(perm)) {
errCount.incrementAndGet();
builder.append(errCount.get())
.append(") ")
.append("Missing ")
.append(channelName)
.append(" Channel Permission: ")
.append(perm.getName())
.append("\r\n");
}
});
if (isChat) {
if (sdLinkConfig.botConfig.channelTopic.doTopicUpdates && !chatPerms.contains(Permission.MANAGE_CHANNEL)) {
errCount.incrementAndGet();
builder.append(errCount.get()).append(") ").append("Missing Chat Channel Permission: Manage Channel. Topic updates will not work").append("\r\n");
}
}
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.managers;
import com.hypherionmc.sdlink.core.discord.BotController;
import net.dv8tion.jda.api.entities.Role;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Load and Cache roles needed by the bot
*/
public class RoleManager {
private static Role staffRole;
private static Role whitelistedRole;
private static Role linkedRole;
private static final HashMap<String, Role> commandRoles = new HashMap<>();
/**
* Check and load the roles required by the bot
* @param errCount
* @param builder
*/
public static void loadRequiredRoles(AtomicInteger errCount, StringBuilder builder) {
if (!sdLinkConfig.botConfig.staffRole.isEmpty()) {
staffRole = getRole(errCount, builder, "Staff", sdLinkConfig.botConfig.staffRole);
}
if (!sdLinkConfig.whitelistingAndLinking.whitelisting.autoWhitelistRole.isEmpty()) {
whitelistedRole = getRole(errCount, builder, "Whitelist", sdLinkConfig.whitelistingAndLinking.whitelisting.autoWhitelistRole);
}
if (!sdLinkConfig.whitelistingAndLinking.accountLinking.linkedRole.isEmpty()) {
linkedRole = getRole(errCount, builder, "Linked Account", sdLinkConfig.whitelistingAndLinking.accountLinking.linkedRole);
}
if (sdLinkConfig.linkedCommands.enabled) {
commandRoles.clear();
sdLinkConfig.linkedCommands.commands.forEach(cmd -> {
if (!cmd.discordRole.isEmpty()) {
Role role = getRole(errCount, builder, cmd.discordCommand + " usage", cmd.discordRole);
if (role != null) {
commandRoles.putIfAbsent(cmd.discordCommand, role);
}
}
});
}
}
/**
* Load a role from either a Name or ID
* @param errCount Counter holding the current error count
* @param builder String builder that is used to build the error messages
* @param roleIdentifier Log identifier for the role being loaded
* @param roleID The ID or Name of the role to load
* @return The role that matched or NULL
*/
private static Role getRole(AtomicInteger errCount, StringBuilder builder, String roleIdentifier, String roleID) {
Role role = BotController.INSTANCE.getJDA().getRoleById(roleID);
if (role != null) {
return role;
} else {
List<Role> roles = BotController.INSTANCE.getJDA().getRolesByName(roleID, true);
if (!roles.isEmpty()) {
return roles.get(0);
}
}
errCount.incrementAndGet();
builder.append(errCount.get())
.append(") ")
.append("Missing ")
.append(roleIdentifier)
.append(" Role. Role: ")
.append(roleID)
.append(" cannot be found in the server")
.append("\r\n");
return null;
}
public static Role getLinkedRole() {
return linkedRole;
}
public static Role getStaffRole() {
return staffRole;
}
public static Role getWhitelistedRole() {
return whitelistedRole;
}
public static HashMap<String, Role> getCommandRoles() {
return commandRoles;
}
}

View File

@@ -0,0 +1,86 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.managers;
import club.minnced.discord.webhook.WebhookClient;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.messaging.MessageDestination;
import com.hypherionmc.sdlink.core.messaging.SDLinkWebhookClient;
import com.hypherionmc.sdlink.core.util.EncryptionUtil;
import java.util.HashMap;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Load and cache Webhook clients for later use
*/
public class WebhookManager {
private static WebhookClient chatWebhookClient, eventWebhookClient, consoleWebhookClient;
private static final HashMap<MessageDestination, WebhookClient> clientMap = new HashMap<>();
/**
* Load configured webhook clients
* Webhooks that are not configured, will use their Channel ID instead
*/
public static void init() {
clientMap.clear();
if (sdLinkConfig == null || !sdLinkConfig.channelsAndWebhooks.webhooks.enabled)
return;
if (!sdLinkConfig.generalConfig.enabled)
return;
if (!sdLinkConfig.channelsAndWebhooks.webhooks.chatWebhook.isEmpty()) {
chatWebhookClient = new SDLinkWebhookClient(
"Chat",
EncryptionUtil.INSTANCE.decrypt(sdLinkConfig.channelsAndWebhooks.webhooks.chatWebhook)
).build();
BotController.INSTANCE.getLogger().info("Using Webhook for Chat Messages");
}
if (!sdLinkConfig.channelsAndWebhooks.webhooks.eventsWebhook.isEmpty()) {
eventWebhookClient = new SDLinkWebhookClient(
"Events",
EncryptionUtil.INSTANCE.decrypt(sdLinkConfig.channelsAndWebhooks.webhooks.eventsWebhook)
).build();
BotController.INSTANCE.getLogger().info("Using Webhook for Event Messages");
}
if (!sdLinkConfig.channelsAndWebhooks.webhooks.consoleWebhook.isEmpty()) {
consoleWebhookClient = new SDLinkWebhookClient(
"Console",
EncryptionUtil.INSTANCE.decrypt(sdLinkConfig.channelsAndWebhooks.webhooks.consoleWebhook)
).build();
BotController.INSTANCE.getLogger().info("Using Webhook for Console Messages");
}
if (chatWebhookClient != null) {
clientMap.put(MessageDestination.CHAT, chatWebhookClient);
}
clientMap.put(MessageDestination.EVENT, eventWebhookClient);
clientMap.put(MessageDestination.CONSOLE, consoleWebhookClient);
}
public static WebhookClient getWebhookClient(MessageDestination destination) {
return clientMap.get(destination);
}
public static void shutdown() {
if (chatWebhookClient != null) {
chatWebhookClient.close();
}
if (eventWebhookClient != null) {
eventWebhookClient.close();
}
if (consoleWebhookClient != null) {
consoleWebhookClient.close();
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.messaging;
/**
* @author HypherionSA
* Specifies to what channel a message should be delivered
*/
public enum MessageDestination {
CHAT,
EVENT,
CONSOLE;
public boolean isChat() {
return this == CHAT;
}
public boolean isEvent() {
return this == EVENT;
}
public boolean isConsole() {
return this == CONSOLE;
}
}

View File

@@ -0,0 +1,19 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.messaging;
/**
* @author HypherionSA
* Used to specify the type of message being sent
*/
public enum MessageType {
CHAT,
START_STOP,
JOIN_LEAVE,
ADVANCEMENT,
DEATH,
COMMAND,
CONSOLE
}

View File

@@ -0,0 +1,42 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.messaging;
/**
* @author HypherionSA
* Helper Class to return the result of interactions between Discord and Minecraft
*/
public class Result {
enum Type {
ERROR,
SUCCESS
}
private final Type type;
private final String message;
private Result(Type type, String message) {
this.type = type;
this.message = message;
}
public static Result success(String message) {
return new Result(Type.SUCCESS, message);
}
public static Result error(String message) {
return new Result(Type.ERROR, message);
}
public boolean isError() {
return this.type == Type.ERROR;
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,27 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.messaging;
import club.minnced.discord.webhook.WebhookClientBuilder;
/**
* @author HypherionSA
* Wrapped {@link WebhookClientBuilder} for our webhooks
*/
public class SDLinkWebhookClient extends WebhookClientBuilder {
public SDLinkWebhookClient(String name, String url) {
super(url);
this.setThreadFactory((job) -> {
Thread thread = new Thread(job);
thread.setName(name + " Webhook Thread");
thread.setDaemon(true);
return thread;
});
this.setWait(false);
}
}

View File

@@ -0,0 +1,209 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.messaging.discord;
import club.minnced.discord.webhook.WebhookClient;
import club.minnced.discord.webhook.send.WebhookEmbed;
import club.minnced.discord.webhook.send.WebhookEmbedBuilder;
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
import com.hypherionmc.sdlink.core.accounts.DiscordAuthor;
import com.hypherionmc.sdlink.core.config.impl.MessageChannelConfig;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.managers.ChannelManager;
import com.hypherionmc.sdlink.core.managers.WebhookManager;
import com.hypherionmc.sdlink.core.messaging.MessageType;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel;
import org.apache.commons.lang3.tuple.Triple;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Represents a message sent from Minecraft to Discord
* This ensures the message is properly formatted and configured
*/
public final class DiscordMessage {
private final MessageType messageType;
private final DiscordAuthor author;
private final String message;
private final Runnable afterSend;
/**
* Private instance. Use {@link DiscordMessageBuilder} to create an instance
*/
DiscordMessage(DiscordMessageBuilder builder) {
this.messageType = builder.getMessageType();
this.author = builder.getAuthor();
this.message = builder.getMessage();
this.afterSend = builder.getAfterSend();
}
/**
* Try to send the message to discord
*/
public void sendMessage() {
if (!BotController.INSTANCE.isBotReady())
return;
try {
if (messageType == MessageType.CONSOLE) {
sendConsoleMessage();
} else {
sendNormalMessage();
}
} catch (Exception e) {
if (sdLinkConfig.generalConfig.debugging) {
BotController.INSTANCE.getLogger().error("Failed to send Discord Message", e);
}
}
}
/**
* Send a Non Console relay message to discord
*/
private void sendNormalMessage() {
Triple<StandardGuildMessageChannel, WebhookClient, Boolean> channel = resolveDestination();
// Check if a webhook is configured, and use that instead
if (channel.getMiddle() != null && sdLinkConfig.channelsAndWebhooks.webhooks.enabled) {
WebhookMessageBuilder builder = new WebhookMessageBuilder();
builder.setUsername(this.author.getUsername());
if (!this.author.getAvatar().isEmpty()) {
builder.setAvatarUrl(this.author.getAvatar());
}
// Message must be an Embed
if (channel.getRight()) {
EmbedBuilder eb = buildEmbed(false);
WebhookEmbed web = WebhookEmbedBuilder.fromJDA(eb.build()).build();
builder.addEmbeds(web);
} else {
builder.setContent(message);
}
channel.getMiddle().send(builder.build());
} else {
// Use the configured channel instead
if (channel.getRight()) {
EmbedBuilder eb = buildEmbed(true);
channel.getLeft().sendMessageEmbeds(eb.build()).queue();
} else {
channel.getLeft().sendMessage(
this.messageType == MessageType.CHAT ?
sdLinkConfig.messageFormatting.chat.replace("%player%", author.getUsername()).replace("%message%", message)
: message)
.queue(success -> {
if (afterSend != null) {
afterSend.run();
}
});
}
}
}
/**
* Only used for console relay messages
*/
private void sendConsoleMessage() {
try {
if (!BotController.INSTANCE.isBotReady() || !sdLinkConfig.chatConfig.sendConsoleMessages)
return;
StandardGuildMessageChannel channel = ChannelManager.getConsoleChannel();
if (channel != null) {
channel.sendMessage(this.message).queue();
}
} catch (Exception e) {
if (sdLinkConfig.generalConfig.debugging) {
BotController.INSTANCE.getLogger().error("Failed to send console message", e);
}
}
if (afterSend != null) {
afterSend.run();
}
}
/**
* Build an embed with the supplied information
* @param withAuthor Should the author be appended to the embed. Not used for Webhooks
*/
private EmbedBuilder buildEmbed(boolean withAuthor) {
EmbedBuilder builder = new EmbedBuilder();
if (withAuthor) {
builder.setAuthor(
this.author.getUsername(),
null,
this.author.getAvatar().isEmpty() ? null : this.author.getAvatar()
);
}
builder.setDescription(message);
return builder;
}
/**
* Figure out where the message must be delivered to, based on the config values
*/
private Triple<StandardGuildMessageChannel, WebhookClient, Boolean> resolveDestination() {
switch (messageType) {
case CHAT -> {
MessageChannelConfig.DestinationObject chat = sdLinkConfig.messageDestinations.chat;
return Triple.of(
ChannelManager.getDestinationChannel(chat.channel),
WebhookManager.getWebhookClient(chat.channel),
chat.useEmbed
);
}
case START_STOP -> {
MessageChannelConfig.DestinationObject startStop = sdLinkConfig.messageDestinations.startStop;
return Triple.of(
ChannelManager.getDestinationChannel(startStop.channel),
WebhookManager.getWebhookClient(startStop.channel),
startStop.useEmbed
);
}
case JOIN_LEAVE -> {
MessageChannelConfig.DestinationObject joinLeave = sdLinkConfig.messageDestinations.joinLeave;
return Triple.of(
ChannelManager.getDestinationChannel(joinLeave.channel),
WebhookManager.getWebhookClient(joinLeave.channel),
joinLeave.useEmbed
);
}
case ADVANCEMENT -> {
MessageChannelConfig.DestinationObject advancement = sdLinkConfig.messageDestinations.advancements;
return Triple.of(
ChannelManager.getDestinationChannel(advancement.channel),
WebhookManager.getWebhookClient(advancement.channel),
advancement.useEmbed
);
}
case DEATH -> {
MessageChannelConfig.DestinationObject death = sdLinkConfig.messageDestinations.death;
return Triple.of(
ChannelManager.getDestinationChannel(death.channel),
WebhookManager.getWebhookClient(death.channel),
death.useEmbed
);
}
case COMMAND -> {
MessageChannelConfig.DestinationObject command = sdLinkConfig.messageDestinations.commands;
return Triple.of(
ChannelManager.getDestinationChannel(command.channel),
WebhookManager.getWebhookClient(command.channel),
command.useEmbed
);
}
}
// This code should never be reached, but it's added here as a fail-safe
MessageChannelConfig.DestinationObject chat = sdLinkConfig.messageDestinations.chat;
return Triple.of(ChannelManager.getDestinationChannel(chat.channel), WebhookManager.getWebhookClient(chat.channel), chat.useEmbed);
}
}

View File

@@ -0,0 +1,88 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.messaging.discord;
import com.hypherionmc.sdlink.core.accounts.DiscordAuthor;
import com.hypherionmc.sdlink.core.messaging.MessageType;
/**
* @author HypherionSA
* Used to construct a {@link DiscordMessage} to be sent back to discord
*/
public final class DiscordMessageBuilder {
private final MessageType messageType;
private DiscordAuthor author;
private String message;
private Runnable afterSend;
/**
* Construct a discord message
* @param messageType The type of message being sent
*/
public DiscordMessageBuilder(MessageType messageType) {
this.messageType = messageType;
}
/**
* Add an Author to the message
*/
public DiscordMessageBuilder author(DiscordAuthor author) {
this.author = author;
if (author.getUsername().equalsIgnoreCase("server")) {
this.author = DiscordAuthor.SERVER;
}
return this;
}
/**
* The Actual message that will be sent
*/
public DiscordMessageBuilder message(String message) {
message = message.replace("<@", "");
message = message.replace("@everyone", "");
message = message.replace("@here", "");
this.message = message;
return this;
}
public DiscordMessageBuilder afterSend(Runnable afterSend) {
this.afterSend = afterSend;
return this;
}
/**
* Build a Discord Message ready to be sent
*/
public DiscordMessage build() {
if (this.author == null) {
this.author = DiscordAuthor.SERVER;
}
if (this.message == null) {
this.message = "";
}
return new DiscordMessage(this);
}
public String getMessage() {
return message;
}
public MessageType getMessageType() {
return messageType;
}
public DiscordAuthor getAuthor() {
return author;
}
public Runnable getAfterSend() {
return afterSend;
}
}

View File

@@ -0,0 +1,28 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.services;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.services.helpers.IMinecraftHelper;
import java.util.ServiceLoader;
/**
* @author HypherionSA
* Service loader for library services
*/
public class SDLinkPlatform {
public static IMinecraftHelper minecraftHelper = load(IMinecraftHelper.class);
public static <T> T load(Class<T> clazz) {
final T loadedService = ServiceLoader.load(clazz)
.findFirst()
.orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName()));
BotController.INSTANCE.getLogger().debug("Loaded {} for service {}", loadedService, clazz);
return loadedService;
}
}

View File

@@ -0,0 +1,32 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.services.helpers;
import com.hypherionmc.sdlink.core.accounts.MinecraftAccount;
import com.hypherionmc.sdlink.core.messaging.Result;
import org.apache.commons.lang3.tuple.Pair;
import java.util.List;
/**
* @author HypherionSA
* Service to bridge communication between the Library and Minecraft
*/
public interface IMinecraftHelper {
void discordMessageReceived(String username, String message);
Result checkWhitelisting();
Result isPlayerWhitelisted(MinecraftAccount account);
Result whitelistPlayer(MinecraftAccount account);
Result unWhitelistPlayer(MinecraftAccount account);
List<MinecraftAccount> getWhitelistedPlayers();
Pair<Integer, Integer> getPlayerCounts();
List<MinecraftAccount> getOnlinePlayers();
long getServerUptime();
String getServerVersion();
Result executeMinecraftCommand(String command, String args);
boolean isOnlineMode();
}

View File

@@ -0,0 +1,126 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.util;
import com.hypherionmc.sdlink.core.discord.BotController;
import org.apache.commons.io.FileUtils;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Random;
/**
* @author HypherionSA
* Util Class to handle Encryption/Decryption of Bot-Tokens and Webhook URLS
* Since people DON'T READ THE COMMENTS and leave these in-tact,
* they are now encrypted by default
*/
public final class EncryptionUtil {
public static EncryptionUtil INSTANCE = getInstance();
private final boolean canRun;
// Instance of the Encryptor Used
private final StandardPBEStringEncryptor encryptor;
private static EncryptionUtil getInstance() {
if (INSTANCE == null) {
INSTANCE = new EncryptionUtil();
}
return INSTANCE;
}
private EncryptionUtil() {
String encCode = "";
File storageDir = new File("sdlinkstorage");
if (storageDir.exists())
storageDir.mkdirs();
// Try to read a saved encryption key, or try to save a new one
try {
File encKey = new File(storageDir.getAbsolutePath() + File.separator + "sdlink.enc");
if (!encKey.exists()) {
FileUtils.writeStringToFile(encKey, getSaltString(), StandardCharsets.UTF_8);
} else {
encCode = FileUtils.readFileToString(encKey, StandardCharsets.UTF_8);
}
} catch (Exception e) {
BotController.INSTANCE.getLogger().error("Failed to initialize Encryption", e);
}
encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(encCode);
canRun = !encCode.isEmpty();
}
/**
* Will Encrypt the string passed into it, if it's not already encrypted
* @param input The string to be encrypted
* @return The encrypted string
*/
public String encrypt(String input) {
if (!canRun)
return input;
if (isEncrypted(input)) {
return input;
}
input = "enc:" + input;
return encryptor.encrypt(input);
}
/**
* Decrypts an encrypted string
* @param input The encrypted String
* @return The Plain Text String
*/
public String decrypt(String input) {
if (!canRun)
return input;
input = internalDecrypt(input);
if (input.startsWith("enc:")) {
input = input.replaceFirst("enc:", "");
}
return input;
}
// Used internally
private String internalDecrypt(String input) {
if (!canRun)
return input;
return encryptor.decrypt(input);
}
// Test if String is encrypted
private boolean isEncrypted(String input) {
try {
String temp = internalDecrypt(input);
return temp.startsWith("enc:");
} catch (EncryptionOperationNotPossibleException ignored) {
// String is likely not encrypted
}
return false;
}
// Generate Random codes for encryption/decryption
private String getSaltString() {
String SALTCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
StringBuilder salt = new StringBuilder();
Random rnd = new Random();
while (salt.length() < 18) {
int index = (int) (rnd.nextFloat() * SALTCHARS.length());
salt.append(SALTCHARS.charAt(index));
}
return salt.toString();
}
}

View File

@@ -0,0 +1,126 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.util;
import com.hypherionmc.sdlink.core.accounts.DiscordAuthor;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.hypherionmc.sdlink.core.messaging.MessageType;
import com.hypherionmc.sdlink.core.messaging.discord.DiscordMessage;
import com.hypherionmc.sdlink.core.messaging.discord.DiscordMessageBuilder;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import static com.hypherionmc.sdlink.core.config.ConfigController.sdLinkConfig;
/**
* @author HypherionSA
* Log Appender to allow messages to be relayed from the Game Console to Discord
*/
@Plugin(name = "SDLinkLogging", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class LogReader extends AbstractAppender {
public static String logs = "";
private long time;
private Thread messageScheduler;
private static boolean isDevEnv = false;
protected LogReader(String name, Filter filter) {
super(name, filter, null, true, new Property[0]);
}
@PluginFactory
public static LogReader createAppender(
@PluginAttribute("name") String name,
@PluginElement("Filter") Filter filter) {
return new LogReader(name, filter);
}
public static void init(boolean isDev) {
isDevEnv = isDev;
LogReader da = LogReader.createAppender("SDLinkLogging", null);
((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(da);
da.start();
}
@Override
public void append(LogEvent event) {
if (BotController.INSTANCE.isBotReady()) {
if (event.getLevel().intLevel() < Level.DEBUG.intLevel()) {
logs += formatMessage(event) + "\n";
scheduleMessage();
}
}
}
private String formatMessage(LogEvent event) {
String devString = "**[" + formatTime(event.getTimeMillis()) + "]** " +
"**[" + event.getThreadName() + "/" + event.getLevel().name() + "]** " +
"**(" + event.getLoggerName().substring(event.getLoggerName().lastIndexOf(".") + 1) + ")** *" +
event.getMessage().getFormattedMessage() + "*";
String prodString = "**[" + formatTime(event.getTimeMillis()) + "]** " +
"**[" + event.getThreadName() + "/" + event.getLevel().name() + "]** *" +
event.getMessage().getFormattedMessage() + "*";
return isDevEnv ? devString : prodString;
}
private String formatTime(long millis) {
DateFormat obj = new SimpleDateFormat("HH:mm:ss");
Date res = new Date(millis);
return obj.format(res);
}
private void scheduleMessage() {
time = System.currentTimeMillis();
if (messageScheduler == null || !messageScheduler.isAlive()) {
messageScheduler = new Thread(() -> {
while (true) {
if (!BotController.INSTANCE.isBotReady())
return;
if (System.currentTimeMillis() - time > 250) {
if (logs.length() > 2000) {
logs = logs.substring(0, 1999);
}
DiscordMessage discordMessage = new DiscordMessageBuilder(MessageType.CONSOLE)
.message(logs)
.author(DiscordAuthor.SERVER)
.build();
if (sdLinkConfig.chatConfig.sendConsoleMessages) {
discordMessage.sendMessage();
}
logs = "";
break;
}
try {
Thread.sleep(30);
} catch (InterruptedException e) {
if (sdLinkConfig.generalConfig.debugging) {
BotController.INSTANCE.getLogger().error("Failed to send console message: {}", e.getMessage());
}
}
}
});
messageScheduler.start();
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.util;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.hypherionmc.sdlink.core.discord.BotController;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import com.jagrosh.jdautilities.menu.EmbedPaginator;
import net.dv8tion.jda.api.exceptions.PermissionException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author HypherionSA
* Util classes to help manage certain discord message actions
*/
public class MessageUtil {
/**
* Create an Embed Paginator for use with Slash Commands
* @param event The event of the executed command
*/
public static EmbedPaginator.Builder defaultPaginator(SlashCommandEvent event) {
return new EmbedPaginator.Builder()
.setTimeout(1, TimeUnit.MINUTES)
.setEventWaiter(BotController.INSTANCE.getEventWaiter())
.waitOnSinglePage(true)
.setFinalAction(m -> {
try {
m.clearReactions().queue();
m.delete().queue();
} catch(PermissionException ex) {
ex.printStackTrace();
event.reply(ex.getMessage()).setEphemeral(true).queue();
}
})
.setText((BiFunction<Integer, Integer, String>) null);
}
/**
* Split a large list of items into smaller sublists. This is to help with Discord limits on pagination
* @param source The list of objects to split
* @param length How many entries are allowed per sub-list
*/
public static <T> Stream<List<T>> listBatches(List<T> source, int length) {
if (length <= 0)
throw new IllegalArgumentException("length = " + length);
int size = source.size();
if (size <= 0)
return Stream.empty();
int fullChunks = (size - 1) / length;
return IntStream.range(0, fullChunks + 1).mapToObj(
n -> source.subList(n * length, n == fullChunks ? size : (n + 1) * length));
}
/**
* Same as {@link #listBatches(List, int)}, but for HashMaps
*/
public static <K, V> List<Map<K, V>> splitMap(Map<K, V> map, int size) {
List<List<Map.Entry<K, V>>> list = Lists.newArrayList(Iterables.partition(map.entrySet(), size));
return list.stream()
.map(entries ->
entries.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,101 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.util;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class SystemUtils {
/**
* Convert Bytes into a human-readable format, like 1GB
* From https://stackoverflow.com/a/3758880
* @param bytes The Size in Bytes
* @return The size formatted in KB, MB, GB, TB, PB etc
*/
public static String byteToHuman(long bytes) {
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024) {
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %ciB", value / 1024.0, ci.current());
}
// Time Conversion
public static final List<Long> times = Arrays.asList(
TimeUnit.DAYS.toMillis(365),
TimeUnit.DAYS.toMillis(30),
TimeUnit.DAYS.toMillis(1),
TimeUnit.HOURS.toMillis(1),
TimeUnit.MINUTES.toMillis(1),
TimeUnit.SECONDS.toMillis(1));
public static final List<String> timesString = Arrays.asList("year", "month", "day", "hour", "minute", "second");
/**
* Unix Timestamp to Duration
* @param duration Unix Timestamp
* @return Formatted Duration
*/
public static String toDuration(long duration) {
StringBuffer res = new StringBuffer();
for (int i = 0; i < times.size(); i++) {
Long current = times.get(i);
long temp = duration / current;
if (temp > 0) {
res.append(temp).append(" ").append(timesString.get(i)).append(temp != 1 ? "s" : "");
break;
}
}
if ("".equals(res.toString()))
return "0 seconds ago";
else
return res.toString();
}
/**
* Convert Seconds into a Timestamp
* @param sec Input in seconds
*/
public static String secondsToTimestamp(long sec) {
long seconds = sec % 60;
long minutes = (sec / 60) % 60;
long hours = (sec / 3600) % 24;
long days = sec / (3600 * 24);
String timeString = String.format("%02d hour(s), %02d minute(s), %02d second(s)", hours, minutes, seconds);
if (days > 0) {
timeString = String.format("%d day(s), %s", days, timeString);
}
return timeString;
}
/**
* Generate random verification code for Whitelisting and Account Linking
*/
public static int generateRandomJoinCode() {
return new Random().ints(1000, 9999).findFirst().getAsInt();
}
/*public static boolean hasPermission(BotController controller, Member member) {
if (controller.getAdminRole() != null) {
return member.getRoles().stream().anyMatch(r -> r.getIdLong() == controller.getAdminRole().getIdLong());
}
return member.hasPermission(Permission.ADMINISTRATOR) || member.hasPermission(Permission.KICK_MEMBERS);
}*/
}

View File

@@ -0,0 +1,22 @@
/*
* This file is part of sdlink-core, licensed under the MIT License (MIT).
* Copyright HypherionSA and Contributors
*/
package com.hypherionmc.sdlink.core.util;
import com.hypherionmc.sdlink.core.discord.BotController;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.hooks.InterfacedEventManager;
import org.jetbrains.annotations.NotNull;
/**
* @author HypherionSA
* Run discord events in seperate threads
*/
public class ThreadedEventManager extends InterfacedEventManager {
@Override
public void handle(@NotNull GenericEvent event) {
BotController.taskManager.submit(() -> super.handle(event));
}
}