commit 7e17389465c382e9c6fbf01ca6a9990ea32176a4 Author: Brandon <46827438+disclearing@users.noreply.github.com> Date: Wed May 3 15:27:43 2023 +0100 ggggggg diff --git a/CoreXD-API-main/CoreXD-API-main/.gitignore b/CoreXD-API-main/CoreXD-API-main/.gitignore new file mode 100644 index 0000000..715f307 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/.gitignore @@ -0,0 +1,38 @@ +# IntelliJ +.idea/ +*.iml +*.iws + +# Mac +.DS_Store + +# Maven +log/ +/target/ +dependency-reduced-pom.xml + +# Java +*.jar + +# JRebel +rebel.xml + +# Keep libs +#!lib/*.jar + +gradle +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/README.md b/CoreXD-API-main/CoreXD-API-main/README.md new file mode 100644 index 0000000..ade9175 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/README.md @@ -0,0 +1 @@ +# CoreXD-API diff --git a/CoreXD-API-main/CoreXD-API-main/api-keys.json b/CoreXD-API-main/CoreXD-API-main/api-keys.json new file mode 100644 index 0000000..991dec0 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/api-keys.json @@ -0,0 +1 @@ +["test"] \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/build.gradle b/CoreXD-API-main/CoreXD-API-main/build.gradle new file mode 100644 index 0000000..a0494e5 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/build.gradle @@ -0,0 +1,94 @@ +plugins { + id 'idea' + id 'java' + id 'maven-publish' + id 'org.jetbrains.kotlin.jvm' version '1.6.0' + id 'com.github.johnrengelman.shadow' version '5.2.0' +} + +group 'net.evilblock' +version '1.0' + +repositories { + mavenCentral() + mavenLocal() + + maven { url("https://m2.dv8tion.net/releases") } +} + +dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.6.0' + implementation 'commons-cli:commons-cli:1.4' + implementation 'net.evilblock.cubed:serializers:1.2' + implementation 'net.evilblock.cubed:store:1.2' + implementation 'com.minexd.rift:bukkit:1.2' + implementation 'com.google.guava:guava-io:r03' + implementation 'com.sparkjava:spark-kotlin:1.0.0-alpha' + implementation 'org.apache.commons:commons-lang3:3.12.0' + implementation 'net.dv8tion:JDA:4.3.0_281' + implementation("com.neovisionaries:nv-websocket-client:2.14") + implementation('com.squareup.okhttp3:okhttp:4.9.3') + + //Opus library support + implementation("club.minnced:opus-java:1.1.1") + + //Collections Utility + implementation('org.apache.commons:commons-collections4:4.4') + + //we use this only together with opus-java + // if that dependency is excluded it also doesn't need jna anymore + // since jna is a transitive runtime dependency of opus-java we don't include it explicitly as dependency + implementation('net.java.dev.jna:jna:5.9.0') + + /* Internal dependencies */ + + //General Utility + implementation("net.sf.trove4j:trove4j:3.0.3") + implementation('com.fasterxml.jackson.core:jackson-databind:2.13.1') + + implementation group: 'com.amazonaws', name: 'aws-java-sdk-ses', version: '1.11.983' + implementation 'org.projectlombok:lombok:1.18.18' +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} + +jar { + manifest { + attributes( + 'Main-Class': 'com.minexd.api.API' + ) + } +} + +shadowJar { + classifier = null + exclude '**/*.kotlin_metadata' +// exclude '**/*.kotlin_module' + exclude '**/*.kotlin_builtins' + archiveFileName = "nasa-api.jar" +} + +publishing { + publications { + shadow(MavenPublication) { publication -> + project.shadow.component(publication) + } + } +} + +tasks.withType(Jar) { + def home = System.properties['user.home'] + destinationDirectory = file("$home/Desktop/@Nasa/api/") +} diff --git a/CoreXD-API-main/CoreXD-API-main/config.json b/CoreXD-API-main/CoreXD-API-main/config.json new file mode 100644 index 0000000..7a1305d --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/config.json @@ -0,0 +1,18 @@ +{ + "awsAccessKeyID":"", + "awsSecretAccessKey":"", + "redisURI":"", + "mongoURI":"mongodb://localhost:27017", + "mongoDb":"minexd_dev", + "discordMainToken":"", + "discordMainID":"", + "discordStaffToken":"", + "discordStaffID":"", + "serverName":"", + "rank-1":"", + "rank-2":"", + "rank-3":"", + "rank-4":"", + "rank-5":"", + "rank-6":"" +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/gradle.properties b/CoreXD-API-main/CoreXD-API-main/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/gradlew b/CoreXD-API-main/CoreXD-API-main/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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 UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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 "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/CoreXD-API-main/CoreXD-API-main/gradlew.bat b/CoreXD-API-main/CoreXD-API-main/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/gradlew.bat @@ -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 diff --git a/CoreXD-API-main/CoreXD-API-main/settings.gradle b/CoreXD-API-main/CoreXD-API-main/settings.gradle new file mode 100644 index 0000000..21699f4 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'minexd-api' + diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/API.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/API.kt new file mode 100644 index 0000000..63a2183 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/API.kt @@ -0,0 +1,464 @@ +package com.minexd.api + +import com.amazonaws.auth.AWSStaticCredentialsProvider +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.regions.Regions +import com.amazonaws.services.simpleemail.AmazonSimpleEmailService +import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder +import com.google.common.io.Files +import com.minexd.api.console.ConsoleInputScanner +import com.minexd.api.logging.APILogger +import com.minexd.api.logging.APILoggingFormatter +import com.minexd.api.module.audit.AuditAPI +import com.minexd.api.module.chattag.TagAPI +import com.minexd.api.module.chattag.TagHandler +import com.minexd.api.module.chattag.TagRepository +import com.minexd.api.module.clan.ClanAPI +import com.minexd.api.module.clan.ClanHandler +import com.minexd.api.module.clan.ClanRepository +import com.minexd.api.module.discord.main.DiscordHandler +import com.minexd.api.module.friend.FriendAPI +import com.minexd.api.module.friend.FriendHandler +import com.minexd.api.module.friend.FriendRepository +import com.minexd.api.module.leaderboard.LeaderboardsAPI +import com.minexd.api.module.leaderboard.LeaderboardsHandler +import com.minexd.api.module.messaging.MessagingAPI +import com.minexd.api.module.party.PartyAPI +import com.minexd.api.module.party.PartyHandler +import com.minexd.api.module.party.PartyRepository +import com.minexd.api.module.profile.ProfileAPI +import com.minexd.api.module.profile.ProfileHandler +import com.minexd.api.module.profile.ProfileRepository +import com.minexd.api.module.profile.setting.SettingOption +import com.minexd.api.module.profile.statistic.StatisticAPI +import com.minexd.api.module.profile.statistic.StatisticsHandler +import com.minexd.api.module.profile.statistic.StatisticsRepository +import com.minexd.api.module.queue.service.QueuePollService +import com.minexd.api.module.rank.RankAPI +import com.minexd.api.module.rank.RankHandler +import com.minexd.api.module.rank.RankRepository +import com.minexd.api.module.register.RegisterAPI +import com.minexd.api.module.server.ServersAPI +import com.minexd.api.module.staff.StaffAPI +import com.minexd.api.module.staff.StaffHandler +import com.minexd.api.service.ServiceRegistry +import com.minexd.api.service.ServicesThread +import com.minexd.api.util.JsonTransformer +import com.minexd.rift.Rift +import com.mongodb.client.MongoDatabase +import net.evilblock.cubed.serializers.Serializers +import net.evilblock.cubed.serializers.impl.AbstractTypeSerializer +import net.evilblock.cubed.store.mongo.Mongo +import net.evilblock.cubed.store.redis.Redis +import net.evilblock.cubed.store.uuidcache.UUIDCache +import net.evilblock.cubed.store.uuidcache.impl.RedisUUIDCache +import net.evilblock.cubed.store.uuidcache.listener.UUIDUpdateMessageListeners +import net.evilblock.pidgin.Pidgin +import net.evilblock.pidgin.PidginOptions +import org.apache.commons.cli.DefaultParser +import org.apache.commons.cli.Option +import org.apache.commons.cli.Options +import spark.Request +import spark.Response +import spark.ResponseTransformer +import spark.Spark.* +import java.io.File +import java.util.concurrent.TimeUnit +import java.util.logging.ConsoleHandler +import java.util.logging.Level +import java.util.logging.Logger +import kotlin.concurrent.timer + +object API { + + var debug: Boolean = false + + @JvmStatic + fun main(args: Array) { + val portOption = Option.builder("p") + .required(false) + .hasArg(true) + .desc("Which port to listen on") + .longOpt("port") + .build() + + val parser = DefaultParser() + + val options = Options() + .addOption(portOption) + + var port = 4567 + + try { + val commandLine = parser.parse(options, args) + + if (commandLine.hasOption("p")) { + port = commandLine.getOptionValue("p").toInt() + } + } catch (e: Exception) { + e.printStackTrace() + return + } + + start(port) + } + + /** + * GSON response transformer for returning OOP objects + */ + private val jsonTransformer: ResponseTransformer = JsonTransformer() + + val logger: Logger = APILogger() + val directory: File = File(File(".").canonicalPath) + + lateinit var config: APIConfig + + val redis: Redis = Redis() + val mongo: Mongo = Mongo() + + lateinit var sesClient: AmazonSimpleEmailService + + lateinit var mongoDatabase: MongoDatabase + lateinit var uuidCache: UUIDCache + lateinit var pidgin: Pidgin + + val commandServer: CommandServer = CommandServer() + + var running: Boolean = false + var shutdown: Boolean = false + + init { + setupLogger() + + Runtime.getRuntime().addShutdownHook(Thread { + if (!shutdown) { + stop() + } + }) + } + + fun start(port: Int) { + val configFile = File("config.json") + if (configFile.exists()) { + Files.newReader(configFile, Charsets.UTF_8).use { reader -> + config = Serializers.gson.fromJson(reader.readLine(), APIConfig::class.java) + } + } else { + config = APIConfig() + Files.write(Serializers.gson.toJson(config, APIConfig::class.java), configFile, Charsets.UTF_8) + } + + logger.info("Initializing API...") + + Serializers.useGsonBuilderThenRebuild { builder -> + builder.registerTypeAdapter(SettingOption::class.java, AbstractTypeSerializer>()) + } + + APIKeys.read() + + sesClient = AmazonSimpleEmailServiceClientBuilder.standard() + .withRegion(Regions.US_EAST_2) + .withCredentials( + AWSStaticCredentialsProvider( + BasicAWSCredentials( + config.awsAccessKeyID, + config.awsSecretAccessKey + ) + ) + ) + .build() + + setupRedis() + setupMongoDB() + + pidgin = Pidgin("CoreXD", redis.jedisPool!!, Serializers.gson, PidginOptions(async = true)) + + setupRepositories() + setupHandlers() + + pidgin.registerListener(UUIDUpdateMessageListeners(uuidCache)) // register after repos & caches initialized + + Rift(RiftImpl()).initialLoad() + ServiceRegistry.register(QueuePollService, 5L, 5L) + + port(port) + logger.info("Configured listener to port $port") + + setupRoutes() + setupExceptionHandling() + + running = true + + commandServer.start() + ServicesThread().start() + + if (!config.discordMainToken.isEmpty()) { + DiscordHandler.init() + logger.info("Started Discord-Bot") + } + + if (!config.discordStaffToken.isEmpty()) { + com.minexd.api.module.discord.staff.StaffHandler.init() + logger.info("Started Staff-Discord-Bot") + } + + logger.info("Now accepting requests and console input!") + ConsoleInputScanner().start() // scanner is blocking, so do this last + } + + fun stop() { + running = false + spark.Spark.stop() + redis.close() + mongo.close() + pidgin.close() + shutdown = true + } + + fun reload() { + RankHandler.load() + TagHandler.load() + StaffHandler.load() + ProfileHandler.load() + + logger.info("Successfully reloaded the API server!") + } + + private fun setupLogger() { + val handler = ConsoleHandler() + handler.level = Level.ALL + handler.formatter = APILoggingFormatter() + + logger.addHandler(handler) + logger.useParentHandlers = false + logger.level = Level.ALL + logger.useParentHandlers = false + } + + private fun setupRedis() { + logger.info("Connecting to Redis...") + redis.connect(config.redisURI) + logger.info("Connected to Redis!") + } + + private fun setupMongoDB() { + logger.info("Connecting to MongoDB...") + + mongo.connect(config.mongoURI) + mongoDatabase = mongo.client.getDatabase(config.mongoDb) + + logger.info("Connected to MongoDB!") + } + + private fun setupRepositories() { + logger.info("Initializing repositories...") + + RankRepository.initialize() + FriendRepository.initialize() + StatisticsRepository.initialize() + ProfileRepository.initialize() + PartyRepository.initialize() + ClanRepository.initialize() + TagRepository.initialize() + } + + private fun setupHandlers() { + logger.info("Initializing caches...") + + uuidCache = RedisUUIDCache(redis) + uuidCache.alwaysFetch = true + uuidCache.load() + + ClanHandler.initialLoad() + PartyHandler.initialLoad() + RankHandler.load() + StaffHandler.load() + ProfileHandler.load() + LeaderboardsHandler.load() + FriendHandler.load() + StatisticsHandler.load() + TagHandler.load() + + timer(initialDelay = TimeUnit.MINUTES.toMillis(2L), period = TimeUnit.MINUTES.toMillis(2L)) { + uuidCache.load() + + RankHandler.load() + StaffHandler.load() + ProfileHandler.load() + LeaderboardsHandler.load() + } + } + + private fun setupRoutes() { + logger.info("Building routes...") + + // TODO: eventually change to annotation based dynamic routing, must be smart and build all routes + path("/api") { + before("/*") { request, response -> + handleRequest(request, response) + } + + path("/servers") { + get("/player-count", ServersAPI.playerCount) + post("/lookup", ServersAPI.lookup) + post("/status", ServersAPI.serversStatus, jsonTransformer) + } + + path("/ranks") { + get("/list", RankAPI.list, jsonTransformer) + get("/get/:id", RankAPI.get, jsonTransformer) + get("/get/:id/test", RankAPI.test, jsonTransformer) + post("/create/:id", RankAPI.create, jsonTransformer) + post("/delete/:id", RankAPI.delete, jsonTransformer) + post("/update/:id", RankAPI.update, jsonTransformer) + } + + path("/tags") { + get("/list", TagAPI.list, jsonTransformer) + get("/get/:id", TagAPI.get, jsonTransformer) + post("/create/:id", TagAPI.create, jsonTransformer) + post("/update/:id", TagAPI.update, jsonTransformer) + post("/delete/:id", TagAPI.delete, jsonTransformer) + } + + path("/audit") { + get("/grants", AuditAPI.getGrants, jsonTransformer) + get("/punishments", AuditAPI.getPunishments, jsonTransformer) + } + + path("/staff") { + get("/list", StaffAPI.list, jsonTransformer) + } + + path("/famous") { + get("/list",ProfileAPI.famousList, jsonTransformer) + } + + get("/leaderboards", LeaderboardsAPI.byGame, jsonTransformer) + + post("/register", RegisterAPI.register) + post("/register/complete", RegisterAPI.complete, jsonTransformer) + + path("/profile") { + get("/:uuid", ProfileAPI.get, jsonTransformer) + + path("/:uuid") { + get("/touch", ProfileAPI.touch, jsonTransformer) + post("/login", ProfileAPI.login, jsonTransformer) + post("/grant", ProfileAPI.grant) + post("/revokeGrant", ProfileAPI.revokeGrant) + post("/punish", ProfileAPI.punish) + post("/pardon", ProfileAPI.pardon) + post("/setTag", ProfileAPI.setTag) + + path("/permissions") { + post("/add", ProfileAPI.addPermission) + post("/revoke", ProfileAPI.revokePermission) + } + + post("/settings/update", ProfileAPI.updateSettings) + + get("/presence", ProfileAPI.getPresence, jsonTransformer) + get("/suspensionInfo", ProfileAPI.getSuspensionInfo, jsonTransformer) + get("/sharedAccounts", ProfileAPI.getSharedAccounts, jsonTransformer) + + post("/countVote", ProfileAPI.countVote) + post("/startSync", ProfileAPI.startSync) + post("/finishSync", ProfileAPI.finishSync) + post("/resetRegistration", RegisterAPI.reset) + post("/resetSync", ProfileAPI.resetSync) + } + } + + path("/sync") { + get("/lookupCode", ProfileAPI.findBySyncCode, jsonTransformer) + get("/lookupAccount", ProfileAPI.findBySyncedAccount, jsonTransformer) + } + + path("/friendships") { + get("/list", FriendAPI.listFriendships) + get("/incoming", FriendAPI.getIncoming) + get("/outgoing", FriendAPI.getOutgoing) + get("/show", FriendAPI.show) + post("/create", FriendAPI.create) + post("/destroy", FriendAPI.destroy) + post("/update", FriendAPI.update) + } + + path("/stats") { + get("/:uuid", StatisticAPI.quickStats, jsonTransformer) + get("/:uuid/:game", StatisticAPI.gameStats, jsonTransformer) + } + + path("/clans") { + get("/find/:search", ClanAPI.find, jsonTransformer) + post("/create", ClanAPI.create, jsonTransformer) + post("/disband", ClanAPI.disband, jsonTransformer) + post("/kick", ClanAPI.kick, jsonTransformer) + post("/invites/create", ClanAPI.createInvite) + post("/invites/destroy", ClanAPI.destroyInvite) + post("/invites/accept", ClanAPI.acceptInvite) + post("/update", ClanAPI.update) + post("/chat", ClanAPI.chat) + } + + path("/party") { + get("/find", PartyAPI.find, jsonTransformer) + post("/create", PartyAPI.create, jsonTransformer) + post("/disband", PartyAPI.disband) + post("/update", PartyAPI.update) + post("/invites/create", PartyAPI.createInvite) + post("/invites/destroy", PartyAPI.destroyInvite) + post("/invites/accept", PartyAPI.acceptInvite) + post("/kick", PartyAPI.kick, jsonTransformer) + post("/leave", PartyAPI.leave, jsonTransformer) + post("/chat", PartyAPI.chat) + } + + path("/messaging") { + post("/message", MessagingAPI.message) + } + } + } + + private fun handleRequest(request: Request, response: Response) { + response.type("application/json") + + val apiKey = request.headers("X-API-Key") + if (apiKey == null) { + halt(401, "Missing API key") + } + + // TODO: maybe switch to a database instead of flat-file lol + if (!APIKeys.keys.contains(apiKey)) { + println("Un-authorized API request: $apiKey") + halt(401, "Unauthorized") + } + + if (request.contentType() != null && request.contentType() != "application/json" && request.contentType() != "application/json; charset=UTF-8") { + println("Malformed content-type") + halt(400, "Bad request") + } + } + + private fun setupExceptionHandling() { + after("/*") { request, response -> + println(response.body()) + logger.info( + response.status() + .toString() + " " + request.requestMethod() + " " + request.url() + "/" + request.servletPath() + ) + } + + exception(java.lang.Exception::class.java) { exception, request, response -> + logger.info("ERROR 500 " + request.pathInfo()) + exception.printStackTrace() + } + + exception(Exception::class.java) { exception, request, response -> + logger.info("ERROR 500 " + request.pathInfo()) + exception.printStackTrace() + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/APIConfig.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/APIConfig.kt new file mode 100644 index 0000000..c5b2ea7 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/APIConfig.kt @@ -0,0 +1,26 @@ +package com.minexd.api + +class APIConfig { + + val awsAccessKeyID = "AKIAY55EPDLCSRD7LV4X" + val awsSecretAccessKey = "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" + + val redisURI: String = "redis://localhost:6379?db=0" + + val mongoURI: String = "mongodb://localhost:27017" + val mongoDb: String = "minexd_dev" + + val discordMainToken: String = "" + val discordStaffToken: String = "" + val discordMainID: String = "" + val discordStaffID: String = "" + val serverName: String = "" + + val rank1: String = "" + val rank2: String = "" + val rank3: String = "" + val rank4: String = "" + val rank5: String = "" + val rank6: String = "" + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/APIKeys.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/APIKeys.kt new file mode 100644 index 0000000..91ec54d --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/APIKeys.kt @@ -0,0 +1,24 @@ +package com.minexd.api + +import com.google.common.io.Files +import com.google.gson.JsonArray +import net.evilblock.cubed.serializers.Serializers +import java.io.File +import java.util.concurrent.ConcurrentHashMap + +object APIKeys { + + val keys: MutableSet = ConcurrentHashMap.newKeySet() + + fun read() { + val file = File("api-keys.json") + if (!file.exists()) { + Files.write("[]".toByteArray(Charsets.UTF_8), file) + } else { + keys.addAll(Files.newReader(file, Charsets.UTF_8) + .use { reader -> Serializers.gson.fromJson(reader.readLine(), JsonArray::class.java) } + .map { it.asString }) + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/CommandServer.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/CommandServer.kt new file mode 100644 index 0000000..92c6638 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/CommandServer.kt @@ -0,0 +1,66 @@ +package com.minexd.api + +import com.minexd.api.module.profile.ProfileHandler +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class CommandServer : Thread("Command-Server") { + + val commandQueue: Queue = ConcurrentLinkedQueue() + + override fun run() { + while (API.running) { + try { + tickCommands() + } catch (e: Exception) { + e.printStackTrace() + } + + sleep(50L) + } + } + + private fun tickCommands() { + if (commandQueue.isEmpty()) { + return + } + + var command: String? + while (commandQueue.poll().also { command = it } != null) { + API.logger.info("[Console] $command") + + when { + command.equals("help", ignoreCase = true) -> { + API.logger.info("/help") + API.logger.info("/reload") + API.logger.info("/stop") + API.logger.info("/clear-cache") + } + command.equals("stop", ignoreCase = true) -> { + API.stop() + } + command.equals("reload", ignoreCase = true) -> { + API.reload() + } + command.equals("cache info", ignoreCase = true) -> { + val profiles = ProfileHandler.getLoadedProfiles().size + API.logger.info("There are $profiles loaded profiles") + } + command.equals("cache clear", ignoreCase = true) -> { + val profiles = ProfileHandler.getLoadedProfiles().size + ProfileHandler.clearLoadedProfiles() + API.logger.info("Unloaded $profiles profiles") + } + command.equals("debug on", ignoreCase = true) -> { + API.debug = true + API.logger.info("Debug enabled") + } + command.equals("debug off", ignoreCase = true) -> { + API.debug = false + API.logger.info("Debug disabled") + } + } + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/Constants.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/Constants.kt new file mode 100644 index 0000000..b5bbba3 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/Constants.kt @@ -0,0 +1,8 @@ +package com.minexd.api + +object Constants { + + const val COLOR_CHAR = '§' + const val CONSOLE_NAME = "${COLOR_CHAR}4${COLOR_CHAR}lConsole" + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/DynamicRoute.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/DynamicRoute.kt new file mode 100644 index 0000000..291894b --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/DynamicRoute.kt @@ -0,0 +1,7 @@ +package com.minexd.api + +class DynamicRoute() { + + + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/Repository.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/Repository.kt new file mode 100644 index 0000000..0a966bd --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/Repository.kt @@ -0,0 +1,7 @@ +package com.minexd.api + +interface Repository { + + fun initialize() + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/RiftImpl.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/RiftImpl.kt new file mode 100644 index 0000000..0e9a867 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/RiftImpl.kt @@ -0,0 +1,30 @@ +package com.minexd.api + +import com.minexd.rift.plugin.Plugin +import net.evilblock.cubed.store.redis.Redis +import java.io.File +import java.util.logging.Logger + +class RiftImpl : Plugin { + + override fun getDirectory(): File { + return API.directory + } + + override fun getLogger(): Logger { + return API.logger + } + + override fun getRedis(): Redis { + return API.redis + } + + override fun hasPresence(): Boolean { + return false + } + + override fun getInstanceID(): String { + throw IllegalStateException("Not supported") + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/console/ConsoleInputScanner.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/console/ConsoleInputScanner.kt new file mode 100644 index 0000000..e429f3a --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/console/ConsoleInputScanner.kt @@ -0,0 +1,26 @@ +package com.minexd.api.console + +import com.minexd.api.API +import java.util.* + +class ConsoleInputScanner { + + fun start() { + val scanner = Scanner(System.`in`) + while (API.running) { + try { + while (scanner.hasNextLine()) { + val command = scanner.nextLine() + API.commandServer.commandQueue.add(command) + } + } catch (e: Exception) { + e.printStackTrace() + } + + Thread.sleep(50L) + } + + println("Console Input Scanner finished") + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/logging/APILogger.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/logging/APILogger.kt new file mode 100644 index 0000000..bc0034e --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/logging/APILogger.kt @@ -0,0 +1,9 @@ +package com.minexd.api.logging + +import java.util.logging.Logger + +class APILogger : Logger("API", null) { + + + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/logging/APILoggingFormatter.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/logging/APILoggingFormatter.kt new file mode 100644 index 0000000..55bc0d9 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/logging/APILoggingFormatter.kt @@ -0,0 +1,33 @@ +package com.minexd.api.logging + +import java.text.SimpleDateFormat +import java.time.Instant +import java.util.* +import java.util.logging.Formatter +import java.util.logging.LogRecord + +class APILoggingFormatter : Formatter() { + + companion object { + private const val PREFIX = "[API]" + private val DT_FORMAT: SimpleDateFormat = SimpleDateFormat("MM/dd/yyyy HH:mm:ss") + } + + override fun format(record: LogRecord): String { + return buildString { + append("[" + formatTime() + "]") + append(" ") + append("(" + record.level.name + ")") + append(": ") + append(PREFIX) + append(" ") + append(record.message) + append("\n") + } + } + + private fun formatTime(): String { + return DT_FORMAT.format(Date.from(Instant.now())) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/audit/AuditAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/audit/AuditAPI.kt new file mode 100644 index 0000000..8923531 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/audit/AuditAPI.kt @@ -0,0 +1,19 @@ +package com.minexd.api.module.audit + +import com.minexd.api.module.profile.ProfileRepository +import spark.Route +import java.util.* + +object AuditAPI { + + val getGrants: Route = Route { request, response -> + val id = UUID.fromString(request.queryParams("id")) + return@Route ProfileRepository.findGrantsIssuedBy(id) + } + + val getPunishments: Route = Route { request, response -> + val id = UUID.fromString(request.queryParams("id")) + return@Route ProfileRepository.findPunishmentsIssuedBy(id) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/Tag.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/Tag.kt new file mode 100644 index 0000000..4d6e77f --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/Tag.kt @@ -0,0 +1,39 @@ +package com.minexd.api.module.chattag + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 7/20/2021 + * + * Data class (POJO) that backs Tags, this is shared w/ CoreXD + */ +class Tag(var name: String) { + + var content: String = "[SET_ME]" + var tagLocation: TagLocation = TagLocation.AFTER + + internal fun handleUpdate(otherTag: Tag) { + this.name = otherTag.name + this.content = otherTag.content + this.tagLocation = otherTag.tagLocation + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Tag + + if (name != other.name) return false + if (content != other.content) return false + if (tagLocation != other.tagLocation) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + content.hashCode() + result = 31 * result + tagLocation.hashCode() + return result + } +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagAPI.kt new file mode 100644 index 0000000..82e2bcb --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagAPI.kt @@ -0,0 +1,137 @@ +package com.minexd.api.module.chattag + +import com.google.gson.JsonObject +import com.minexd.api.API +import com.minexd.api.module.rank.RankHandler +import net.evilblock.cubed.serializers.Serializers +import net.evilblock.pidgin.message.Message +import spark.Route +import spark.kotlin.halt + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 7/20/2021 + */ +object TagAPI { + + // TODO: 7/20/2021 send pidgin update messages when updates occur so other servers can notice + + val list = Route { request, response -> + return@Route TagHandler.getTags().sortedBy { it.name } + } + + val get = Route { request, response -> + return@Route RankHandler.getRankById(request.params(":id")) + } + + val create = Route { request, response -> + val name = request.params(":id") + + if (TagHandler.getByName(name) != null) { + return@Route halt(400, "A \'$name\' tag already exists!") + } + + val body = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + + val tag = Tag(name) + + if (body.has("content")) { + val content = body["content"].asString + tag.content = content + } + + if (body.has("tagLocation")) { + val tagLocationString = body["tagLocation"].asString + + var tagLocation: TagLocation = TagLocation.AFTER + try { + tagLocation = TagLocation.valueOf(tagLocationString) + } catch (ex: IllegalArgumentException) { + return@Route halt(404, "Not a valid tag location") + } + + tag.tagLocation = tagLocation + } + + TagHandler.cache(tag) + TagRepository.save(tag) + + API.pidgin.sendMessage( + Message( + id = "TagUpdate", + data = mapOf( + "TagID" to tag.name, + "Update" to TagUpdate.UPDATE + ) + ) + ) + + return@Route tag + } + + val delete = Route { request, response -> + val tag = TagHandler.getByName(request.params(":id")) ?: return@Route halt(404) + + TagHandler.forget(tag) + TagRepository.delete(tag) + + API.pidgin.sendMessage( + Message( + id = "TagUpdate", + data = mapOf( + "TagID" to tag.name, + "Update" to TagUpdate.DELETE + ) + ) + ) + + return@Route tag + } + + val update = Route { request, response -> + val tag = TagHandler.getByName(request.params(":id")) ?: return@Route halt(404) + + var updated = false + + val body = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + + if (body.has("content")) { + val newBody = body["content"].asString + tag.content = newBody + updated = true + } + + if (body.has("tagLocation")) { + val newPossTagLoc = body["tagLocation"].asString + var tagLoc: TagLocation = TagLocation.AFTER + + try { + tagLoc = TagLocation.valueOf(newPossTagLoc) + } catch (ex: IllegalArgumentException) { + return@Route halt(404, "Not a valid tag location") + } + + tag.tagLocation = tagLoc + updated = true + } + + + if (updated) { + TagHandler.cache(tag) + TagRepository.save(tag) + API.pidgin.sendMessage( + Message( + id = "TagUpdate", + data = mapOf( + "TagID" to tag.name, + "Update" to TagUpdate.UPDATE + ) + ) + ) + } else { + response.status(404) + } + + return@Route tag + } +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagHandler.kt new file mode 100644 index 0000000..ec07d02 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagHandler.kt @@ -0,0 +1,48 @@ +package com.minexd.api.module.chattag + +import java.util.concurrent.ConcurrentHashMap + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 7/20/2021 + */ +object TagHandler { + + private val tags = ConcurrentHashMap() + + fun load() { + val loadedTags = TagRepository.load() + for (tag in loadedTags) { + val existing = getByName(tag.name) + if (existing != null) { + existing.handleUpdate(tag) + } else { + cache(tag) + } + } + } + + fun cache(tag: Tag) { + tags[tag.name.lowercase()] = tag + } + + fun getByName(name: String?): Tag? { + if (name.isNullOrEmpty()) { + return null + } + for (tag in tags.values) { + if (tag.name.equals(name, ignoreCase = true)) { + return tag + } + } + return null + } + + fun getTags(): Collection { + return tags.values + } + + fun forget(tag: Tag) { + tags.remove(tag.name.lowercase()) + } +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagLocation.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagLocation.kt new file mode 100644 index 0000000..151d14f --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagLocation.kt @@ -0,0 +1,10 @@ +package com.minexd.api.module.chattag + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 7/20/2021 + */ +enum class TagLocation { + BEFORE, // typically before the rank & display name (e.g., [TAG] [RANK_NAME] Username: Message) + AFTER // (e.g., [RANK_NAME] Username [TAG]: Message) +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagRepository.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagRepository.kt new file mode 100644 index 0000000..eaabb3d --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagRepository.kt @@ -0,0 +1,55 @@ +package com.minexd.api.module.chattag + +import com.google.gson.reflect.TypeToken +import com.minexd.api.API +import com.minexd.api.Repository +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.Filters +import com.mongodb.client.model.ReplaceOptions +import net.evilblock.cubed.serializers.Serializers +import org.bson.Document +import java.lang.reflect.Type + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 7/20/2021 + */ +object TagRepository : Repository { + + private val TAG_TYPE: Type = object : TypeToken() {}.type + + private lateinit var tagsCollection: MongoCollection + + override fun initialize() { + tagsCollection = API.mongoDatabase.getCollection("chattags") + } + + internal fun load(): Set { + return mutableSetOf().also { tags -> + val documents = tagsCollection.find() + for (document in documents) { + tags.add(deserialize(document)) + } + } + } + + fun findTagByName(name: String): Tag? { + return deserialize(tagsCollection.find(Filters.eq("name", name)).first() ?: return null) + } + + fun save(tag: Tag) { + tagsCollection.replaceOne( + Filters.eq("name", tag.name), + Document.parse(Serializers.gson.toJson(tag, TAG_TYPE)), + ReplaceOptions().upsert(true) + ) + } + + fun delete(tag: Tag) { + tagsCollection.deleteOne(Filters.eq("name", tag.name)) + } + + private fun deserialize(document: Document): Tag { + return Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), TAG_TYPE) as Tag + } +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagUpdate.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagUpdate.kt new file mode 100644 index 0000000..2f00e77 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/chattag/TagUpdate.kt @@ -0,0 +1,10 @@ +package com.minexd.api.module.chattag + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 7/20/2021 + */ +enum class TagUpdate { + UPDATE, + DELETE +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/Clan.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/Clan.kt new file mode 100644 index 0000000..9dab0a9 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/Clan.kt @@ -0,0 +1,77 @@ +package com.minexd.api.module.clan + +import java.util.* + +class Clan( + val id: UUID = UUID.randomUUID(), + var name: String, + var leader: UUID +) { + + /** + * When this clan was created. + */ + val createdAt: Long = System.currentTimeMillis() + + /** + * The password to join without being invited. + */ + var password: String? = null + + /** + * This clan's tag, which is a shorthand or abbreviation that represents this clan. + */ + var tag: String? = null + + /** + * This clan's MOTD (message of the day). + */ + var motd: String = "This is the default description..." + + /** + * The members that form this clan. + */ + internal val members: MutableMap = hashMapOf() + + /** + * Invitations sent to players to join this clan. + */ + internal val invites: MutableMap = hashMapOf() + + fun isMember(player: UUID): Boolean { + return members.containsKey(player) + } + + fun getMember(player: UUID): ClanMember? { + return members[player] + } + + fun hasInvite(player: UUID): Boolean { + if (invites.containsKey(player)) { + val invite = invites[player]!! + if (System.currentTimeMillis() - invite.createdAt <= 86_400_000L) { + return true + } else { + invites.remove(player) + } + } + return false + } + + fun getInvite(player: UUID): ClanInvite? { + return invites[player] + } + + fun addInvite(invite: ClanInvite) { + invites[invite.player] = invite + } + + fun removeInvite(player: UUID) { + invites.remove(player) + } + + companion object { + val NAME_REGEX = Regex("^[-a-zA-Z0-9]+") + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanAPI.kt new file mode 100644 index 0000000..a2cba93 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanAPI.kt @@ -0,0 +1,93 @@ +package com.minexd.api.module.clan + +import com.minexd.api.API +import com.minexd.api.module.clan.result.CreateError +import com.minexd.api.module.party.PartyHandler +import spark.Route +import spark.kotlin.halt +import java.util.* + +object ClanAPI { + + val find: Route = Route { request, response -> + val search = request.params(":search") + + try { + val uuid = UUID.fromString(search) + + val clan = ClanHandler.getClanById(uuid) ?: ClanHandler.getClanByPlayer(uuid) + if (clan != null) { + return@Route clan + } + } catch (e: Exception) { } + + var clan: Clan? = ClanHandler.getClanByName(search) + if (clan == null) { + val playerUUID = API.uuidCache.uuid(search) + if (playerUUID != null) { + clan = ClanHandler.getClanByPlayer(playerUUID) + } + } + + return@Route clan ?: halt(404) + } + + val create: Route = Route { request, response -> + val playerUUID = UUID.fromString(request.queryParams("player_id")) + + if (PartyHandler.getPartyByPlayer(playerUUID) != null) { + return@Route halt(400, com.minexd.api.module.party.result.CreateError.ALREADY_IN_PARTY.name) + } + + val name = request.body() + if (name.isEmpty() || name.length > 12 || !name.matches(Clan.NAME_REGEX)) { + return@Route halt(400, CreateError.NAME_INVALID.name) + } + + if (ClanHandler.getClanByName(name) != null) { + return@Route halt(400, CreateError.NAME_TAKEN.name) + } + + if (ClanHandler.getClanByPlayer(playerUUID) != null) { + return@Route halt(400, CreateError.ALREADY_IN_CLAN.name) + } + + val clan = Clan(name = name, leader = playerUUID) + clan.members[playerUUID] = ClanMember(playerUUID, ClanMember.Role.LEADER) + + ClanHandler.cache(clan) + ClanRepository.saveClan(clan) + + response.status(201) + return@Route clan + } + + val disband: Route = Route { request, response -> + + } + + val kick: Route = Route { request, response -> + + } + + val createInvite: Route = Route { request, response -> + + } + + val destroyInvite: Route = Route { request, response -> + + } + + val acceptInvite: Route = Route { request, response -> + + } + + val update: Route = Route { request, response -> + + } + + val chat: Route = Route { request, response -> + + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanHandler.kt new file mode 100644 index 0000000..d3257ce --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanHandler.kt @@ -0,0 +1,60 @@ +package com.minexd.api.module.clan + +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +object ClanHandler { + + private val clanById: MutableMap = ConcurrentHashMap() + private val clanByName: MutableMap = ConcurrentHashMap() + private val clanByPlayer: MutableMap = ConcurrentHashMap() + + fun initialLoad() { + + } + + fun getClanById(id: UUID): Clan? { + return clanById[id] + } + + fun getClanByName(name: String): Clan? { + return clanByName[name.toLowerCase()] + } + + fun getClanByPlayer(player: UUID): Clan? { + return clanByPlayer[player] + } + + fun cache(clan: Clan) { + clanById[clan.id] = clan + clanByName[clan.name.toLowerCase()] = clan + + for (member in clan.members.keys) { + clanByPlayer[member] = clan + } + } + + fun forget(clan: Clan) { + clanById.remove(clan.id) + clanByName.remove(clan.name.toLowerCase()) + + for (member in clan.members.keys) { + clanByPlayer.remove(member) + } + } + + fun rename(clan: Clan, newName: String) { + clanByName.remove(clan.name.toLowerCase()) + clan.name = newName + clanByName[clan.name.toLowerCase()] + } + + fun cache(player: UUID, clan: Clan) { + clanByPlayer[player] = clan + } + + fun forget(player: UUID) { + clanByPlayer.remove(player) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanInvite.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanInvite.kt new file mode 100644 index 0000000..8b86751 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanInvite.kt @@ -0,0 +1,5 @@ +package com.minexd.api.module.clan + +import java.util.* + +data class ClanInvite(val player: UUID, var invitedBy: UUID? = null, val createdAt: Long = System.currentTimeMillis()) \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanMember.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanMember.kt new file mode 100644 index 0000000..8524a45 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanMember.kt @@ -0,0 +1,18 @@ +package com.minexd.api.module.clan + +import java.util.* + +data class ClanMember(val uuid: UUID = UUID.randomUUID(), var role: Role) { + + val createdAt: Long = System.currentTimeMillis() + + constructor(uuid: UUID) : this(uuid, Role.MEMBER) + + enum class Role(val displayName: String) { + LEADER("***"), + CAPTAIN("**"), + OFFICER("*"), + MEMBER(""); + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanRepository.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanRepository.kt new file mode 100644 index 0000000..e8874b8 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/ClanRepository.kt @@ -0,0 +1,46 @@ +package com.minexd.api.module.clan + +import com.google.gson.reflect.TypeToken +import com.minexd.api.API +import com.minexd.api.Repository +import com.mongodb.BasicDBObject +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.Collation +import com.mongodb.client.model.CollationStrength +import com.mongodb.client.model.IndexOptions +import com.mongodb.client.model.ReplaceOptions +import net.evilblock.cubed.serializers.Serializers +import org.bson.Document +import java.util.* + +object ClanRepository : Repository { + + private val CLAN_TYPE = object : TypeToken() {}.type + + private lateinit var clansCollection: MongoCollection + + override fun initialize() { + clansCollection = API.mongoDatabase.getCollection("clans") + clansCollection.createIndex(BasicDBObject("id", 1)) + clansCollection.createIndex(BasicDBObject("name", 1), IndexOptions().collation(Collation.builder() + .locale("en") + .collationStrength(CollationStrength.SECONDARY) + .build())) // case insensitive index for name + } + + fun findClan(id: UUID): Clan? { + val document = clansCollection.find(Document("id", id.toString())).first() ?: return null + return Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), CLAN_TYPE) + } + + fun findClan(name: String): Clan? { + val document = clansCollection.find(Document("name", name)).first() ?: return null + return Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), CLAN_TYPE) + } + + fun saveClan(clan: Clan) { + val document = Document.parse(Serializers.gson.toJson(clan, CLAN_TYPE)) + clansCollection.replaceOne(Document("id", clan.id.toString()), document, ReplaceOptions().upsert(true)) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/result/CreateError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/result/CreateError.kt new file mode 100644 index 0000000..b355274 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/clan/result/CreateError.kt @@ -0,0 +1,9 @@ +package com.minexd.api.module.clan.result + +enum class CreateError { + + NAME_INVALID, + NAME_TAKEN, + ALREADY_IN_CLAN, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/DiscordHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/DiscordHandler.kt new file mode 100644 index 0000000..b05b159 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/DiscordHandler.kt @@ -0,0 +1,32 @@ +package com.minexd.api.module.discord.main + +import com.minexd.api.API.config +import com.minexd.api.module.discord.main.listener.JoinEventListener +import com.minexd.api.module.discord.main.listener.SyncChannelListener +import com.minexd.api.module.profile.Profile +import net.dv8tion.jda.api.JDA +import net.dv8tion.jda.api.JDABuilder +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.requests.GatewayIntent + +object DiscordHandler +{ + lateinit var jda: JDA + + fun init() { + jda = JDABuilder.createDefault(config.discordMainToken).enableIntents( + GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_VOICE_STATES).build() + + jda.addEventListener(SyncChannelListener) + jda.addEventListener(JoinEventListener) + } + + fun getGuild() : Guild { + return jda.getGuildById(config.discordMainID)!! + } + + fun getMember(profile: Profile) : Member? { + return getGuild().getMemberById(profile.syncedAccount!!) + } +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/listener/JoinEventListener.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/listener/JoinEventListener.kt new file mode 100644 index 0000000..a60e789 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/listener/JoinEventListener.kt @@ -0,0 +1,23 @@ +package com.minexd.api.module.discord.main.listener + +import com.minexd.api.API.config +import com.minexd.api.APIConfig +import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter + +object JoinEventListener : ListenerAdapter() +{ + + override fun onGuildMemberJoin(event: GuildMemberJoinEvent) { + val guild = event.guild + val channels = guild.getTextChannelsByName("welcome", true) + if (channels.isEmpty()) return + val member = event.member + val serverName = config.serverName + guild.addRoleToMember(member, guild.getRolesByName("Member", true)[0]).queue() + val welcomeChannel = channels[0] + val formattedString = "Welcome to the **${config.serverName} Discord**, " + member.asMention + "!" + welcomeChannel.sendMessage(formattedString).queue() + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/listener/SyncChannelListener.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/listener/SyncChannelListener.kt new file mode 100644 index 0000000..5eea85f --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/main/listener/SyncChannelListener.kt @@ -0,0 +1,90 @@ +package com.minexd.api.module.discord.main.listener + +import com.minexd.api.API +import com.minexd.api.API.config +import com.minexd.api.module.discord.main.DiscordHandler +import com.minexd.api.module.profile.ProfileRepository +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.Role +import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter +import net.evilblock.pidgin.message.Message +import java.util.concurrent.TimeUnit + +object SyncChannelListener : ListenerAdapter() +{ + + override fun onGuildMessageReceived(event: GuildMessageReceivedEvent) { + val channel = event.channel + + if (!channel.name.equals("sync", ignoreCase = true)) return + if (!event.message.contentRaw.startsWith("!sync") && !event.message.author.isBot) { + event.message.delete().queueAfter(2, TimeUnit.SECONDS) + } + + val code = event.message.contentRaw.split(" ")[1] + + val profile = ProfileRepository.findProfileBySyncCode(code)!! + + if (profile.synced || profile.syncedAccount != null) { + event.channel.sendMessage("Your account is already synced").queue() + return + } + + if (profile.syncCode != code) { + event.channel.sendMessage("Invalid Code!").queue() + return + } + + if (System.currentTimeMillis() >= profile.syncCodeExpiry!!) { + event.channel.sendMessage("Expired Code!").queue() + return + } + + profile.syncCode = null + profile.syncCodeExpiry = null + profile.syncedAccount = event.author.id + + ProfileRepository.saveProfile(profile) + + var member: Member = event.member!! + var linkedRole: Role = event.guild.getRolesByName("linked", true)[0] + + //LINKED ROLE + event.guild.addRoleToMember(member, linkedRole).queue() + + if (!profile.getBestDisplayRank().default) { + if (profile.getBestDisplayRank().id.contains(config.rank1, ignoreCase = true)) { + var rank1: Role? = event.guild.getRolesByName(config.rank1, true)[0] + event.guild.addRoleToMember(member, rank1!!).queue() + } else if (profile.getBestDisplayRank().id.contains(config.rank2, ignoreCase = true)) { + var rank2: Role? = event.guild.getRolesByName(config.rank2, true)[0] + event.guild.addRoleToMember(member, rank2!!).queue() + } else if (profile.getBestDisplayRank().id.contains(config.rank3, ignoreCase = true)) { + var rank3: Role? = event.guild.getRolesByName(config.rank3, true)[0] + event.guild.addRoleToMember(member, rank3!!).queue() + } else if (profile.getBestDisplayRank().id.contains(config.rank4, true)) { + var rank4: Role? = event.guild.getRolesByName(config.rank4, true)[0] + event.guild.addRoleToMember(member, rank4!!).queue() + } else if (profile.getBestDisplayRank().id.contains(config.rank5, ignoreCase = true)) { + var rank5: Role? = event.guild.getRolesByName(config.rank5, true)[0] + event.guild.addRoleToMember(member, rank5!!).queue() + } else if (profile.getBestDisplayRank().id.contains(config.rank6, ignoreCase = true)) { + if (!config.rank6.isEmpty()) { + var rank6: Role? = event.guild.getRolesByName(config.rank6, true)[0] + event.guild.addRoleToMember(member, rank6!!).queue() + } + } + } + + API.pidgin.sendMessage( + Message( + id = "AccountSynced", + data = mapOf( + "PlayerUUID" to profile.uuid.toString() + ) + ) + ) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/StaffHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/StaffHandler.kt new file mode 100644 index 0000000..1fddcf0 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/StaffHandler.kt @@ -0,0 +1,33 @@ +package com.minexd.api.module.discord.staff + +import com.minexd.api.API.config +import com.minexd.api.module.discord.main.listener.SyncChannelListener +import com.minexd.api.module.discord.staff.listener.StaffJoinEventListener +import com.minexd.api.module.discord.staff.listener.StaffLeaveEventListener +import com.minexd.api.module.profile.Profile +import net.dv8tion.jda.api.JDA +import net.dv8tion.jda.api.JDABuilder +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.requests.GatewayIntent + +object StaffHandler +{ + lateinit var jda: JDA + + fun init() { + jda = JDABuilder.createDefault(config.discordStaffToken).enableIntents( + GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_VOICE_STATES).build() + + jda.addEventListener(StaffJoinEventListener) + jda.addEventListener(StaffLeaveEventListener) + } + + fun getGuild() : Guild { + return jda.getGuildById(config.discordStaffID)!! + } + + fun getMember(profile: Profile) : Member? { + return getGuild().getMemberById(profile.syncedAccount!!) + } +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/listener/StaffJoinEventListener.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/listener/StaffJoinEventListener.kt new file mode 100644 index 0000000..33221e9 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/listener/StaffJoinEventListener.kt @@ -0,0 +1,20 @@ +package com.minexd.api.module.discord.staff.listener + +import com.minexd.api.API.config +import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter + +object StaffJoinEventListener : ListenerAdapter() +{ + + override fun onGuildMemberJoin(event: GuildMemberJoinEvent) { + val guild = event.guild + val channels = guild.getTextChannelsByName("welcome", true) + if (channels.isEmpty()) return + val member = event.member + val welcomeChannel = channels[0] + val formattedString = "Welcome to the **${config.serverName} Staff Team**, " + member.asMention + "!" + welcomeChannel.sendMessage(formattedString).queue() + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/listener/StaffLeaveEventListener.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/listener/StaffLeaveEventListener.kt new file mode 100644 index 0000000..062b572 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/discord/staff/listener/StaffLeaveEventListener.kt @@ -0,0 +1,20 @@ +package com.minexd.api.module.discord.staff.listener + +import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent +import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter + +object StaffLeaveEventListener : ListenerAdapter() +{ + + override fun onGuildMemberRemove(event: GuildMemberRemoveEvent) { + val guild = event.guild + val channels = guild.getTextChannelsByName("welcome", true) + if (channels.isEmpty()) return + val member = event.member + val welcomeChannel = channels[0] + val formattedString = "Goodbye, " + member!!.asMention + "!" + welcomeChannel.sendMessage(formattedString).queue() + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendAPI.kt new file mode 100644 index 0000000..ae14ab3 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendAPI.kt @@ -0,0 +1,233 @@ +package com.minexd.api.module.friend + +import com.minexd.api.API +import com.minexd.api.module.friend.result.Friend +import com.minexd.api.module.friend.result.FriendRemoveResult +import com.minexd.api.module.friend.result.FriendRequestResult +import com.minexd.api.module.friend.structure.Friendship +import com.minexd.api.module.friend.structure.FriendshipType +import com.minexd.api.module.messaging.MessagingHandler +import com.minexd.api.module.profile.ProfileHandler +import com.minexd.api.module.profile.ProfileRepository +import com.minexd.api.module.profile.setting.impl.FriendRequestsSetting +import com.minexd.api.module.profile.setting.impl.PresenceVisibilitySetting +import com.minexd.api.presence.PlayerPresenceHandler +import net.evilblock.cubed.serializers.Serializers +import net.evilblock.pidgin.message.Message +import spark.Route +import spark.kotlin.halt +import java.util.* + +object FriendAPI { + + val listFriendships = Route { request, response -> + val uuid = UUID.fromString(request.queryParams("player_id")) + + val friendships = FriendRepository.findFriendships(uuid) + .mapNotNull { friendship -> + val other = friendship.getOtherPlayer(uuid) ?: return@mapNotNull null + + val friend = Friend( + other, + API.uuidCache.name(other), + friendship.type, + friendship.score, + friendship.player1, + friendship.createdAt, + friendship.favorited.contains(uuid) + ) + + val friendProfile = ProfileHandler.getOrFetchProfile(other) + + val loadPresence = when (friendProfile.getSetting(PresenceVisibilitySetting).getValue()) { + PresenceVisibilitySetting.OptionValue.EVERYONE -> { + true + } + PresenceVisibilitySetting.OptionValue.FRIENDS -> { + friendship.type == FriendshipType.FRIENDS + } + PresenceVisibilitySetting.OptionValue.NOBODY -> { + false + } + } + + if (loadPresence) { + friend.presence = PlayerPresenceHandler.getOrFetchPresence(other) + } + + friend + } + + return@Route Serializers.gson.toJson(friendships.sortedWith(Friend.DEFAULT_SORT).reversed()) + } + + val getIncoming: Route = Route { request, response -> + val uuid = UUID.fromString(request.queryParams("player_id")) + + return@Route FriendRepository.findIncomingFriendships(uuid) + .mapNotNull { friendship -> + val other = friendship.getOtherPlayer(uuid) ?: return@mapNotNull null + Friend( + other, + API.uuidCache.name(other), + friendship.type, + friendship.score, + friendship.player1, + friendship.createdAt, + friendship.favorited.contains(uuid) + ) + } + } + + val getOutgoing: Route = Route { request, response -> + val uuid = UUID.fromString(request.queryParams("player_id")) + + return@Route FriendRepository.findOutgoingFriendships(uuid) + .mapNotNull { friendship -> + val other = friendship.getOtherPlayer(uuid) ?: return@mapNotNull null + Friend( + other, + API.uuidCache.name(other), + friendship.type, + friendship.score, + friendship.player1, + friendship.createdAt, + friendship.favorited.contains(uuid) + ) + } + } + + val show: Route = Route { request, response -> + val uuid = UUID.fromString(request.queryParams("player_id")) + val friendId = UUID.fromString(request.queryParams("friend_id")) + return@Route FriendRepository.findFriendship(uuid, friendId) + ?: return@Route halt(400, "You are not friends with ${API.uuidCache.name(friendId)}") + } + + val create: Route = Route { request, response -> + val senderUUID = UUID.fromString(request.queryParams("player_id")) + val friendUUID = UUID.fromString(request.queryParams("friend_id")) + + if (senderUUID == friendUUID) { + return@Route halt(400, FriendRequestResult.CANNOT_REQUEST_SELF.name) + } + + val friendship = FriendRepository.findFriendship(senderUUID, friendUUID) + if (friendship != null) { + if (friendship.type == FriendshipType.FRIENDS) { + return@Route halt(400, FriendRequestResult.ALREADY_FRIENDS.name) + } + + if (friendship.player1 == senderUUID) { + return@Route halt(400, FriendRequestResult.ALREADY_REQUESTED.name) + } + + if (FriendRepository.findFriends(senderUUID).size >= FriendHandler.MAX_FRIENDS_LIST_SIZE) { + return@Route halt(400, FriendRequestResult.FRIENDS_LIST_FULL.name) + } + + if (FriendRepository.findFriends(friendUUID).size >= FriendHandler.MAX_FRIENDS_LIST_SIZE) { + return@Route halt(400, FriendRequestResult.TARGET_FRIENDS_LIST_FULL.name) + } + + if (MessagingHandler.isIgnored(friendUUID, senderUUID)) { + return@Route halt(400, FriendRequestResult.CANNOT_REQUEST.name) + } + + friendship.type = FriendshipType.FRIENDS + friendship.createdAt = System.currentTimeMillis() + FriendRepository.saveFriendship(friendship) + + val senderProfile = ProfileRepository.findOrCreateProfile(senderUUID) + val friendProfile = ProfileRepository.findOrCreateProfile(friendUUID) + + API.pidgin.sendMessage(Message( + id = "FriendAccept", + data = mapOf( + "Sender" to senderUUID.toString(), + "SenderName" to senderProfile.getColoredUsername(), + "Receiver" to friendProfile.uuid, + "ReceiverName" to friendProfile.getColoredUsername() + ) + )) + + response.status(201) + + return@Route Serializers.gson.toJsonTree(friendship).also { json -> + val obj = json.asJsonObject + obj.addProperty("action_taken", FriendRequestResult.REQUEST_ACCEPTED.name) + } + } else { + val onlyAccept = request.queryParams("only_accept")?.toBoolean() ?: false + if (onlyAccept) { + return@Route halt(400, FriendRequestResult.NOT_REQUESTED.name) + } + + val newFriendship = Friendship(player1 = senderUUID, player2 = friendUUID) + FriendRepository.saveFriendship(newFriendship) + + val senderProfile = ProfileRepository.findOrCreateProfile(senderUUID) + val friendProfile = ProfileRepository.findOrCreateProfile(friendUUID) + + if (friendProfile.getSetting(FriendRequestsSetting).getValue() == FriendRequestsSetting.OptionValue.DENY) { + return@Route halt(400, FriendRequestResult.CANNOT_REQUEST.name) + } + + if (MessagingHandler.isIgnored(friendUUID, senderUUID)) { + return@Route halt(400, FriendRequestResult.CANNOT_REQUEST.name) + } + + API.pidgin.sendMessage(Message( + id = "FriendRequest", + data = mapOf( + "Sender" to senderUUID.toString(), + "SenderName" to senderProfile.getColoredUsername(), + "Receiver" to friendUUID, + "ReceiverName" to friendProfile.getColoredUsername() + ) + )) + + response.status(201) + + return@Route Serializers.gson.toJsonTree(newFriendship).also { json -> + val obj = json.asJsonObject + obj.addProperty("action_taken", FriendRequestResult.REQUEST_CREATED.name) + } + } + } + + val destroy: Route = Route { request, response -> + val senderUUID = UUID.fromString(request.queryParams("player_id")) + val friendUUID = UUID.fromString(request.queryParams("friend_id")) + + val friendship = FriendRepository.findFriendship(senderUUID, friendUUID) + ?: return@Route halt(403, "You are not friends with ${API.uuidCache.name(friendUUID)}") + + FriendRepository.deleteFriendship(friendship) + + API.pidgin.sendMessage(Message( + id = "FriendRemove", + data = mapOf( + "Sender" to senderUUID.toString(), + "Receiver" to friendUUID.toString() + ) + )) + + val result = if (friendship.type == FriendshipType.FRIENDS) { + FriendRemoveResult.FRIEND_REMOVED + } else { + if (senderUUID == friendship.player1) { + FriendRemoveResult.REQUEST_CANCELLED + } else { + FriendRemoveResult.REQUEST_REJECTED + } + }.toResponse() + + return@Route Serializers.gson.toJsonTree(result) + } + + val update: Route = Route { request, response -> + + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendHandler.kt new file mode 100644 index 0000000..1dcdd08 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendHandler.kt @@ -0,0 +1,33 @@ +package com.minexd.api.module.friend + +import com.minexd.api.module.friend.result.Friend +import com.minexd.api.service.ServiceRegistry +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit + +object FriendHandler : Runnable { + + const val MAX_FRIENDS_LIST_SIZE: Int = 500 + + private val cached: MutableMap, Long>> = ConcurrentHashMap() + + fun cache(uuid: UUID, friends: List) { + cached[uuid] = Pair(friends, System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1L)) + } + + fun load() { + ServiceRegistry.register(this, 20L * 3L, 20L * 3L) + } + + override fun run() { + val expired = cached.filter { + System.currentTimeMillis() >= it.value.second + }.keys + + for (uuid in expired) { + cached.remove(uuid) + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendRepository.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendRepository.kt new file mode 100644 index 0000000..94f5dbe --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/FriendRepository.kt @@ -0,0 +1,152 @@ +package com.minexd.api.module.friend + +import com.google.gson.reflect.TypeToken +import com.minexd.api.API +import com.minexd.api.Repository +import com.minexd.api.module.friend.structure.Friendship +import com.minexd.api.module.friend.structure.FriendshipType +import com.mongodb.BasicDBObject +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.client.model.UpdateOptions +import net.evilblock.cubed.serializers.Serializers +import org.bson.Document +import org.bson.types.BasicBSONList +import java.lang.reflect.Type +import java.util.* + +object FriendRepository : Repository { + + private val RELATION_TYPE: Type = object : TypeToken() {}.type + + private lateinit var friendshipsCol: MongoCollection + + override fun initialize() { + friendshipsCol = API.mongoDatabase.getCollection("friends_friendships") + friendshipsCol.createIndex(BasicDBObject("friendshipId", 1)) + friendshipsCol.createIndex(BasicDBObject("player1", 1)) + friendshipsCol.createIndex(BasicDBObject("player2", 1)) + } + + /** + * Attempts to retrieve a [Friendship] from the given [id] (relation ID). + */ + fun findFriendship(id: UUID): Friendship? { + val document = friendshipsCol.find(Document("friendshipId", id.toString())).first() ?: return null + return Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), RELATION_TYPE) + } + + /** + * Attempts to retrieve a [Friendship] between the 2 given players, [player1] and [player2]. + */ + fun findFriendship(player1: UUID, player2: UUID): Friendship? { + val query = Document("\$or", BasicBSONList().also { orList -> + orList.add(Document("\$and", BasicBSONList().also { andList -> + andList.add(Document("player1", player1.toString())) + andList.add(Document("player2", player2.toString())) + })) + + orList.add(Document("\$and", BasicBSONList().also { andList -> + andList.add(Document("player1", player2.toString())) + andList.add(Document("player2", player1.toString())) + })) + }) + + val document = friendshipsCol.find(query).first() ?: return null + return Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), RELATION_TYPE) + } + + /** + * Retrieves a list of [Friendship]s for the given [player]. + */ + fun findFriendships(player: UUID): List { + val query = Document("\$or", listOf( + Document("player1", player.toString()), + Document("player2", player.toString()) + )) + + return arrayListOf().also { relations -> + val documents = friendshipsCol.find(query) + for (document in documents) { + relations.add(Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), RELATION_TYPE)) + } + } + } + + /** + * Retrieves a list of [Friendship]s for the given [player]. + */ + fun findFriends(player: UUID): List { + val query = Document("\$and", BasicBSONList().also { andList -> + andList.add(Document("\$or", BasicBSONList().also { orList -> + orList.add(Document("player1", player.toString())) + orList.add(Document("player2", player.toString())) + })) + + andList.add(Document("type", FriendshipType.FRIENDS.name)) + }) + + return arrayListOf().also { relations -> + val documents = friendshipsCol.find(query) + for (document in documents) { + relations.add(Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), RELATION_TYPE)) + } + } + } + + /** + * Retrieves a list of incoming [Friendship]s for the given [player]. + */ + fun findIncomingFriendships(player: UUID): List { + val query = Document("\$and", BasicBSONList().also { andList -> + andList.add(Document("player2", player.toString())) + andList.add(Document("type", FriendshipType.PENDING.name)) + }) + + return arrayListOf().also { relations -> + val documents = friendshipsCol.find(query) + for (document in documents) { + relations.add(Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), RELATION_TYPE)) + } + } + } + + /** + * Retrieves a list of outgoing [Friendship]s for the given [player]. + */ + fun findOutgoingFriendships(player: UUID): List { + val query = Document("\$and", BasicBSONList().also { andList -> + andList.add(Document("player1", player.toString())) + andList.add(Document("type", FriendshipType.PENDING.name)) + }) + + return arrayListOf().also { relations -> + val documents = friendshipsCol.find(Document(query)) + for (document in documents) { + relations.add(Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), RELATION_TYPE)) + } + } + } + + /** + * Saves the given [friendship]. + */ + fun saveFriendship(friendship: Friendship) { + friendshipsCol.replaceOne(Document("friendshipId", friendship.friendshipId.toString()), Document.parse(Serializers.gson.toJson(friendship, RELATION_TYPE)), ReplaceOptions().upsert(true)) + } + + /** + * Deletes the given [friendship]. + */ + fun deleteFriendship(friendship: Friendship) { + friendshipsCol.deleteOne(Document("friendshipId", friendship.friendshipId.toString())) + } + + /** + * Updates the "Friend Score" for the given [friendship]. + */ + fun modifyFriendScore(friendship: Friendship) { + friendshipsCol.updateOne(Document("friendshipId", friendship.friendshipId.toString()), Document("\$inc", Document("score", friendship.score)), UpdateOptions().upsert(true)) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/Friend.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/Friend.kt new file mode 100644 index 0000000..aa4d00e --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/Friend.kt @@ -0,0 +1,114 @@ +package com.minexd.api.module.friend.result + +import com.minexd.api.module.friend.structure.FriendshipType +import com.minexd.api.presence.PlayerPresence +import com.minexd.api.util.NaturalOrderComparator +import java.util.* + +/** + * Represents a friend from a friendship. + */ +data class Friend( + val uuid: UUID, + var username: String, + var type: FriendshipType, + var score: Int, + val createdBy: UUID, + val createdAt: Long, + var favorited: Boolean +) { + + var presence: PlayerPresence? = null + + override fun equals(other: Any?): Boolean { + return if (other is Friend) { + uuid == other.uuid + } else { + false + } + } + + override fun hashCode(): Int { + return uuid.hashCode() + } + + companion object { + @JvmStatic + val DEFAULT_SORT: Comparator = Comparator { o1: Friend, o2: Friend -> + if (o1.favorited && !o2.favorited) { + return@Comparator 1 + } else if (!o1.favorited && o2.favorited) { + return@Comparator -1 + } + + if (o1.type == FriendshipType.FRIENDS && o2.type == FriendshipType.FRIENDS) { + if (o1.presence != null && o2.presence != null) { + if (o1.presence!!.isOnline() && o2.presence!!.isOnline()) { + return@Comparator o1.score - o2.score + } else if (o1.presence!!.isOnline()) { + return@Comparator 1 + } else if (o2.presence!!.isOnline()) { + return@Comparator -1 + } else { + return@Comparator (o1.presence!!.heartbeat - o2.presence!!.heartbeat).toInt() + } + } else if (o1.presence != null && o2.presence == null) { + return@Comparator 1 + } else if (o1.presence == null && o2.presence != null) { + return@Comparator -1 + } else if ((o1.presence != null && o1.presence!!.isOnline()) && (o2.presence == null || !o2.presence!!.isOnline())) { + return@Comparator -1 + } else if ((o1.presence == null || !o1.presence!!.isOnline()) && (o2.presence != null && o2.presence!!.isOnline())) { + return@Comparator 1 + } else { + return@Comparator o1.score - o2.score + } + } else if (o1.type == FriendshipType.FRIENDS && o2.type != FriendshipType.FRIENDS) { + return@Comparator 1 + } else if (o1.type != FriendshipType.FRIENDS && o2.type == FriendshipType.FRIENDS) { + return@Comparator -1 + } + + return@Comparator (o1.createdAt - o2.createdAt).toInt() + } + + @JvmStatic + val ALPHABETICAL_SORT: Comparator = Comparator { o1, o2 -> + return@Comparator if (o1.presence != null && o2.presence != null) { + if (o1.presence?.isOnline() == true && o2.presence?.isOnline() == true) { + NaturalOrderComparator.compare(o1.username.toLowerCase(), o2.username.toLowerCase()) + } else if (o1.presence?.isOnline() == true) { + 1 + } else if (o2.presence?.isOnline() == true) { + -1 + } else { + NaturalOrderComparator.compare(o1.username.toLowerCase(), o2.username.toLowerCase()) + } + } else { + NaturalOrderComparator.compare(o1.username.toLowerCase(), o2.username.toLowerCase()) + } + } + + @JvmStatic + val LAST_ONLINE_SORT: Comparator = Comparator { o1, o2 -> + return@Comparator if (o1.presence != null && o2.presence != null) { + if (o1.presence?.isOnline() == true && o2.presence?.isOnline() == true) { + NaturalOrderComparator.compare(o1.username, o2.username) + } else if (o1.presence?.isOnline() == true) { + 1 + } else if (o2.presence?.isOnline() == true) { + -1 + } else { + (o1.presence!!.heartbeat - o2.presence!!.heartbeat).toInt() + } + } else if (o1.presence != null) { + return@Comparator 1 + } else if (o2.presence != null) { + return@Comparator -1 + } else { + return@Comparator 0 + } + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/FriendRemoveResult.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/FriendRemoveResult.kt new file mode 100644 index 0000000..b0595f1 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/FriendRemoveResult.kt @@ -0,0 +1,15 @@ +package com.minexd.api.module.friend.result + +enum class FriendRemoveResult { + + FRIEND_REMOVED, + REQUEST_CANCELLED, + REQUEST_REJECTED; + + fun toResponse(): Response { + return Response(this) + } + + data class Response(val result: FriendRemoveResult) + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/FriendRequestResult.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/FriendRequestResult.kt new file mode 100644 index 0000000..4e26b23 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/result/FriendRequestResult.kt @@ -0,0 +1,15 @@ +package com.minexd.api.module.friend.result + +enum class FriendRequestResult { + + ALREADY_FRIENDS, + ALREADY_REQUESTED, + REQUEST_CREATED, + REQUEST_ACCEPTED, + NOT_REQUESTED, + CANNOT_REQUEST_SELF, + CANNOT_REQUEST, + FRIENDS_LIST_FULL, + TARGET_FRIENDS_LIST_FULL, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/structure/Friendship.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/structure/Friendship.kt new file mode 100644 index 0000000..b9b925f --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/structure/Friendship.kt @@ -0,0 +1,55 @@ +package com.minexd.api.module.friend.structure + +import java.util.* + +/** + * The model for a friendship between 2 players. Specially crafted + * to meet the needs of both OOP and efficient MongoDB queries, + * which is important because we use GSON reflection serializer + * to convert this object into a BSON document. + * + * The core assumption of this model is: + * player1 is ALWAYS the player to initiate the friendship ("friend request") + */ +data class Friendship( + val player1: UUID, + val player2: UUID +) { + + /** + * A unique ID for this friendship. + */ + val friendshipId: UUID = UUID.randomUUID() + + /** + * When this friendship was created. + */ + var createdAt: Long = System.currentTimeMillis() + + /** + * The type of friendship between the two participants. + */ + var type: FriendshipType = FriendshipType.PENDING + + /** + * This friendship's score, otherwise known as "Friend Score". + */ + var score: Int = 0 + + /** + * Who involved in this friendship has favorited this friendship on their "friend's list". + */ + val favorited: MutableList = arrayListOf() + + /** + * If the given [source] is involved in this friendship, the counterpart player UUID is returned, otherwise null. + */ + fun getOtherPlayer(source: UUID): UUID? { + return when (source) { + player1 -> player2 + player2 -> player1 + else -> null + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/structure/FriendshipType.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/structure/FriendshipType.kt new file mode 100644 index 0000000..d93c330 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/friend/structure/FriendshipType.kt @@ -0,0 +1,8 @@ +package com.minexd.api.module.friend.structure + +enum class FriendshipType { + + PENDING, + FRIENDS + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/Leaderboard.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/Leaderboard.kt new file mode 100644 index 0000000..51a3010 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/Leaderboard.kt @@ -0,0 +1,24 @@ +package com.minexd.api.module.leaderboard + +abstract class Leaderboard(val id: String, val name: String) { + + companion object { + internal const val CACHED_ENTRIES_SIZE = 10 + } + + internal var entries: List> = arrayListOf() + + abstract fun fetchEntries(): List> + + fun refresh() { + val newEntries = fetchEntries() + var position = 1 + + for (entry in newEntries) { + entry.position = position++ + } + + this.entries = newEntries + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardEntry.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardEntry.kt new file mode 100644 index 0000000..47ef9a7 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardEntry.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020. Joel Evans + * + * Use and or redistribution of compiled JAR file and or source code is permitted only if given + * explicit permission from original author: Joel Evans + */ + +package com.minexd.api.module.leaderboard + +import java.util.* + +data class LeaderboardEntry( + var position: Int, + val uuid: UUID, + val displayName: String, + val value: T +) diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardsAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardsAPI.kt new file mode 100644 index 0000000..bb0e1a7 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardsAPI.kt @@ -0,0 +1,12 @@ +package com.minexd.api.module.leaderboard + +import com.minexd.api.module.server.util.GameType +import spark.Route + +object LeaderboardsAPI { + + val byGame: Route = Route { request, response -> + return@Route LeaderboardsHandler.getLeaderboardsByGame(GameType.valueOf(request.queryParams("game"))) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardsHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardsHandler.kt new file mode 100644 index 0000000..8e2119c --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/LeaderboardsHandler.kt @@ -0,0 +1,37 @@ +package com.minexd.api.module.leaderboard + +import com.minexd.api.API +import com.minexd.api.module.leaderboard.prison.* +import com.minexd.api.module.server.util.GameType + +object LeaderboardsHandler { + + private val leaderboards: Map> = mapOf( + GameType.PRISON to listOf( + PrisonBlocksMinedLeaderboard, + PrisonKillsLeaderboard, + PrisonPlayTimeLeaderboard, + PrisonRankLeaderboard, + PrisonTokensLeaderboard + ) + ) + + fun load() { + for ((gameType, leaderboards) in leaderboards) { + API.logger.info("Updating $gameType leaderboards") + + for (leaderboard in leaderboards) { + try { + leaderboard.refresh() + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + + fun getLeaderboardsByGame(gameType: GameType): List { + return leaderboards[gameType] ?: emptyList() + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonBlocksMinedLeaderboard.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonBlocksMinedLeaderboard.kt new file mode 100644 index 0000000..164d0a9 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonBlocksMinedLeaderboard.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020. Joel Evans + * + * Use and or redistribution of compiled JAR file and or source code is permitted only if given + * explicit permission from original author: Joel Evans + */ + +package com.minexd.api.module.leaderboard.prison + +import com.minexd.api.API +import com.minexd.api.module.leaderboard.Leaderboard +import com.minexd.api.module.leaderboard.LeaderboardEntry +import com.minexd.api.module.profile.statistic.StatisticsRepository +import com.minexd.api.module.staff.StaffHandler +import org.bson.Document +import java.util.* + +object PrisonBlocksMinedLeaderboard : Leaderboard("top-blocks-mined", "Top Blocks Mined") { + + override fun fetchEntries(): List> { + val entries = arrayListOf>() + + for (document in StatisticsRepository.prisonProfilesCollection.find()) { + val uuid = UUID.fromString(document.getString("uuid")) + + if (StaffHandler.staffMembers.contains(uuid)) { + continue + } + + val displayName = API.uuidCache.name(uuid) + val blocksMined = (document["statistics"] as Document).getInteger("blocksMined") + + entries.add(LeaderboardEntry(0, uuid, displayName, blocksMined)) + } + + return entries.sortedByDescending { it.value }.take(CACHED_ENTRIES_SIZE) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonKillsLeaderboard.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonKillsLeaderboard.kt new file mode 100644 index 0000000..06dfd86 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonKillsLeaderboard.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020. Joel Evans + * + * Use and or redistribution of compiled JAR file and or source code is permitted only if given + * explicit permission from original author: Joel Evans + */ + +package com.minexd.api.module.leaderboard.prison + +import com.minexd.api.API +import com.minexd.api.module.leaderboard.Leaderboard +import com.minexd.api.module.leaderboard.LeaderboardEntry +import com.minexd.api.module.profile.statistic.StatisticsRepository +import com.minexd.api.module.staff.StaffHandler +import org.bson.Document +import java.util.* + +object PrisonKillsLeaderboard : Leaderboard("top-kills", "Top Kills") { + + override fun fetchEntries(): List> { + val entries = arrayListOf>() + + for (document in StatisticsRepository.prisonProfilesCollection.find()) { + val uuid = UUID.fromString(document.getString("uuid")) + + if (StaffHandler.staffMembers.contains(uuid)) { + continue + } + + val displayName = API.uuidCache.name(uuid) + + val statistics = document["statistics"] as Document + val kills = statistics.getInteger("kills") ?: statistics.getInteger("currentPrestige") + + entries.add(LeaderboardEntry(0, uuid, displayName, kills)) + } + + return entries.sortedByDescending { it.value }.take(CACHED_ENTRIES_SIZE) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonPlayTimeLeaderboard.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonPlayTimeLeaderboard.kt new file mode 100644 index 0000000..8b71c36 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonPlayTimeLeaderboard.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020. Joel Evans + * + * Use and or redistribution of compiled JAR file and or source code is permitted only if given + * explicit permission from original author: Joel Evans + */ + +package com.minexd.api.module.leaderboard.prison + +import com.minexd.api.API +import com.minexd.api.module.leaderboard.Leaderboard +import com.minexd.api.module.leaderboard.LeaderboardEntry +import com.minexd.api.module.profile.statistic.StatisticsRepository +import com.minexd.api.module.staff.StaffHandler +import org.bson.Document +import java.util.* + +object PrisonPlayTimeLeaderboard : Leaderboard("top-play-time", "Top Play Time") { + + override fun fetchEntries(): List> { + val entries = arrayListOf>() + + for (document in StatisticsRepository.prisonProfilesCollection.find()) { + val uuid = UUID.fromString(document.getString("uuid")) + + if (StaffHandler.staffMembers.contains(uuid)) { + continue + } + + val displayName = API.uuidCache.name(uuid) + val statistics = document["statistics"] ?: continue + + val playTime = (statistics as Document)["playTime"] + if (playTime is Int) { + entries.add(LeaderboardEntry(0, uuid, displayName, playTime.toLong())) + } else { + entries.add(LeaderboardEntry(0, uuid, displayName, playTime as Long)) + } + } + + return entries.sortedByDescending { it.value }.take(CACHED_ENTRIES_SIZE) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonRankLeaderboard.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonRankLeaderboard.kt new file mode 100644 index 0000000..29552dd --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonRankLeaderboard.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020. Joel Evans + * + * Use and or redistribution of compiled JAR file and or source code is permitted only if given + * explicit permission from original author: Joel Evans + */ + +package com.minexd.api.module.leaderboard.prison + +import com.minexd.api.API +import com.minexd.api.module.leaderboard.Leaderboard +import com.minexd.api.module.leaderboard.LeaderboardEntry +import com.minexd.api.module.profile.statistic.StatisticsRepository +import com.minexd.api.module.staff.StaffHandler +import java.util.* + +object PrisonRankLeaderboard : Leaderboard("top-rank", "Top Rank") { + + override fun fetchEntries(): List> { + val entries = arrayListOf>() + + for (document in StatisticsRepository.prisonProfilesCollection.find()) { + val uuid = UUID.fromString(document.getString("uuid")) + + if (StaffHandler.staffMembers.contains(uuid)) { + continue + } + + val displayName = API.uuidCache.name(uuid) + val rank = document.getInteger("rank") + + entries.add(LeaderboardEntry(0, uuid, displayName, rank)) + } + + return entries.sortedByDescending { it.value }.take(CACHED_ENTRIES_SIZE) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonTokensLeaderboard.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonTokensLeaderboard.kt new file mode 100644 index 0000000..71a841d --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/leaderboard/prison/PrisonTokensLeaderboard.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020. Joel Evans + * + * Use and or redistribution of compiled JAR file and or source code is permitted only if given + * explicit permission from original author: Joel Evans + */ + +package com.minexd.api.module.leaderboard.prison + +import com.minexd.api.API +import com.minexd.api.module.leaderboard.Leaderboard +import com.minexd.api.module.leaderboard.LeaderboardEntry +import com.minexd.api.module.profile.statistic.StatisticsRepository +import com.minexd.api.module.staff.StaffHandler +import java.util.* + +object PrisonTokensLeaderboard : Leaderboard("top-tokens", "Top Tokens") { + + override fun fetchEntries(): List> { + val entries = arrayListOf>() + + for (document in StatisticsRepository.prisonProfilesCollection.find()) { + val uuid = UUID.fromString(document.getString("uuid")) + + if (StaffHandler.staffMembers.contains(uuid)) { + continue + } + + val displayName = API.uuidCache.name(uuid) + + val balance = document["tokenBalance"] ?: document["tokensBalance"] + if (balance is Int) { + entries.add(LeaderboardEntry(0, uuid, displayName, balance.toLong())) + } else { + entries.add(LeaderboardEntry(0, uuid, displayName, balance as Long)) + } + } + + return entries.sortedByDescending { it.value }.take(CACHED_ENTRIES_SIZE) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessageError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessageError.kt new file mode 100644 index 0000000..1904ffd --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessageError.kt @@ -0,0 +1,12 @@ +package com.minexd.api.module.messaging + +enum class MessageError { + + SELF_MESSAGES_DISABLED, + PLAYER_OFFLINE, + PLAYER_IGNORED, + PLAYER_MESSAGES_FRIENDS_ONLY, + PLAYER_MESSAGES_DISABLED, + CANNOT_MESSAGE_PLAYER, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessagingAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessagingAPI.kt new file mode 100644 index 0000000..795fa2c --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessagingAPI.kt @@ -0,0 +1,89 @@ +package com.minexd.api.module.messaging + +import com.minexd.api.API +import com.minexd.api.module.friend.FriendRepository +import com.minexd.api.module.friend.structure.FriendshipType +import com.minexd.api.module.profile.ProfileHandler +import com.minexd.api.module.profile.setting.impl.PrivateMessagesSetting +import com.minexd.api.presence.PlayerPresenceHandler +import net.evilblock.pidgin.message.Message +import spark.Route +import spark.kotlin.halt +import java.util.* + +object MessagingAPI { + + val message: Route = Route { request, response -> + val senderUUID = UUID.fromString(request.queryParams("player_id")) + val friendUUID = UUID.fromString(request.queryParams("friend_id")) + + val senderPresence = PlayerPresenceHandler.getOrFetchPresence(senderUUID) + ?: return@Route halt(404) + + val friendPresence = PlayerPresenceHandler.getOrFetchPresence(friendUUID) + if (friendPresence == null || !friendPresence.isOnline()) { + return@Route halt(400, MessageError.PLAYER_OFFLINE.name) + } + + if (MessagingHandler.isIgnored(senderUUID, friendUUID)) { + return@Route halt(400, MessageError.PLAYER_IGNORED.name) + } + + if (MessagingHandler.isIgnored(friendUUID, senderUUID)) { + return@Route halt(400, MessageError.CANNOT_MESSAGE_PLAYER.name) + } + + val friendship = FriendRepository.findFriendship(senderUUID, friendUUID) + val senderProfile = ProfileHandler.getOrFetchProfile(senderUUID) + + when (senderProfile.getSetting(PrivateMessagesSetting).getValue()) { + PrivateMessagesSetting.OptionValue.NOBODY -> { + return@Route halt(400, MessageError.SELF_MESSAGES_DISABLED.name) + } + PrivateMessagesSetting.OptionValue.FRIENDS -> { + if (friendship == null || friendship.type == FriendshipType.PENDING) { + return@Route halt(400, MessageError.SELF_MESSAGES_DISABLED.name) + } + } + else -> { } + } + + val friendProfile = ProfileHandler.getOrFetchProfile(friendUUID) ?: return@Route halt(404) + when (friendProfile.getSetting(PrivateMessagesSetting).getValue()) { + PrivateMessagesSetting.OptionValue.NOBODY -> { + return@Route halt(400, MessageError.PLAYER_MESSAGES_DISABLED.name) + } + PrivateMessagesSetting.OptionValue.FRIENDS -> { + if (friendship == null || friendship.type != FriendshipType.FRIENDS) { + return@Route halt(400, MessageError.PLAYER_MESSAGES_FRIENDS_ONLY.name) + } + } + else -> { } + } + + MessagingHandler.setLastMessaged(senderUUID, friendUUID) + MessagingHandler.setLastMessaged(friendUUID, senderUUID) + + val rawMessage = request.body() + val message = rawMessage.substring(1, rawMessage.length - 1) + + API.pidgin.sendMessage( + Message( + id = "PrivateMessage", + data = mapOf( + "SenderUUID" to senderUUID.toString(), + "SenderName" to senderProfile.getColoredUsername(), + "SenderServer" to senderPresence.server, + "ReceiverUUID" to friendUUID.toString(), + "ReceiverName" to friendProfile.getColoredUsername(), + "ReceiverServer" to friendPresence.server, + "Message" to message + ) + ) + ) + + response.status(201) + return@Route response + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessagingHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessagingHandler.kt new file mode 100644 index 0000000..17708cd --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/messaging/MessagingHandler.kt @@ -0,0 +1,79 @@ +package com.minexd.api.module.messaging + +import com.minexd.api.API +import java.util.* + +object MessagingHandler { + + private const val REDIS_KEY = "Core:Messaging" + + /** + * Gets the last messaged player for a player. + * + * @param player the player + * + * @return the last player's uuid that [player] has messaged + */ + fun getLastMessaged(player: UUID): UUID? { + return API.redis.runRedisCommand { redis -> + redis.hget("$REDIS_KEY:$player", "LastMessaged") + }?.run { UUID.fromString(this) } + } + + /** + * Sets the last messaged player for a player. + */ + fun setLastMessaged(player: UUID, lastMessaged: UUID) { + API.redis.runRedisCommand { redis -> + redis.hset("$REDIS_KEY:$player", hashMapOf("LastMessaged" to "$lastMessaged")) + } + } + + /** + * Retrieves a player's ignore list. + */ + fun getIgnoreList(player: UUID): Set { + return API.redis.runRedisCommand { redis -> + if (redis.exists("$REDIS_KEY:IgnoreList:$player")) { + redis.smembers("$REDIS_KEY:IgnoreList:$player").map { UUID.fromString(it) }.toSet() + } else { + emptySet() + } + } + } + + /** + * Gets whether or not the [player] is ignored by the [target]. + */ + fun isIgnored(player: UUID, target: UUID): Boolean { + return getIgnoreList(player).contains(target) + } + + /** + * Adds a target to a player's ignore list. + */ + fun addToIgnoreList(player: UUID, target: UUID) { + API.redis.runRedisCommand { redis -> + redis.sadd("$REDIS_KEY:IgnoreList:$player", target.toString()) + } + } + + /** + * Removes a target from a player's ignore list. + */ + fun removeFromIgnoreList(player: UUID, target: UUID) { + API.redis.runRedisCommand { redis -> + redis.srem("$REDIS_KEY:IgnoreList:$player", target.toString()) + } + } + + /** + * Clears a player's ignore list. + */ + fun clearIgnoreList(player: UUID) { + API.redis.runRedisCommand { redis -> + redis.del("$REDIS_KEY:IgnoreList:$player") + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/Party.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/Party.kt new file mode 100644 index 0000000..6320a86 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/Party.kt @@ -0,0 +1,41 @@ +package com.minexd.api.module.party + +import com.minexd.api.module.party.structure.PartyInvite +import com.minexd.api.module.party.structure.PartyPrivacy +import java.util.* + +class Party( + val id: UUID = UUID.randomUUID(), + var leader: UUID +) { + + var privacy: PartyPrivacy = PartyPrivacy.EVERYONE + + internal val members: MutableSet = hashSetOf() + internal val invites: MutableMap = hashMapOf() + + fun hasInvite(player: UUID): Boolean { + if (invites.containsKey(player)) { + val invite = invites[player]!! + if (System.currentTimeMillis() - invite.createdAt <= 86_400_000L) { + return true + } else { + invites.remove(player) + } + } + return false + } + + fun getInvite(player: UUID): PartyInvite? { + return invites[player] + } + + fun addInvite(invite: PartyInvite) { + invites[invite.player] = invite + } + + fun removeInvite(player: UUID) { + invites.remove(player) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyAPI.kt new file mode 100644 index 0000000..f3a037f --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyAPI.kt @@ -0,0 +1,472 @@ +package com.minexd.api.module.party + +import com.google.gson.JsonObject +import com.google.gson.reflect.TypeToken +import com.minexd.api.API +import com.minexd.api.module.discord.main.DiscordHandler +import com.minexd.api.module.friend.FriendRepository +import com.minexd.api.module.friend.structure.FriendshipType +import com.minexd.api.module.messaging.MessagingHandler +import com.minexd.api.module.party.result.* +import com.minexd.api.module.party.structure.PartyInvite +import com.minexd.api.module.party.structure.PartyPrivacy +import com.minexd.api.module.profile.ProfileHandler +import com.minexd.api.module.profile.setting.impl.PartyRequestsSetting +import com.minexd.api.presence.PlayerPresenceHandler +import com.minexd.api.service.ServiceRegistry +import net.evilblock.cubed.serializers.Serializers +import net.evilblock.pidgin.message.Message +import spark.Route +import spark.kotlin.halt +import java.util.* + +object PartyAPI { + + private val MAP_TYPE = object : TypeToken>() {}.type + + val find: Route = Route { request, response -> + when { + request.queryParams().contains("party_id") -> { + val uuid = UUID.fromString(request.queryParams("party_id")) + return@Route PartyHandler.getPartyById(uuid) ?: halt(404) + } + request.queryParams().contains("player_id") -> { + val uuid = UUID.fromString(request.queryParams("player_id")) + return@Route PartyHandler.getPartyByPlayer(uuid) ?: halt(404) + } + request.queryParams().contains("search") -> { + val search = request.queryParams("search") + + try { + val uuid = UUID.fromString(search) + + val party = PartyHandler.getPartyById(uuid) ?: PartyHandler.getPartyByPlayer(uuid) + if (party != null) { + return@Route party + } + } catch (e: Exception) { } + + val playerUUID = API.uuidCache.uuid(search) + if (playerUUID != null) { + return@Route PartyHandler.getPartyByPlayer(playerUUID) ?: halt(404) + } + + return@Route halt(404) + } + else -> { + return@Route halt(400, "Bad request") + } + } + } + + val create: Route = Route { request, response -> + val playerUUID = UUID.fromString(request.queryParams("player_id")) + + if (PartyHandler.getPartyByPlayer(playerUUID) != null) { + return@Route halt(400, CreateError.ALREADY_IN_PARTY.name) + } + + + val party = Party(leader = playerUUID) + party.members.add(playerUUID) + + PartyHandler.cache(party) + PartyRepository.saveParty(party) + + val profile = ProfileHandler.getOrTryFetchProfile(playerUUID) + if (profile != null && (profile.synced || profile.syncedAccount != null)) { + DiscordHandler.getGuild().createVoiceChannel("party-${API.uuidCache.fetchUsername(party.leader)?.lowercase()}", DiscordHandler.getGuild().getCategoryById("930535751043653682")).queue { + val member = DiscordHandler.getMember(profile) + if (member != null) { + DiscordHandler.getGuild().moveVoiceMember(member, it).queue() + } + } + } + + response.status(201) + return@Route party + } + + val disband: Route = Route { request, response -> + val partyUUID = UUID.fromString(request.queryParams("party_id")) + + + // check if party exists + val party = PartyHandler.getPartyById(partyUUID) + ?: return@Route halt(404, DisbandError.NO_PARTY.name) + + val playerUUID = if (request.queryParams().contains("player_id")) { + UUID.fromString(request.queryParams("player_id")) + } else { + null + } + + if (playerUUID != null) { + // check if player is the party leader + if (playerUUID != party.leader) { + return@Route halt(400, DisbandError.NOT_LEADER.name) + } + } + + PartyHandler.forget(party) + PartyRepository.deleteParty(party) + + ServiceRegistry.executeOnce(Runnable { + val data = mutableMapOf("PartyID" to party.id.toString()) + + if (playerUUID != null) { + val profile = ProfileHandler.getOrFetchProfile(playerUUID)!! + data["PlayerUUID"] = playerUUID.toString() + data["PlayerName"] = profile.getColoredUsername() + } + + API.pidgin.sendMessage(Message( + id = "PartyDisband", + data = mapOf("PartyID" to party.id.toString()) + )) + }, 1L) + + val profile = ProfileHandler.getOrTryFetchProfile(playerUUID!!) + if (profile != null && (profile.synced || profile.syncedAccount != null)) { + DiscordHandler.getGuild().getVoiceChannelsByName("party-${API.uuidCache.fetchUsername(party.leader)}", true).firstOrNull()?.delete()?.queue() + } + + return@Route response + } + + val update: Route = Route { request, response -> + val partyUUID = UUID.fromString(request.queryParams("party_id")) + + val party = PartyHandler.getPartyById(partyUUID) + ?: return@Route halt(404, UpdateError.NO_PARTY.name) + + val body = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + + var updated = false + + if (body.has("leader")) { + party.leader = UUID.fromString(body["leader"].asString) + updated = true + } + + if (body.has("privacy")) { + party.privacy = PartyPrivacy.valueOf(body["privacy"].asString) + updated = true + } + + if (updated) { + PartyRepository.saveParty(party) + + API.pidgin.sendMessage(Message( + id = "PartyUpdate", + data = Serializers.gson.fromJson(body, MAP_TYPE) + )) + } else { + return@Route halt(304, UpdateError.NO_CHANGES.name) + } + + return@Route response + } + + val createInvite: Route = Route { request, response -> + val playerUUID = UUID.fromString(request.queryParams("player_id")) + val targetUUID = UUID.fromString(request.queryParams("target_id")) + + // cannot invite self + if (playerUUID == targetUUID) { + return@Route halt(400, "Bad request") + } + + // check if player has a party + val party = PartyHandler.getPartyByPlayer(playerUUID) + ?: return@Route halt(404, CreateInviteError.NO_PARTY.name) + + // check if player is the party leader + if (playerUUID != party.leader) { + return@Route halt(400, CreateInviteError.NOT_LEADER.name) + } + + // check if target is already a member + if (party.members.contains(targetUUID)) { + return@Route halt(400, CreateInviteError.TARGET_ALREADY_MEMBER.name) + } + + // check if target is already invited + if (party.hasInvite(targetUUID)) { + return@Route halt(400, CreateInviteError.TARGET_ALREADY_INVITED.name) + } + + // check if player is ignoring target + if (MessagingHandler.isIgnored(playerUUID, targetUUID)) { + return@Route halt(400, CreateInviteError.TARGET_IGNORED.name) + } + + // check if target is ignoring player + if (MessagingHandler.isIgnored(targetUUID, playerUUID)) { + return@Route halt(400, CreateInviteError.CANNOT_INVITE.name) + } + + val senderProfile = ProfileHandler.getOrFetchProfile(playerUUID) + + // check if target is online + val targetPresence = PlayerPresenceHandler.getOrFetchPresence(targetUUID) + if (targetPresence == null || !targetPresence.isOnline()) { + return@Route halt(400, CreateInviteError.TARGET_OFFLINE.name) + } + + // check if target has profile + val targetProfile = ProfileHandler.getOrTryFetchProfile(targetUUID) + ?: return@Route halt(400, CreateInviteError.TARGET_OFFLINE.name) + + // check target privacy settings + when (targetProfile.getSetting(PartyRequestsSetting).getValue()) { + PartyRequestsSetting.OptionValue.FRIENDS -> { + val friendship = FriendRepository.findFriendship(playerUUID, targetUUID) + if (friendship == null || friendship.type != FriendshipType.FRIENDS) { + return@Route halt(400, CreateInviteError.CANNOT_INVITE.name) + } + } + PartyRequestsSetting.OptionValue.NOBODY -> { + return@Route halt(400, CreateInviteError.CANNOT_INVITE.name) + } + else -> { } + } + + val invite = PartyInvite(player = targetUUID, invitedBy = playerUUID) + + party.addInvite(invite) + PartyRepository.saveParty(party) + + ServiceRegistry.executeOnce(Runnable { + API.pidgin.sendMessage(Message( + id = "PartyInvite", + data = mapOf( + "PartyID" to party.id.toString(), + "TargetUUID" to targetUUID.toString(), + "TargetName" to targetProfile.getColoredUsername(), + "SenderUUID" to playerUUID.toString(), + "SenderName" to senderProfile.getColoredUsername() + ) + )) + }, 1L) + + response.status(201) + return@Route response + } + + val destroyInvite: Route = Route { request, response -> + val playerUUID = UUID.fromString(request.queryParams("player_id")) + val targetUUID = UUID.fromString(request.queryParams("target_id")) + + // cannot invite self + if (playerUUID == targetUUID) { + return@Route halt(400, "Bad request") + } + + // check if player has a party + val party = PartyHandler.getPartyByPlayer(playerUUID) + ?: return@Route halt(404, DestroyInviteError.NO_PARTY.name) + + // check if player is the party leader + if (playerUUID != party.leader) { + return@Route halt(400, DestroyInviteError.NOT_LEADER.name) + } + + // check if target is already a member + if (party.members.contains(targetUUID)) { + return@Route halt(400, DestroyInviteError.TARGET_ALREADY_MEMBER.name) + } + + // check if target is invited + if (!party.hasInvite(targetUUID)) { + return@Route halt(400, DestroyInviteError.TARGET_NOT_INVITED.name) + } + + val senderProfile = ProfileHandler.getOrFetchProfile(playerUUID) + + // check if target has profile + val targetProfile = ProfileHandler.getOrTryFetchProfile(targetUUID) + ?: return@Route halt(404) + + party.removeInvite(targetUUID) + PartyRepository.saveParty(party) + + API.pidgin.sendMessage(Message( + id = "PartyInviteRevoke", + data = mapOf( + "PartyID" to party.id.toString(), + "TargetUUID" to targetUUID.toString(), + "TargetName" to targetProfile.getColoredUsername(), + "SenderUUID" to playerUUID.toString(), + "SenderName" to senderProfile.getColoredUsername() + ) + )) + + return@Route response + } + + val acceptInvite: Route = Route { request, response -> + val partyUUID = UUID.fromString(request.queryParams("party_id")) + val party = PartyHandler.getPartyById(partyUUID) + ?: return@Route halt(404) + + val playerUUID = UUID.fromString(request.queryParams("player_id")) + + // check if player is already in a party + if (PartyHandler.getPartyByPlayer(playerUUID) != null) { + return@Route halt(400, AcceptInviteError.ALREADY_IN_PARTY.name) + } + + // check if player is invited + if (!party.hasInvite(playerUUID)) { + return@Route halt(400, AcceptInviteError.NO_INVITE.name) + } + + // check if party is full + if (party.members.size >= PartyHandler.MAX_PARTY_SIZE) { + return@Route halt(400, AcceptInviteError.PARTY_FULL.name) + } + + val profile = ProfileHandler.getOrTryFetchProfile(playerUUID) + + party.removeInvite(playerUUID) + party.members.add(playerUUID) + PartyHandler.cache(playerUUID, party) + PartyRepository.saveParty(party) + + if (profile != null && (profile.synced || profile.syncedAccount != null)) { + val member = DiscordHandler.getMember(profile) + if (member != null) { + DiscordHandler.getGuild().moveVoiceMember(member, DiscordHandler.getGuild().getVoiceChannelsByName("party-${API.uuidCache.fetchUsername(party.leader)}", true).firstOrNull()).queue() + } + } + + API.pidgin.sendMessage(Message( + id = "PartyJoin", + data = mapOf( + "PartyID" to party.id.toString(), + "PlayerUUID" to playerUUID.toString(), + "PlayerName" to profile?.getColoredUsername() + ) + )) + + response.status(200) + return@Route response + } + + val kick: Route = Route { request, response -> + val playerUUID = UUID.fromString(request.queryParams("player_id")) + val targetUUID = UUID.fromString(request.queryParams("target_id")) + + // cannot kick self + if (playerUUID == targetUUID) { + return@Route halt(400, "Bad request") + } + + // check if player has party + val party = PartyHandler.getPartyByPlayer(playerUUID) + ?: return@Route halt(404, KickError.NO_PARTY.name) + + // check if player is the party leader + if (playerUUID != party.leader) { + return@Route halt(400, KickError.NOT_LEADER.name) + } + + // check if target is a member + if (!party.members.contains(targetUUID)) { + return@Route KickError.TARGET_NOT_MEMBER + } + + val senderProfile = ProfileHandler.getOrFetchProfile(playerUUID) + + // check if target has profile + val targetProfile = ProfileHandler.getOrTryFetchProfile(targetUUID) + ?: return@Route halt(404) + + party.members.remove(targetUUID) + PartyHandler.forget(targetUUID) + PartyRepository.saveParty(party) + + if (targetProfile.synced || targetProfile.syncedAccount != null) { + val member = DiscordHandler.getMember(targetProfile) + if (member != null) { + DiscordHandler.getGuild().kickVoiceMember(member) + } + } + + API.pidgin.sendMessage(Message( + id = "PartyKick", + data = mapOf( + "PartyID" to party.id.toString(), + "SenderUUID" to playerUUID.toString(), + "SenderName" to senderProfile.getColoredUsername(), + "TargetUUID" to targetUUID.toString(), + "TargetName" to targetProfile.getColoredUsername() + ) + )) + + return@Route response + } + + val leave: Route = Route { request, response -> + val playerUUID = UUID.fromString(request.queryParams("player_id")) + + // check if player has party + val party = PartyHandler.getPartyByPlayer(playerUUID) + ?: return@Route halt(404, LeaveError.NO_PARTY.name) + + val senderProfile = ProfileHandler.getOrTryFetchProfile(playerUUID) + + party.members.remove(playerUUID) + PartyHandler.forget(playerUUID) + PartyRepository.saveParty(party) + + if (senderProfile != null && (senderProfile.synced || senderProfile.syncedAccount != null)) { + val member = DiscordHandler.getMember(senderProfile) + if (member != null) { + DiscordHandler.getGuild().kickVoiceMember(member) + } + } + + API.pidgin.sendMessage(Message( + id = "PartyLeave", + data = mapOf( + "PartyID" to party.id.toString(), + "PlayerUUID" to playerUUID.toString(), + "PlayerName" to senderProfile?.getColoredUsername() + ) + )) + + return@Route response + } + + val chat: Route = Route { request, response -> + val playerUUID = UUID.fromString(request.queryParams("player_id")) + + val party = PartyHandler.getPartyByPlayer(playerUUID) + ?: return@Route halt(404, ChatError.NO_PARTY.name) + + val profile = ProfileHandler.getOrFetchProfile(playerUUID) + + val presence = PlayerPresenceHandler.getOrFetchPresence(playerUUID) + ?: return@Route halt(404, "Bad request") + + val rawMessage = request.body() + val message = rawMessage.substring(1, rawMessage.length - 1) + + API.pidgin.sendMessage(Message( + id = "PartyChat", + data = mapOf( + "PartyID" to party.id.toString(), + "SenderUUID" to playerUUID.toString(), + "SenderName" to profile.getColoredUsername(), + "SenderServer" to presence.server, + "Message" to message + ) + )) + + response.status(201) + return@Route response + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyHandler.kt new file mode 100644 index 0000000..251e39e --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyHandler.kt @@ -0,0 +1,62 @@ +package com.minexd.api.module.party + +import com.minexd.api.module.party.service.PartyAutoDisbandService +import com.minexd.api.module.party.service.PartyInviteExpiryService +import com.minexd.api.service.ServiceRegistry +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit + +object PartyHandler { + + val parties: MutableSet = ConcurrentHashMap.newKeySet() + private val partyById: MutableMap = ConcurrentHashMap() + private val partyByPlayer: MutableMap = ConcurrentHashMap() + + const val MAX_PARTY_SIZE = 32 + val INVITE_LIFE_TIME = TimeUnit.MINUTES.toMillis(2L) + + fun initialLoad() { + for (party in PartyRepository.findParties()) { + cache(party) + } + + ServiceRegistry.register(PartyAutoDisbandService, 20L, 20L) + ServiceRegistry.register(PartyInviteExpiryService, 20L, 20L) + } + + fun getPartyById(id: UUID): Party? { + return partyById[id] + } + + fun getPartyByPlayer(player: UUID): Party? { + return partyByPlayer[player] + } + + fun cache(party: Party) { + parties.add(party) + partyById[party.id] = party + + for (member in party.members) { + partyByPlayer[member] = party + } + } + + fun forget(party: Party) { + parties.remove(party) + partyById.remove(party.id) + + for (member in party.members) { + partyByPlayer.remove(member) + } + } + + fun cache(player: UUID, party: Party) { + partyByPlayer[player] = party + } + + fun forget(player: UUID) { + partyByPlayer.remove(player) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyRepository.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyRepository.kt new file mode 100644 index 0000000..5aae73a --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/PartyRepository.kt @@ -0,0 +1,46 @@ +package com.minexd.api.module.party + +import com.google.gson.reflect.TypeToken +import com.minexd.api.API +import com.minexd.api.Repository +import com.mongodb.BasicDBObject +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.Collation +import com.mongodb.client.model.CollationStrength +import com.mongodb.client.model.IndexOptions +import net.evilblock.cubed.serializers.Serializers +import org.bson.Document + +object PartyRepository : Repository { + + private val PARTY_TYPE = object : TypeToken() {}.type + + private lateinit var partyCollection: MongoCollection + + override fun initialize() { + partyCollection = API.mongoDatabase.getCollection("party") + partyCollection.createIndex(BasicDBObject("id", 1)) + partyCollection.createIndex(BasicDBObject("owner", 1)) + partyCollection.createIndex(BasicDBObject("name", 1), IndexOptions().collation(Collation.builder() + .locale("en") + .collationStrength(CollationStrength.SECONDARY) + .build())) // case insensitive index for name + } + + fun findParties(): List { + return arrayListOf().also { list -> + for (document in partyCollection.find()) { + list.add(Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), PARTY_TYPE)) + } + } + } + + fun saveParty(party: Party) { + partyCollection.replaceOne(Document("id", party.id.toString()), Document.parse(Serializers.gson.toJson(party, PARTY_TYPE))) + } + + fun deleteParty(party: Party) { + partyCollection.deleteOne(Document("id", party.id.toString())) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/AcceptInviteError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/AcceptInviteError.kt new file mode 100644 index 0000000..d4f7191 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/AcceptInviteError.kt @@ -0,0 +1,9 @@ +package com.minexd.api.module.party.result + +enum class AcceptInviteError { + + NO_INVITE, + ALREADY_IN_PARTY, + PARTY_FULL, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/ChatError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/ChatError.kt new file mode 100644 index 0000000..1093bc8 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/ChatError.kt @@ -0,0 +1,7 @@ +package com.minexd.api.module.party.result + +enum class ChatError { + + NO_PARTY, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/CreateError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/CreateError.kt new file mode 100644 index 0000000..453b2d9 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/CreateError.kt @@ -0,0 +1,7 @@ +package com.minexd.api.module.party.result + +enum class CreateError { + + ALREADY_IN_PARTY, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/CreateInviteError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/CreateInviteError.kt new file mode 100644 index 0000000..aa7e563 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/CreateInviteError.kt @@ -0,0 +1,13 @@ +package com.minexd.api.module.party.result + +enum class CreateInviteError { + + NO_PARTY, + NOT_LEADER, + TARGET_OFFLINE, + TARGET_IGNORED, + TARGET_ALREADY_INVITED, + TARGET_ALREADY_MEMBER, + CANNOT_INVITE, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/DestroyInviteError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/DestroyInviteError.kt new file mode 100644 index 0000000..4d5ad80 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/DestroyInviteError.kt @@ -0,0 +1,10 @@ +package com.minexd.api.module.party.result + +enum class DestroyInviteError { + + NO_PARTY, + NOT_LEADER, + TARGET_NOT_INVITED, + TARGET_ALREADY_MEMBER, + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/DisbandError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/DisbandError.kt new file mode 100644 index 0000000..6b2082a --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/DisbandError.kt @@ -0,0 +1,8 @@ +package com.minexd.api.module.party.result + +enum class DisbandError { + + NO_PARTY, + NOT_LEADER + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/KickError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/KickError.kt new file mode 100644 index 0000000..b854b31 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/KickError.kt @@ -0,0 +1,9 @@ +package com.minexd.api.module.party.result + +enum class KickError { + + NO_PARTY, + NOT_LEADER, + TARGET_NOT_MEMBER + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/LeaveError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/LeaveError.kt new file mode 100644 index 0000000..aa3e303 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/LeaveError.kt @@ -0,0 +1,7 @@ +package com.minexd.api.module.party.result + +enum class LeaveError { + + NO_PARTY + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/UpdateError.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/UpdateError.kt new file mode 100644 index 0000000..146f1f1 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/result/UpdateError.kt @@ -0,0 +1,8 @@ +package com.minexd.api.module.party.result + +enum class UpdateError { + + NO_PARTY, + NO_CHANGES + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/service/PartyAutoDisbandService.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/service/PartyAutoDisbandService.kt new file mode 100644 index 0000000..3065821 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/service/PartyAutoDisbandService.kt @@ -0,0 +1,37 @@ +package com.minexd.api.module.party.service + +import com.minexd.api.API +import com.minexd.api.module.party.Party +import com.minexd.api.module.party.PartyHandler +import com.minexd.api.module.party.PartyRepository +import com.minexd.api.presence.PlayerPresenceHandler +import net.evilblock.pidgin.message.Message + +object PartyAutoDisbandService : Runnable { + + override fun run() { + val disband = arrayListOf() + + for (party in PartyHandler.parties) { + val onlineMembers = party.members.filter { uuid -> + val presence = PlayerPresenceHandler.getOrFetchPresence(uuid) + presence != null && presence.isOnline() + } + + if (onlineMembers.isEmpty()) { + disband.add(party) + } + } + + for (party in disband) { + PartyHandler.forget(party) + PartyRepository.deleteParty(party) + + API.pidgin.sendMessage(Message( + id = "PartyDisband", + data = mapOf("PartyID" to party.id.toString()) + )) + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/service/PartyInviteExpiryService.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/service/PartyInviteExpiryService.kt new file mode 100644 index 0000000..577e58f --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/service/PartyInviteExpiryService.kt @@ -0,0 +1,42 @@ +package com.minexd.api.module.party.service + +import com.minexd.api.API +import com.minexd.api.module.party.PartyHandler +import com.minexd.api.module.profile.ProfileHandler +import net.evilblock.pidgin.message.Message +import java.util.* + +object PartyInviteExpiryService : Runnable { + + override fun run() { + for (party in PartyHandler.parties) { + if (party.invites.isNotEmpty()) { + val expired = arrayListOf() + + for (invite in party.invites.values) { + if (invite.isExpired()) { + expired.add(invite.player) + } + } + + if (expired.isNotEmpty()) { + for (invite in expired) { + party.removeInvite(invite) + + val profile = ProfileHandler.getOrTryFetchProfile(invite) ?: continue + + API.pidgin.sendMessage(Message( + id = "PartyInviteExpire", + data = mapOf( + "PartyID" to party.id.toString(), + "PlayerUUID" to invite.toString(), + "PlayerName" to profile.getColoredUsername() + ) + )) + } + } + } + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/structure/PartyInvite.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/structure/PartyInvite.kt new file mode 100644 index 0000000..7a720ed --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/structure/PartyInvite.kt @@ -0,0 +1,16 @@ +package com.minexd.api.module.party.structure + +import com.minexd.api.module.party.PartyHandler +import java.util.* + +data class PartyInvite( + val player: UUID, + val invitedBy: UUID, + val createdAt: Long = System.currentTimeMillis() +) { + + fun isExpired(): Boolean { + return System.currentTimeMillis() - createdAt >= PartyHandler.INVITE_LIFE_TIME + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/structure/PartyPrivacy.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/structure/PartyPrivacy.kt new file mode 100644 index 0000000..cd18817 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/party/structure/PartyPrivacy.kt @@ -0,0 +1,10 @@ +package com.minexd.api.module.party.structure + +enum class PartyPrivacy { + + EVERYONE, + FRIENDS, + INVITED, + NOBODY + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/Profile.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/Profile.kt new file mode 100644 index 0000000..768059e --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/Profile.kt @@ -0,0 +1,158 @@ +package com.minexd.api.module.profile + +import com.google.gson.annotations.JsonAdapter +import com.minexd.api.API +import com.minexd.api.module.profile.grant.Grant +import com.minexd.api.module.profile.punishment.Punishment +import com.minexd.api.module.profile.punishment.PunishmentType +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption +import com.minexd.api.module.profile.setting.SettingsMapSerializer +import com.minexd.api.module.profile.vote.VoteRecord +import com.minexd.api.module.rank.Rank +import com.minexd.api.module.rank.RankHandler +import java.math.BigInteger +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +class Profile(val uuid: UUID) { + + @Transient var cacheExpiry: Long? = null + + val firstSeen: Long = System.currentTimeMillis() + val identities: MutableSet = hashSetOf() + + val grants: ArrayList = arrayListOf() + val permissions: ArrayList = arrayListOf() + val punishments: MutableList = arrayListOf() + + var votes: MutableList = arrayListOf() + var votePoints: Int = 0 + + var registered: Boolean = false + var registeredEmail: String? = null + var registrationCode: String? = null + var registrationCodeExpiry: Long? = null + + var synced: Boolean = false + var syncedAccount: String? = null + var syncCode: String? = null + var syncCodeExpiry: Long? = null + val coins: Int = 0 + + var activeTag: String? = null + + @JsonAdapter(SettingsMapSerializer::class) + val settings: ConcurrentHashMap, SettingOption<*>> = ConcurrentHashMap() + + fun getUsername(): String { + return API.uuidCache.name(uuid) + } + + fun getColoredUsername(): String { + return getBestDisplayRank().getColor() + getUsername() + } + + fun getCompoundedPermissions(): Set { + val permissions = HashSet() + permissions.addAll(this.permissions) + permissions.addAll(RankHandler.defaultRank.getCompoundedPermissions()) + + getGrantsByGroups(setOf("GLOBAL")) + .filter { grant -> grant.isActive() } + .sortedBy { grant -> grant.rank.displayOrder } + .forEach { grant -> grant.rank.let { permissions.addAll(it.getCompoundedPermissions()) } } + + return permissions + } + + fun getMappedCompoundedPermissions(): Map> { + val defaultRank = RankHandler.defaultRank + + val map: HashMap> = hashMapOf() + defaultRank.getMappedCompoundedPermissions(map) + + getGrantsByGroups(setOf("GLOBAL")) + .filter { grant -> grant.isActive() } + .sortedBy { grant -> grant.rank.displayOrder } + .forEach { grant -> grant.rank.getMappedCompoundedPermissions(map) } + + return map + } + + fun getGrantById(id: UUID): Grant? { + for (grant in grants) { + if (grant.id == id) { + return grant + } + } + return null + } + + fun getGrantByRank(rank: Rank, active: Boolean): Grant? { + for (grant in grants) { + if (grant.rank == rank && (!active || grant.isActive())) { + return grant + } + } + return null + } + + fun getGrantsByGroups(groups: Set): List { + val grants = arrayListOf() + for (grant in this.grants) { + for (group in groups) { + if (grant.rank.groups.contains(group)) { + grants.add(grant) + } + } + } + return grants + } + + fun getBestDisplayRank(): Rank { + return getBestDisplayGrant()?.rank ?: RankHandler.defaultRank + } + + fun getBestDisplayGrant(): Grant? { + return getGrantsByGroups(setOf("GLOBAL")) + .filter { grant -> grant.isActive() && !grant.rank.hidden } + .minByOrNull { grant -> grant.rank.displayOrder } + } + + fun getPunishmentById(id: UUID): Punishment? { + for (punishment in punishments) { + if (punishment.uuid == id) { + return punishment + } + } + return null + } + + fun getActivePunishment(type: PunishmentType): Punishment? { + for (punishment in punishments) { + if (punishment.punishmentType == type && punishment.isActive()) { + return punishment + } + } + return null + } + + /** + * Updates the user's setting option for the given [setting]. + */ + fun updateSetting(setting: Setting<*>, value: SettingOption<*>) { + settings[setting] = value + } + + /** + * Gets the user's setting option for the given [setting]. + */ + fun getSetting(setting: Setting): SettingOption { + if (!settings.containsKey(setting)) { + settings[setting] = setting.newDefaultOption() + } + return settings[setting]!! as SettingOption + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileAPI.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileAPI.kt new file mode 100644 index 0000000..57fe21c --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileAPI.kt @@ -0,0 +1,513 @@ +package com.minexd.api.module.profile + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.minexd.api.API +import com.minexd.api.Constants +import com.minexd.api.module.chattag.TagHandler +import com.minexd.api.module.profile.grant.Grant +import com.minexd.api.module.profile.punishment.Punishment +import com.minexd.api.module.profile.punishment.PunishmentType +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingsRegistry +import com.minexd.api.module.profile.sync.SyncError +import com.minexd.api.module.profile.vote.VoteRecord +import com.minexd.api.module.rank.RankHandler +import com.minexd.api.presence.PlayerPresenceHandler +import com.minexd.api.util.Cryptography +import net.evilblock.cubed.serializers.Serializers +import net.evilblock.pidgin.message.Message +import org.apache.commons.lang3.RandomStringUtils +import spark.Route +import spark.kotlin.halt +import java.util.* +import java.util.concurrent.TimeUnit + +object ProfileAPI { + + val get: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + return@Route ProfileHandler.getOrTryFetchProfile(uuid) ?: halt(404, "That player does not exist") + } + + val touch: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + + if (API.debug) { + API.logger.info(Serializers.gson.toJson(ProfileHandler.getOrFetchProfile(uuid), Profile::class.java)) + } + + return@Route ProfileHandler.getOrFetchProfile(uuid) + } + + val login: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileRepository.findOrCreateProfile(uuid) + + val body = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + if (!body.has("ipAddress")) { + halt(400, "Bad request") + } + + val ipAddress = body.get("ipAddress").asString + val isLocalAddress = ipAddress == "localhost" || ipAddress == "127.0.0.1" || ipAddress.startsWith("192.168") + if (!isLocalAddress) { + val identity = String(Cryptography.encrypt(ipAddress.toByteArray())) + if (!profile.identities.contains(identity)) { + profile.identities.add(identity) + ProfileRepository.saveIdentity(uuid, identity) + } + } + + if (API.debug) { + API.logger.info(Serializers.gson.toJson(profile, Profile::class.java)) + } + + return@Route profile + } + + val grant: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileRepository.findOrCreateProfile(uuid) + + val grant = Serializers.gson.fromJson(request.body(), Grant::class.java) + if (grant.rank == null) { + return@Route halt(400, "That rank does not exist") + } + + if (profile.getGrantByRank(grant.rank, active = true) != null) { + return@Route halt(400, "Player already has the ${grant.rank.id} rank") + } + + profile.grants.add(grant) + ProfileRepository.saveProfile(profile) + + API.logger.info("Broadcasting grant (${grant.rank.id}) for $uuid across network") + + API.pidgin.sendMessage( + Message( + id = "ProfileGrant", + data = mapOf( + "UUID" to uuid.toString(), + "Grant" to grant.id.toString() + ) + ) + ) + + response.status(201) + return@Route response + } + + val revokeGrant: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileRepository.findOrCreateProfile(uuid) + + val body = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + + val rank = RankHandler.getRankById(body["rank"].asString) ?: return@Route halt(404) + val grant = profile.getGrantByRank(rank, active = true) ?: return@Route halt(403, "Player does not have rank") + + val removedBy = UUID.fromString(body["removedBy"].asString) + val removeReason = body["removeReason"].asString + + grant.removed = true + grant.removedAt = System.currentTimeMillis() + grant.removedBy = removedBy + grant.removeReason = removeReason + + ProfileRepository.saveProfile(profile) + + API.logger.info("Broadcasting revoke grant (${grant.rank.id}) for $uuid across network") + + API.pidgin.sendMessage( + Message( + id = "ProfileGrant", + data = mapOf( + "UUID" to uuid.toString(), + "Grant" to grant.id.toString() + ) + ) + ) + + response.status(200) + return@Route response + } + + val punish: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileRepository.findOrCreateProfile(uuid) + + val punishment = Serializers.gson.fromJson(request.body(), Punishment::class.java) + + if (punishment.punishmentType != PunishmentType.WARN) + { + if (profile.getActivePunishment(punishment.punishmentType) != null) + { + return@Route halt(403, "Player is already ${punishment.punishmentType.context}") + } + } + + profile.punishments.add(punishment) + ProfileRepository.saveProfile(profile) + + val executorProfile = punishment.issuedBy?.let { ProfileRepository.findOrCreateProfile(it) } + + API.pidgin.sendMessage( + Message( + id = "ProfileUpdate", + data = mapOf("UUID" to uuid.toString()) + ) + ) + + API.pidgin.sendMessage( + Message( + id = "ExecutePunishment", + data = mapOf( + "Target" to profile.uuid.toString(), + "TargetName" to profile.getColoredUsername(), + "Executor" to executorProfile?.uuid, + "ExecutorName" to (executorProfile?.getColoredUsername() ?: Constants.CONSOLE_NAME), + "Punishment" to punishment, + "SharedAccounts" to ProfileRepository.findSharedAccounts(profile), + "Silent" to true + ) + ) + ) + + response.status(201) + return@Route response + } + + val pardon: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileRepository.findOrCreateProfile(uuid) + + val body = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + + val punishmentType = PunishmentType.valueOf(body["punishmentType"].asString) + if (punishmentType == PunishmentType.WARN) { + return@Route halt(403, "Cannot pardon a warning") + } + + val punishment = profile.getActivePunishment(punishmentType) + ?: return@Route halt(403, "Player is not ${punishmentType.context}") + + val pardonedBy = if (body["pardonedBy"].isJsonPrimitive) { + UUID.fromString(body["pardonedBy"].asString) + } else { + null + } + + val pardonReason = body["pardonReason"].asString + + punishment.pardoned = true + punishment.pardonedAt = System.currentTimeMillis() + punishment.pardonedBy = pardonedBy + punishment.pardonReason = pardonReason + + ProfileRepository.saveProfile(profile) + + val executorProfile = punishment.pardonedBy?.let { ProfileRepository.findOrCreateProfile(it) } + + API.pidgin.sendMessage( + Message( + id = "ProfileUpdate", + data = mapOf("UUID" to uuid.toString()) + ) + ) + + API.pidgin.sendMessage( + Message( + id = "ExecutePunishment", + data = mapOf( + "Target" to profile.uuid.toString(), + "TargetName" to profile.getColoredUsername(), + "Executor" to executorProfile?.uuid, + "ExecutorName" to (executorProfile?.getColoredUsername() ?: Constants.CONSOLE_NAME), + "Punishment" to punishment, + "Silent" to true + ) + ) + ) + + response.status(200) + return@Route response + } + + val addPermission: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileRepository.findOrCreateProfile(uuid) + + val permission = request.body().replace("\"", "") + + if (!profile.permissions.contains(permission)) { + profile.permissions.add(permission) + } else { + halt(403, "Player has already been granted that permission") + } + + ProfileRepository.saveProfile(profile) + + API.pidgin.sendMessage( + Message( + id = "ProfileUpdate", + data = mapOf("UUID" to uuid.toString()) + ) + ) + + response.status(201) + return@Route response + } + + val revokePermission: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileRepository.findOrCreateProfile(uuid) + + val permission = request.body().replace("\"", "") + + if (profile.permissions.contains(permission)) { + profile.permissions.remove(permission) + } else { + halt(403, "Player has not been granted that permission") + } + + ProfileRepository.saveProfile(profile) + + API.pidgin.sendMessage( + Message( + id = "ProfileUpdate", + data = mapOf("UUID" to uuid.toString()) + ) + ) + + response.status(200) + return@Route response + } + + val updateSettings: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileHandler.getOrTryFetchProfile(uuid) ?: return@Route halt(404) + + val body = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + + if (!body.has("setting")) { + return@Route halt(400, "Bad request") + } + + if (!body.has("value")) { + return@Route halt(400, "Bad request") + } + + val setting: Setting<*> = SettingsRegistry.find(body["setting"].asString) + ?: return@Route halt(400, "Bad request") + + val value = setting.deserializeOption(body.get("value")) + if (!setting.getOptions().contains(value)) { + return@Route halt(400, "Option not accepted") + } + + profile.updateSetting(setting, value) + ProfileRepository.saveProfile(profile) + + response.status(200) + return@Route response + } + + val getPresence: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + return@Route PlayerPresenceHandler.getOrFetchPresence(uuid) + } + + val getSuspensionInfo: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileHandler.getOrTryFetchProfile(uuid) + ?: return@Route halt(404, "Player is not banned") + + val activeBan = ProfileRepository.findActiveBan(profile) + ?: return@Route halt(404, "Player is not banned") + + return@Route mapOf( + "punishment" to activeBan.second, + "relation" to activeBan.first.uuid.toString() + ) + } + + val getSharedAccounts: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + val profile = ProfileHandler.getOrTryFetchProfile(uuid) ?: return@Route emptyList() + return@Route ProfileRepository.findSharedAccounts(profile) + } + + val countVote: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + + val profile = ProfileHandler.getOrTryFetchProfile(uuid) + ?: return@Route halt(404) + + val record = Serializers.gson.fromJson(request.body(), VoteRecord::class.java) + profile.votes.add(record) + profile.votePoints++ + + ProfileRepository.saveProfile(profile) + + response.status(201) + return@Route response + } + + val startSync: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + + val profile = ProfileHandler.getOrTryFetchProfile(uuid) + ?: return@Route halt(404) + + if (profile.synced || profile.syncedAccount != null) { + return@Route halt(400, SyncError.ALREADY_SYNCED.name) + } + + if (profile.syncCode != null && System.currentTimeMillis() < profile.syncCodeExpiry!!) { + return@Route halt(400, SyncError.CODE_ALREADY_SENT.name) + } + + profile.syncCode = RandomStringUtils.random(8, 0, 0, true, true, null, Cryptography.secureRandom) + profile.syncCodeExpiry = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(3L) + + ProfileRepository.saveProfile(profile) + + response.status(201) + response.body(profile.syncCode) + return@Route response + } + + val finishSync: Route = Route { request, response -> + val data = Serializers.gson.fromJson(request.body(), JsonObject::class.java) + if (!data.has("code")) { + return@Route halt(400, "Bad request") + } + + if (!data.has("account")) { + return@Route halt(400, "Bad request") + } + + val uuid = UUID.fromString(request.params(":uuid")) + + val profile = ProfileHandler.getOrTryFetchProfile(uuid) + ?: return@Route halt(404) + + if (profile.synced || profile.syncedAccount != null) { + return@Route halt(400, SyncError.ALREADY_SYNCED.name) + } + + if (profile.syncCode != data["code"].asString) { + return@Route halt(400, SyncError.CODE_INVALID.name) + } + + if (System.currentTimeMillis() >= profile.syncCodeExpiry!!) { + return@Route halt(400, SyncError.CODE_EXPIRED.name) + } + + profile.syncCode = null + profile.syncCodeExpiry = null + profile.syncedAccount = data["account"].asString + profile.synced = true + + ProfileRepository.saveProfile(profile) + + API.pidgin.sendMessage( + Message( + id = "AccountSynced", + data = mapOf( + "PlayerUUID" to profile.uuid.toString() + ) + ) + ) + + response.status(200) + return@Route response + } + + val resetSync: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + + val profile = ProfileHandler.getOrTryFetchProfile(uuid) + ?: return@Route halt(404) + + profile.synced = false + profile.syncedAccount = null + profile.syncCode = null + profile.syncCodeExpiry = null + + ProfileRepository.saveProfile(profile) + + response.status(200) + return@Route response + } + + val findBySyncCode: Route = Route { request, response -> + if (!request.queryParams().contains("code")) { + return@Route halt(400, "Bad request") + } + + val profile = ProfileRepository.findProfileBySyncCode(request.queryParams("code")) + println(profile != null) + + return@Route profile ?: halt(404) + } + + val findBySyncedAccount: Route = Route { request, response -> + if (!request.queryParams().contains("id")) { + return@Route halt(400, "Bad request") + } + + return@Route ProfileRepository.findProfileBySyncedAccount(request.queryParams("id")) ?: halt(404) + } + + val setTag: Route = Route { request, response -> + val uuid = UUID.fromString(request.params(":uuid")) + + val profile = ProfileHandler.getOrTryFetchProfile(uuid) + ?: return@Route halt(404) + + // for SOME reason the requester in CoreXD is sending "" wrapped around the body?! + // see https://github.com/square/retrofit/issues/1210 + // && + // https://stackoverflow.com/questions/36904477/retrofit-2-multipart-post-request-sends-extra-quotes-to-php/36907435#36907435 + val tag = request.body().replace("\"", "") + + API.logger.info("setTag request $uuid, $tag") + API.logger.info(TagHandler.getTags().map { it.name }.toString()) + + if (tag.isBlank()) { + profile.activeTag = null + ProfileRepository.saveProfile(profile) + + response.status(200) + return@Route response + } + + val validTagObj = TagHandler.getByName(tag) + + if (validTagObj == null) { + return@Route halt(400, "That tag does not exist.") + } else { + profile.activeTag = tag + } + + ProfileRepository.saveProfile(profile) + + response.status(200) + return@Route response + } + + val famousList = Route { request, response -> + return@Route JsonObject().also { json -> + for ((rank, players) in ProfileHandler.famous.entries) { + json.add(rank.id, JsonArray().also { playerArray -> + for (player in players) { + playerArray.add(Serializers.gson.toJsonTree(player)) + } + }) + } + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileHandler.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileHandler.kt new file mode 100644 index 0000000..3b11fc8 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileHandler.kt @@ -0,0 +1,78 @@ +package com.minexd.api.module.profile + +import com.minexd.api.module.rank.Rank +import com.minexd.api.module.rank.RankRepository +import com.minexd.api.module.staff.Staff +import com.minexd.api.module.staff.StaffHandler +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit + +object ProfileHandler { + + private val profiles: MutableMap = ConcurrentHashMap() + + val famous: MutableMap> = ConcurrentHashMap() + var famousMembers: MutableSet = ConcurrentHashMap.newKeySet() + + fun load() { + for (rank in RankRepository.findRanks().filter{it.isFamous()}) { + this.famous[rank] = ProfileRepository.findProfilesByRank(rank).map{it.uuid} + } + + val newMembersSet = ConcurrentHashMap.newKeySet() + for (famousList in this.famous.values) { + for (famous in famousList) { + newMembersSet.add(famous) + } + } + + this.famousMembers = newMembersSet + } + + fun getLoadedProfiles(): Collection { + return profiles.values + } + + fun clearLoadedProfiles() { + profiles.clear() + } + + fun isProfileLoaded(uuid: UUID): Boolean { + return profiles.containsKey(uuid) + } + + fun getProfile(uuid: UUID): Profile { + return profiles[uuid]!! + } + + fun getOrTryFetchProfile(uuid: UUID): Profile? { + return if (isProfileLoaded(uuid)) { + getProfile(uuid) + } else { + return ProfileRepository.findProfileById(uuid)?.also { profile -> + profile.cacheExpiry = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1L) + cacheProfile(profile) + } + } + } + + fun getOrFetchProfile(uuid: UUID): Profile { + return if (isProfileLoaded(uuid)) { + getProfile(uuid) + } else { + return ProfileRepository.findOrCreateProfile(uuid).also { profile -> + profile.cacheExpiry = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1L) + cacheProfile(profile) + } + } + } + + fun cacheProfile(profile: Profile) { + profiles[profile.uuid] = profile + } + + fun forgetProfile(profile: Profile) { + profiles.remove(profile.uuid) + } +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileRepository.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileRepository.kt new file mode 100644 index 0000000..11df2cf --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/ProfileRepository.kt @@ -0,0 +1,166 @@ +package com.minexd.api.module.profile + +import com.google.gson.reflect.TypeToken +import com.minexd.api.API +import com.minexd.api.Repository +import com.minexd.api.module.profile.grant.GrantQueryResult +import com.minexd.api.module.profile.punishment.Punishment +import com.minexd.api.module.profile.punishment.PunishmentQueryResult +import com.minexd.api.module.profile.punishment.PunishmentType +import com.minexd.api.module.rank.Rank +import com.mongodb.BasicDBObject +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.* +import net.evilblock.cubed.serializers.Serializers +import org.bson.Document +import java.util.* + +object ProfileRepository : Repository { + + private val PROFILE_TYPE = object : TypeToken() {}.type + + private lateinit var profilesCol: MongoCollection + + override fun initialize() { + profilesCol = API.mongoDatabase.getCollection("profiles") + profilesCol.createIndex(BasicDBObject("uuid", 1)) + profilesCol.createIndex(BasicDBObject("registeredEmail", 1), IndexOptions().collation( + Collation.builder() + .locale("en") + .collationStrength(CollationStrength.SECONDARY) + .build())) + } + + private fun deserializeDocument(document: Document): Profile { + return (Serializers.gson.fromJson(document.toJson(Serializers.writerSettings), PROFILE_TYPE) as Profile).also { profile -> + if (profile.votes == null) profile.votes = arrayListOf() + profile.grants.removeIf { + it.rank == null + } + } + } + + fun findProfileById(player: UUID): Profile? { + return deserializeDocument(profilesCol.find(Document("uuid", player.toString())).first() ?: return null) + } + + fun findProfileByEmail(email: String): Profile? { + val query = Document().also { query -> + query.append("registered", true) + query.append("registeredEmail", email) + } + + return deserializeDocument(profilesCol.find(query).first() ?: return null) + } + + fun findProfileByRegistrationCode(code: String): Profile? { + return deserializeDocument(profilesCol.find(Document("registrationCode", code)).first() ?: return null) + } + + fun findProfileBySyncCode(code: String): Profile? { + return deserializeDocument(profilesCol.find(Document("syncCode", code)).first() ?: return null) + } + + fun findProfileBySyncedAccount(code: String): Profile? { + return deserializeDocument(profilesCol.find(Document("syncedAccount", code)).first() ?: return null) + } + + fun findOrCreateProfile(player: UUID): Profile { + return ProfileHandler.getOrTryFetchProfile(player) ?: Profile(player).also { newProfile -> + saveProfile(newProfile) + } + } + + fun saveProfile(profile: Profile) { + profilesCol.replaceOne(Document("uuid", profile.uuid.toString()), Document.parse(Serializers.gson.toJson(profile, PROFILE_TYPE)), ReplaceOptions().upsert(true)) + } + + fun saveIdentity(uuid: UUID, identity: String) { + profilesCol.updateOne(Document("uuid", uuid.toString()), Document("\$push", Document("identities", identity)), UpdateOptions().upsert(true)) + } + + fun findSharedAccounts(profile: Profile): List { + val identities = profile.identities + if (identities.isEmpty()) { + return emptyList() + } + + return arrayListOf().also { results -> + for (matchingDocument in profilesCol.find(Document("identities", identities))) { + val uuid = UUID.fromString(matchingDocument.getString("uuid")) + if (uuid != null && !results.contains(uuid) && uuid != profile.uuid) { + results.add(uuid) + } + } + } + } + + fun findActiveBan(profile: Profile): Pair? { + profile.getActivePunishment(PunishmentType.BLACKLIST)?.also { blacklist -> + return Pair(profile, blacklist) + } + + profile.getActivePunishment(PunishmentType.BAN)?.also { ban -> + return Pair(profile, ban) + } + + val sharedAccounts = findSharedAccounts(profile) + if (sharedAccounts.isNotEmpty()) { + for (altId in sharedAccounts) { + val altProfile = findOrCreateProfile(altId) + + altProfile.getActivePunishment(PunishmentType.BLACKLIST)?.also { blacklist -> + return Pair(altProfile, blacklist) + } + + altProfile.getActivePunishment(PunishmentType.BAN)?.also { ban -> + return Pair(altProfile, ban) + } + } + } + + return null + } + + fun findGrantsIssuedBy(uuid: UUID): List { + return arrayListOf().also { results -> + val query = Document("grants", Document("\$elemMatch", Document("issuedBy", uuid.toString()))) + for (matchingDocument in profilesCol.find(query)) { + val profile = deserializeDocument(matchingDocument) + for (grant in profile.grants) { + if (grant.issuedBy == uuid) { + results.add(GrantQueryResult(profile, grant)) + } + } + } + } + } + + fun findPunishmentsIssuedBy(uuid: UUID): List { + return arrayListOf().also { results -> + val query = Document("punishments", Document("\$elemMatch", Document("issuedBy", uuid.toString()))) + for (matchingDocument in profilesCol.find(query)) { + val profile = deserializeDocument(matchingDocument) + for (punishment in profile.punishments) { + if (punishment.issuedBy == uuid) { + results.add(PunishmentQueryResult(profile, punishment)) + } + } + } + } + } + + fun findProfilesByRank(rank: Rank): List { + return arrayListOf().also { results -> + val query = Document("grants", Document("\$elemMatch", Document("rank", rank.id).append("removed", false))) + for (matchingDocument in profilesCol.find(query)) { + val profile = deserializeDocument(matchingDocument) + val bestGrant = profile.getBestDisplayGrant() ?: continue + if (bestGrant.rank == rank) { + results.add(profile) + } + } + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/grant/Grant.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/grant/Grant.kt new file mode 100644 index 0000000..5c9dfa0 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/grant/Grant.kt @@ -0,0 +1,27 @@ +package com.minexd.api.module.profile.grant + +import com.google.gson.annotations.JsonAdapter +import com.minexd.api.module.rank.Rank +import java.util.* + +open class Grant( + @JsonAdapter(Rank.RankReferenceSerializer::class) + var rank: Rank, + var issuedBy: UUID? = null, + val reason: String, + var expiresAt: Long? = null +) { + + val id: UUID = UUID.randomUUID() + val issuedAt: Long = System.currentTimeMillis() + + var removed: Boolean = false + var removedBy: UUID? = null + var removedAt: Long? = null + var removeReason: String? = null + + fun isActive(): Boolean { + return removedAt == null && (expiresAt == null || System.currentTimeMillis() < expiresAt!!) + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/grant/GrantQueryResult.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/grant/GrantQueryResult.kt new file mode 100644 index 0000000..5bb5694 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/grant/GrantQueryResult.kt @@ -0,0 +1,5 @@ +package com.minexd.api.module.profile.grant + +import com.minexd.api.module.profile.Profile + +class GrantQueryResult(val profile: Profile, val grant: Grant) \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/Punishment.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/Punishment.kt new file mode 100644 index 0000000..941e937 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/Punishment.kt @@ -0,0 +1,33 @@ +package com.minexd.api.module.profile.punishment + +import java.util.* + +class Punishment(val uuid: UUID = UUID.randomUUID(), val punishmentType: PunishmentType) { + + var reason: String = "" + var issuedBy: UUID? = null + val issuedAt: Long = System.currentTimeMillis() + var expiresAt: Long? = null + + var pardoned: Boolean = false + var pardonReason: String? = null + var pardonedBy: UUID? = null + var pardonedAt: Long? = null + + fun isActive(): Boolean { + return !pardoned && (expiresAt == null || System.currentTimeMillis() < expiresAt!!) + } + + fun isPermanent(): Boolean { + return expiresAt == null + } + + fun getDuration(): Long { + return expiresAt!! - issuedAt + } + + fun getRemainingTime(): Long { + return expiresAt!! - System.currentTimeMillis() + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/PunishmentQueryResult.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/PunishmentQueryResult.kt new file mode 100644 index 0000000..003313a --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/PunishmentQueryResult.kt @@ -0,0 +1,5 @@ +package com.minexd.api.module.profile.punishment + +import com.minexd.api.module.profile.Profile + +data class PunishmentQueryResult(val profile: Profile, val punishment: Punishment) \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/PunishmentType.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/PunishmentType.kt new file mode 100644 index 0000000..daf9f52 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/punishment/PunishmentType.kt @@ -0,0 +1,10 @@ +package com.minexd.api.module.profile.punishment + +enum class PunishmentType(val context: String) { + + BLACKLIST("blacklisted"), + BAN("banned"), + MUTE("muted"), + WARN("warned") + +} diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/Setting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/Setting.kt new file mode 100644 index 0000000..f02bc2e --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/Setting.kt @@ -0,0 +1,15 @@ +package com.minexd.api.module.profile.setting + +import com.google.gson.JsonElement + +abstract class Setting { + + val cached: SettingOption = newDefaultOption() + + abstract fun newDefaultOption(): SettingOption + + abstract fun getOptions(): List> + + abstract fun deserializeOption(value: JsonElement): SettingOption + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingOption.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingOption.kt new file mode 100644 index 0000000..2ad124a --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingOption.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020. Joel Evans + * + * Use and or redistribution of compiled JAR file and or source code is permitted only if given + * explicit permission from original author: Joel Evans + */ + +package com.minexd.api.module.profile.setting + +import net.evilblock.cubed.serializers.impl.AbstractTypeSerializable +import java.lang.reflect.Type + +interface SettingOption : AbstractTypeSerializable { + + fun getValue(): T + + override fun getAbstractType(): Type { + return this::class.java + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingsMapSerializer.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingsMapSerializer.kt new file mode 100644 index 0000000..251ef6d --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingsMapSerializer.kt @@ -0,0 +1,31 @@ +package com.minexd.api.module.profile.setting + +import com.google.gson.* +import java.lang.reflect.Type +import java.util.concurrent.ConcurrentHashMap + +class SettingsMapSerializer : JsonSerializer, SettingOption<*>>>, JsonDeserializer, SettingOption<*>>> { + + override fun serialize(map: ConcurrentHashMap, SettingOption<*>>, type: Type, context: JsonSerializationContext): JsonElement { + return JsonObject().also { json -> + for ((key, value) in map) { + json.add(key.javaClass.simpleName, context.serialize(value.getValue())) + } + } + } + + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): ConcurrentHashMap, SettingOption<*>> { + return ConcurrentHashMap, SettingOption<*>>().also { map -> + for ((key, value) in json.asJsonObject.entrySet()) { + try { + val setting = SettingsRegistry.find(key) ?: continue + val settingValue = setting.deserializeOption(value) + map[setting] = settingValue + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingsRegistry.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingsRegistry.kt new file mode 100644 index 0000000..779725f --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/SettingsRegistry.kt @@ -0,0 +1,26 @@ +package com.minexd.api.module.profile.setting + +import com.minexd.api.module.profile.setting.impl.* +import java.util.concurrent.ConcurrentHashMap + +object SettingsRegistry { + + private val registered: ConcurrentHashMap> = ConcurrentHashMap>().also { map -> + listOf( + FriendRequestsSetting, + FriendsStreamSetting, + LobbyPlayerVisibilitySetting, + MessagingSoundsSetting, + PartyRequestsSetting, + PresenceVisibilitySetting, + PrivateMessagesSetting, + StaffReportsSetting, + StaffVanishSetting + ).forEach { map[it.javaClass.simpleName] = it } + } + + fun find(className: String): Setting<*>? { + return registered[className] + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/FriendRequestsSetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/FriendRequestsSetting.kt new file mode 100644 index 0000000..fb7ea5d --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/FriendRequestsSetting.kt @@ -0,0 +1,40 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object FriendRequestsSetting : Setting() { + + override fun newDefaultOption(): Option { + return Option(OptionValue.ALLOW) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + ALLOW, + DENY + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/FriendsStreamSetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/FriendsStreamSetting.kt new file mode 100644 index 0000000..2130466 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/FriendsStreamSetting.kt @@ -0,0 +1,40 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object FriendsStreamSetting : Setting() { + + override fun newDefaultOption(): Option { + return Option(OptionValue.SHOW) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + SHOW, + HIDE + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/LobbyPlayerVisibilitySetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/LobbyPlayerVisibilitySetting.kt new file mode 100644 index 0000000..c6efadb --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/LobbyPlayerVisibilitySetting.kt @@ -0,0 +1,41 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object LobbyPlayerVisibilitySetting : Setting() { + + override fun newDefaultOption(): Option { + return Option(OptionValue.EVERYONE) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + EVERYONE, + FRIENDS, + NOBODY + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/MessagingSoundsSetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/MessagingSoundsSetting.kt new file mode 100644 index 0000000..f958a27 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/MessagingSoundsSetting.kt @@ -0,0 +1,40 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object MessagingSoundsSetting : Setting() { + + override fun newDefaultOption(): Option { + return Option(OptionValue.PLAY_SOUND) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + PLAY_SOUND, + NO_SOUND + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PartyRequestsSetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PartyRequestsSetting.kt new file mode 100644 index 0000000..29f238d --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PartyRequestsSetting.kt @@ -0,0 +1,41 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object PartyRequestsSetting : Setting() { + + override fun newDefaultOption(): SettingOption { + return Option(OptionValue.EVERYONE) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + EVERYONE, + FRIENDS, + NOBODY + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PresenceVisibilitySetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PresenceVisibilitySetting.kt new file mode 100644 index 0000000..b096b0b --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PresenceVisibilitySetting.kt @@ -0,0 +1,41 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object PresenceVisibilitySetting : Setting() { + + override fun newDefaultOption(): Option { + return Option(OptionValue.EVERYONE) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + EVERYONE, + FRIENDS, + NOBODY + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PrivateMessagesSetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PrivateMessagesSetting.kt new file mode 100644 index 0000000..fe63d86 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/PrivateMessagesSetting.kt @@ -0,0 +1,41 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object PrivateMessagesSetting : Setting() { + + override fun newDefaultOption(): Option { + return Option(OptionValue.EVERYONE) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + EVERYONE, + FRIENDS, + NOBODY + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/StaffReportsSetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/StaffReportsSetting.kt new file mode 100644 index 0000000..b4f1587 --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/StaffReportsSetting.kt @@ -0,0 +1,40 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object StaffReportsSetting : Setting() { + + override fun newDefaultOption(): SettingOption { + return Option(OptionValue.SHOW) + } + + override fun getOptions(): List> { + return OptionValue.values().map { Option(it) } + } + + override fun deserializeOption(value: JsonElement): SettingOption { + return Option(OptionValue.valueOf(value.asString)) + } + + class Option(val option: OptionValue) : SettingOption { + override fun getValue(): OptionValue { + return option + } + + override fun equals(other: Any?): Boolean { + return other is Option && other.getValue() == getValue() + } + + override fun hashCode(): Int { + return option.hashCode() + } + } + + enum class OptionValue { + SHOW, + HIDE + } + +} \ No newline at end of file diff --git a/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/StaffVanishSetting.kt b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/StaffVanishSetting.kt new file mode 100644 index 0000000..dfb5daa --- /dev/null +++ b/CoreXD-API-main/CoreXD-API-main/src/main/kotlin/com/minexd/api/module/profile/setting/impl/StaffVanishSetting.kt @@ -0,0 +1,40 @@ +package com.minexd.api.module.profile.setting.impl + +import com.google.gson.JsonElement +import com.minexd.api.module.profile.setting.Setting +import com.minexd.api.module.profile.setting.SettingOption + +object StaffVanishSetting : Setting() { + + override fun newDefaultOption(): Option { + return Option(OptionValue.VISIBLE) + } + + override fun getOptions(): List