This commit is contained in:
Brandon 2023-05-03 15:27:43 +01:00
commit 7e17389465
3871 changed files with 372832 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1 @@
# CoreXD-API

View File

@ -0,0 +1 @@
["test"]

View File

@ -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/")
}

View File

@ -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":""
}

View File

@ -0,0 +1 @@
kotlin.code.style=official

185
CoreXD-API-main/CoreXD-API-main/gradlew vendored Normal file
View File

@ -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" "$@"

View File

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

View File

@ -0,0 +1,2 @@
rootProject.name = 'minexd-api'

View File

@ -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<String>) {
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<SettingOption<*>>())
}
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()
}
}
}

View File

@ -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 = ""
}

View File

@ -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<String> = 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 })
}
}
}

View File

@ -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<String> = ConcurrentLinkedQueue<String>()
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")
}
}
}
}
}

View File

@ -0,0 +1,8 @@
package com.minexd.api
object Constants {
const val COLOR_CHAR = '§'
const val CONSOLE_NAME = "${COLOR_CHAR}4${COLOR_CHAR}lConsole"
}

View File

@ -0,0 +1,7 @@
package com.minexd.api
class DynamicRoute() {
}

View File

@ -0,0 +1,7 @@
package com.minexd.api
interface Repository {
fun initialize()
}

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -0,0 +1,9 @@
package com.minexd.api.logging
import java.util.logging.Logger
class APILogger : Logger("API", null) {
}

View File

@ -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()))
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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<String, Tag>()
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<Tag> {
return tags.values
}
fun forget(tag: Tag) {
tags.remove(tag.name.lowercase())
}
}

View File

@ -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)
}

View File

@ -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<Tag>() {}.type
private lateinit var tagsCollection: MongoCollection<Document>
override fun initialize() {
tagsCollection = API.mongoDatabase.getCollection("chattags")
}
internal fun load(): Set<Tag> {
return mutableSetOf<Tag>().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
}
}

View File

@ -0,0 +1,10 @@
package com.minexd.api.module.chattag
/**
* @author Missionary (missionarymc@gmail.com)
* @since 7/20/2021
*/
enum class TagUpdate {
UPDATE,
DELETE
}

View File

@ -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<UUID, ClanMember> = hashMapOf()
/**
* Invitations sent to players to join this clan.
*/
internal val invites: MutableMap<UUID, ClanInvite> = 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]+")
}
}

View File

@ -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 ->
}
}

View File

@ -0,0 +1,60 @@
package com.minexd.api.module.clan
import java.util.*
import java.util.concurrent.ConcurrentHashMap
object ClanHandler {
private val clanById: MutableMap<UUID, Clan> = ConcurrentHashMap()
private val clanByName: MutableMap<String, Clan> = ConcurrentHashMap()
private val clanByPlayer: MutableMap<UUID, Clan> = 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)
}
}

View File

@ -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())

View File

@ -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("");
}
}

View File

@ -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<Clan>() {}.type
private lateinit var clansCollection: MongoCollection<Document>
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))
}
}

View File

@ -0,0 +1,9 @@
package com.minexd.api.module.clan.result
enum class CreateError {
NAME_INVALID,
NAME_TAKEN,
ALREADY_IN_CLAN,
}

View File

@ -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!!)
}
}

View File

@ -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()
}
}

View File

@ -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()
)
)
)
}
}

View File

@ -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!!)
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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 ->
}
}

View File

@ -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<UUID, Pair<List<Friend>, Long>> = ConcurrentHashMap()
fun cache(uuid: UUID, friends: List<Friend>) {
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)
}
}
}

View File

@ -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<Friendship>() {}.type
private lateinit var friendshipsCol: MongoCollection<Document>
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<Friendship> {
val query = Document("\$or", listOf(
Document("player1", player.toString()),
Document("player2", player.toString())
))
return arrayListOf<Friendship>().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<Friendship> {
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<Friendship>().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<Friendship> {
val query = Document("\$and", BasicBSONList().also { andList ->
andList.add(Document("player2", player.toString()))
andList.add(Document("type", FriendshipType.PENDING.name))
})
return arrayListOf<Friendship>().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<Friendship> {
val query = Document("\$and", BasicBSONList().also { andList ->
andList.add(Document("player1", player.toString()))
andList.add(Document("type", FriendshipType.PENDING.name))
})
return arrayListOf<Friendship>().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))
}
}

View File

@ -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<Friend> = 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<Friend> = 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<Friend> = 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
}
}
}
}

View File

@ -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)
}

View File

@ -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,
}

View File

@ -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<UUID> = 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
}
}
}

View File

@ -0,0 +1,8 @@
package com.minexd.api.module.friend.structure
enum class FriendshipType {
PENDING,
FRIENDS
}

View File

@ -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<LeaderboardEntry<*>> = arrayListOf()
abstract fun fetchEntries(): List<LeaderboardEntry<*>>
fun refresh() {
val newEntries = fetchEntries()
var position = 1
for (entry in newEntries) {
entry.position = position++
}
this.entries = newEntries
}
}

View File

@ -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<T>(
var position: Int,
val uuid: UUID,
val displayName: String,
val value: T
)

View File

@ -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")))
}
}

View File

@ -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<GameType, List<Leaderboard>> = 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<Leaderboard> {
return leaderboards[gameType] ?: emptyList()
}
}

View File

@ -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<LeaderboardEntry<*>> {
val entries = arrayListOf<LeaderboardEntry<Int>>()
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)
}
}

View File

@ -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<LeaderboardEntry<*>> {
val entries = arrayListOf<LeaderboardEntry<Int>>()
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)
}
}

View File

@ -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<LeaderboardEntry<*>> {
val entries = arrayListOf<LeaderboardEntry<Long>>()
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)
}
}

View File

@ -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<LeaderboardEntry<*>> {
val entries = arrayListOf<LeaderboardEntry<Int>>()
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)
}
}

View File

@ -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<LeaderboardEntry<*>> {
val entries = arrayListOf<LeaderboardEntry<Long>>()
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)
}
}

View File

@ -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,
}

View File

@ -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
}
}

View File

@ -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<UUID> {
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")
}
}
}

View File

@ -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<UUID> = hashSetOf()
internal val invites: MutableMap<UUID, PartyInvite> = 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)
}
}

View File

@ -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<Map<String, Any?>>() {}.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
}
}

View File

@ -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<Party> = ConcurrentHashMap.newKeySet()
private val partyById: MutableMap<UUID, Party> = ConcurrentHashMap()
private val partyByPlayer: MutableMap<UUID, Party> = 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)
}
}

View File

@ -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<Party>() {}.type
private lateinit var partyCollection: MongoCollection<Document>
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<Party> {
return arrayListOf<Party>().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()))
}
}

View File

@ -0,0 +1,9 @@
package com.minexd.api.module.party.result
enum class AcceptInviteError {
NO_INVITE,
ALREADY_IN_PARTY,
PARTY_FULL,
}

View File

@ -0,0 +1,7 @@
package com.minexd.api.module.party.result
enum class ChatError {
NO_PARTY,
}

View File

@ -0,0 +1,7 @@
package com.minexd.api.module.party.result
enum class CreateError {
ALREADY_IN_PARTY,
}

View File

@ -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,
}

View File

@ -0,0 +1,10 @@
package com.minexd.api.module.party.result
enum class DestroyInviteError {
NO_PARTY,
NOT_LEADER,
TARGET_NOT_INVITED,
TARGET_ALREADY_MEMBER,
}

View File

@ -0,0 +1,8 @@
package com.minexd.api.module.party.result
enum class DisbandError {
NO_PARTY,
NOT_LEADER
}

View File

@ -0,0 +1,9 @@
package com.minexd.api.module.party.result
enum class KickError {
NO_PARTY,
NOT_LEADER,
TARGET_NOT_MEMBER
}

View File

@ -0,0 +1,7 @@
package com.minexd.api.module.party.result
enum class LeaveError {
NO_PARTY
}

View File

@ -0,0 +1,8 @@
package com.minexd.api.module.party.result
enum class UpdateError {
NO_PARTY,
NO_CHANGES
}

View File

@ -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<Party>()
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())
))
}
}
}

View File

@ -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<UUID>()
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()
)
))
}
}
}
}
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,10 @@
package com.minexd.api.module.party.structure
enum class PartyPrivacy {
EVERYONE,
FRIENDS,
INVITED,
NOBODY
}

View File

@ -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<String> = hashSetOf()
val grants: ArrayList<Grant> = arrayListOf()
val permissions: ArrayList<String> = arrayListOf()
val punishments: MutableList<Punishment> = arrayListOf()
var votes: MutableList<VoteRecord> = 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<Setting<*>, SettingOption<*>> = ConcurrentHashMap()
fun getUsername(): String {
return API.uuidCache.name(uuid)
}
fun getColoredUsername(): String {
return getBestDisplayRank().getColor() + getUsername()
}
fun getCompoundedPermissions(): Set<String> {
val permissions = HashSet<String>()
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<Rank, Set<String>> {
val defaultRank = RankHandler.defaultRank
val map: HashMap<Rank, HashSet<String>> = 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<String>): List<Grant> {
val grants = arrayListOf<Grant>()
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 <T> getSetting(setting: Setting<T>): SettingOption<T> {
if (!settings.containsKey(setting)) {
settings[setting] = setting.newDefaultOption()
}
return settings[setting]!! as SettingOption<T>
}
}

View File

@ -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<UUID>()
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))
}
})
}
}
}
}

View File

@ -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<UUID, Profile> = ConcurrentHashMap()
val famous: MutableMap<Rank,List<UUID>> = ConcurrentHashMap()
var famousMembers: MutableSet<UUID> = 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<UUID>()
for (famousList in this.famous.values) {
for (famous in famousList) {
newMembersSet.add(famous)
}
}
this.famousMembers = newMembersSet
}
fun getLoadedProfiles(): Collection<Profile> {
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)
}
}

View File

@ -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<Profile>() {}.type
private lateinit var profilesCol: MongoCollection<Document>
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<UUID> {
val identities = profile.identities
if (identities.isEmpty()) {
return emptyList()
}
return arrayListOf<UUID>().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, Punishment>? {
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<GrantQueryResult> {
return arrayListOf<GrantQueryResult>().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<PunishmentQueryResult> {
return arrayListOf<PunishmentQueryResult>().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<Profile> {
return arrayListOf<Profile>().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)
}
}
}
}
}

View File

@ -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!!)
}
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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")
}

View File

@ -0,0 +1,15 @@
package com.minexd.api.module.profile.setting
import com.google.gson.JsonElement
abstract class Setting<T> {
val cached: SettingOption<T> = newDefaultOption()
abstract fun newDefaultOption(): SettingOption<T>
abstract fun getOptions(): List<SettingOption<T>>
abstract fun deserializeOption(value: JsonElement): SettingOption<T>
}

View File

@ -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<T> : AbstractTypeSerializable {
fun getValue(): T
override fun getAbstractType(): Type {
return this::class.java
}
}

View File

@ -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<ConcurrentHashMap<Setting<*>, SettingOption<*>>>, JsonDeserializer<ConcurrentHashMap<Setting<*>, SettingOption<*>>> {
override fun serialize(map: ConcurrentHashMap<Setting<*>, 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<Setting<*>, SettingOption<*>> {
return ConcurrentHashMap<Setting<*>, 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()
}
}
}
}
}

View File

@ -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<String, Setting<*>> = ConcurrentHashMap<String, Setting<*>>().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]
}
}

View File

@ -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<FriendRequestsSetting.OptionValue>() {
override fun newDefaultOption(): Option {
return Option(OptionValue.ALLOW)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<FriendsStreamSetting.OptionValue>() {
override fun newDefaultOption(): Option {
return Option(OptionValue.SHOW)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<LobbyPlayerVisibilitySetting.OptionValue>() {
override fun newDefaultOption(): Option {
return Option(OptionValue.EVERYONE)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<MessagingSoundsSetting.OptionValue>() {
override fun newDefaultOption(): Option {
return Option(OptionValue.PLAY_SOUND)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<PartyRequestsSetting.OptionValue>() {
override fun newDefaultOption(): SettingOption<OptionValue> {
return Option(OptionValue.EVERYONE)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<PresenceVisibilitySetting.OptionValue>() {
override fun newDefaultOption(): Option {
return Option(OptionValue.EVERYONE)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<PrivateMessagesSetting.OptionValue>() {
override fun newDefaultOption(): Option {
return Option(OptionValue.EVERYONE)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<StaffReportsSetting.OptionValue>() {
override fun newDefaultOption(): SettingOption<OptionValue> {
return Option(OptionValue.SHOW)
}
override fun getOptions(): List<SettingOption<OptionValue>> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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
}
}

View File

@ -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<StaffVanishSetting.OptionValue>() {
override fun newDefaultOption(): Option {
return Option(OptionValue.VISIBLE)
}
override fun getOptions(): List<Option> {
return OptionValue.values().map { Option(it) }
}
override fun deserializeOption(value: JsonElement): SettingOption<OptionValue> {
return Option(OptionValue.valueOf(value.asString))
}
class Option(val option: OptionValue) : SettingOption<OptionValue> {
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 {
VISIBLE,
HIDDEN
}
}

View File

@ -0,0 +1,11 @@
package com.minexd.api.module.profile.social
enum class SocialMediaType {
YOUTUBE,
TWITCH,
TWITTER,
INSTAGRAM,
DISCORD,
}

Some files were not shown because too many files have changed in this diff Show More