ggggggg
This commit is contained in:
commit
7e17389465
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
# CoreXD-API
|
|
@ -0,0 +1 @@
|
|||
["test"]
|
|
@ -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/")
|
||||
}
|
|
@ -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":""
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
kotlin.code.style=official
|
|
@ -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" "$@"
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
rootProject.name = 'minexd-api'
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.minexd.api
|
||||
|
||||
class APIConfig {
|
||||
|
||||
val awsAccessKeyID = "AKIAY55EPDLCSRD7LV4X"
|
||||
val awsSecretAccessKey = "KP5kHeve+k1VPTOa8nwZu3e032emq6QOoBB/Fkvc"
|
||||
|
||||
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 = ""
|
||||
|
||||
}
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.minexd.api
|
||||
|
||||
object Constants {
|
||||
|
||||
const val COLOR_CHAR = '§'
|
||||
const val CONSOLE_NAME = "${COLOR_CHAR}4${COLOR_CHAR}lConsole"
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.minexd.api
|
||||
|
||||
class DynamicRoute() {
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.minexd.api
|
||||
|
||||
interface Repository {
|
||||
|
||||
fun initialize()
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.minexd.api.logging
|
||||
|
||||
import java.util.logging.Logger
|
||||
|
||||
class APILogger : Logger("API", null) {
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.minexd.api.module.chattag
|
||||
|
||||
/**
|
||||
* @author Missionary (missionarymc@gmail.com)
|
||||
* @since 7/20/2021
|
||||
*/
|
||||
enum class TagUpdate {
|
||||
UPDATE,
|
||||
DELETE
|
||||
}
|
|
@ -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]+")
|
||||
}
|
||||
|
||||
}
|
|
@ -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 ->
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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())
|
|
@ -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("");
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.minexd.api.module.clan.result
|
||||
|
||||
enum class CreateError {
|
||||
|
||||
NAME_INVALID,
|
||||
NAME_TAKEN,
|
||||
ALREADY_IN_CLAN,
|
||||
|
||||
}
|
|
@ -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!!)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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!!)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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 ->
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.minexd.api.module.friend.structure
|
||||
|
||||
enum class FriendshipType {
|
||||
|
||||
PENDING,
|
||||
FRIENDS
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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")))
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class AcceptInviteError {
|
||||
|
||||
NO_INVITE,
|
||||
ALREADY_IN_PARTY,
|
||||
PARTY_FULL,
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class ChatError {
|
||||
|
||||
NO_PARTY,
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class CreateError {
|
||||
|
||||
ALREADY_IN_PARTY,
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class DestroyInviteError {
|
||||
|
||||
NO_PARTY,
|
||||
NOT_LEADER,
|
||||
TARGET_NOT_INVITED,
|
||||
TARGET_ALREADY_MEMBER,
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class DisbandError {
|
||||
|
||||
NO_PARTY,
|
||||
NOT_LEADER
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class KickError {
|
||||
|
||||
NO_PARTY,
|
||||
NOT_LEADER,
|
||||
TARGET_NOT_MEMBER
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class LeaveError {
|
||||
|
||||
NO_PARTY
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.minexd.api.module.party.result
|
||||
|
||||
enum class UpdateError {
|
||||
|
||||
NO_PARTY,
|
||||
NO_CHANGES
|
||||
|
||||
}
|
|
@ -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())
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.minexd.api.module.party.structure
|
||||
|
||||
enum class PartyPrivacy {
|
||||
|
||||
EVERYONE,
|
||||
FRIENDS,
|
||||
INVITED,
|
||||
NOBODY
|
||||
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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!!)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
|
@ -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")
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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]
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue