diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/artifacts/VkRconBot_jar.xml b/.idea/artifacts/VkRconBot_jar.xml new file mode 100644 index 0000000..c6c4ecf --- /dev/null +++ b/.idea/artifacts/VkRconBot_jar.xml @@ -0,0 +1,13 @@ + + + $PROJECT_DIR$/out/artifacts/VkRconBot_jar + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b4bdd59 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..942f3a2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..df543e3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..4d27ef0 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..0e65cea --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..82dbec8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index bf82ff0..0000000 Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index dc3affc..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c5f3f6b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "interactive" -} \ No newline at end of file diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 4394b07..8e4eadf 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -1,12 +1,36 @@ 4.0.0 - theoni.vkbot - vkbot - VkBot - 1.2 + com.mefrreex.vkbot + vkrconbot + VkRconBot + 1.4 + src/main/kotlin + src/test/kotlin + ${project.name}-${project.version} + + org.jetbrains.kotlin + kotlin-maven-plugin + 1.7.21 + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + maven-shade-plugin 2.1 @@ -19,27 +43,86 @@ - theoni.vkbot.Bot + com.mefrreex.vkbot.BootstrapKt + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + com.mefrreex.vkbot.BootstrapKt + + + + maven-compiler-plugin + + 17 + 17 + + + + + mavenCentral + https://repo1.maven.org/maven2/ + + - org.projectlombok - lombok - 1.18.20 - provided + org.jetbrains.kotlin + kotlin-test-junit5 + 1.7.21 + test + + + kotlin-test + org.jetbrains.kotlin + + + junit-jupiter-api + org.junit.jupiter + + + + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + test + + + junit-platform-engine + org.junit.platform + + + apiguardian-api + org.apiguardian + + + junit-jupiter-api + org.junit.jupiter + + - 17 - 17 + official UTF-8 + 17 diff --git a/mvnw b/mvnw deleted file mode 100644 index 095fed6..0000000 --- a/mvnw +++ /dev/null @@ -1,287 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.1.1 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - 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 - else - JAVACMD="`\\unset -f command; \\command -v java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - printf '%s' "$(cd "$basedir"; pwd)" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=$(find_maven_basedir "$(dirname $0)") -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $wrapperUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - QUIET="--quiet" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - QUIET="" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" - fi - [ $? -eq 0 ] || rm -f "$wrapperJarPath" - elif command -v curl > /dev/null; then - QUIET="--silent" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - QUIET="" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L - fi - [ $? -eq 0 ] || rm -f "$wrapperJarPath" - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=`cygpath --path --windows "$javaSource"` - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index cba1f04..0000000 --- a/mvnw.cmd +++ /dev/null @@ -1,187 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.1.1 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 198cda0..470bcf9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,63 +1,131 @@ - - - 4.0.0 - - theoni.vkbot - vkbot - 1.3 - jar - - VkBot - - - UTF-8 - 17 - 17 - - - - - org.projectlombok - lombok - 1.18.20 - provided - - - com.vk.api - sdk - 1.0.14 - - - org.yaml - snakeyaml - 1.21 - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.1 - - - package - - shade - - - - - theoni.vkbot.Bot - - - - - - - - + + + 4.0.0 + + vkrconbot + com.mefrreex.vkbot + 1.4 + jar + + VkRconBot + + + UTF-8 + official + 17 + + + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + + + org.jetbrains.kotlin + kotlin-test-junit5 + 1.7.21 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + 1.7.21 + + + com.vk.api + sdk + 1.0.14 + + + org.yaml + snakeyaml + 1.21 + + + + + ${project.name}-${project.version} + src/main/kotlin + src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + 1.7.21 + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.1 + + + package + + shade + + + + + com.mefrreex.vkbot.BootstrapKt + + + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + com.mefrreex.vkbot.BootstrapKt + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + \ No newline at end of file diff --git a/src/main/java/theoni/vkbot/Bot.java b/src/main/java/theoni/vkbot/Bot.java deleted file mode 100644 index 76e8800..0000000 --- a/src/main/java/theoni/vkbot/Bot.java +++ /dev/null @@ -1,15 +0,0 @@ -package theoni.vkbot; - -import theoni.vkbot.managers.ConfigManager; - -public class Bot { - - public static void main(String[] args) throws InterruptedException { - - ConfigManager.saveResource("config.yml"); - ConfigManager.saveResource("messages.yml"); - - new BotHandler().startBot(); - System.out.println(Messages.BOT_STARTED.getText()); - } -} diff --git a/src/main/java/theoni/vkbot/BotHandler.java b/src/main/java/theoni/vkbot/BotHandler.java deleted file mode 100644 index 38cc238..0000000 --- a/src/main/java/theoni/vkbot/BotHandler.java +++ /dev/null @@ -1,110 +0,0 @@ -package theoni.vkbot; - -import com.vk.api.sdk.client.actors.GroupActor; -import com.vk.api.sdk.exceptions.ApiException; -import com.vk.api.sdk.exceptions.ClientException; -import com.vk.api.sdk.objects.messages.*; -import com.vk.api.sdk.queries.messages.MessagesGetLongPollHistoryQuery; - -import theoni.vkbot.managers.BotManager; -import theoni.vkbot.managers.ConfigManager; -import theoni.vkbot.managers.RconManager; -import theoni.vkbot.utils.Keyboards; - -import java.io.File; -import java.util.List; - -public class BotHandler { - - private ConfigManager config; - private String commandPrefix; - - public BotHandler() { - this.config = new ConfigManager(new File("config.yml")); - this.commandPrefix = config.getString("command-prefix"); - } - - public void startBot() { - - Thread thread = new Thread(new Runnable() { - - public void run() { - - GroupActor actor = new GroupActor(config.getInt("groupId"), config.getString("token")); - BotManager manager = new BotManager(actor); - - Integer ts = manager.getTs(); - - while (true){ - try { - - MessagesGetLongPollHistoryQuery historyQuery = manager.getHistoryQuery(ts); - List messages = historyQuery.execute().getMessages().getItems(); - - if (!messages.isEmpty()){ - messages.forEach(message -> { - - String text = message.getText().toLowerCase(); - - if (text.equals("Начать")){ - manager.sendMessage(Messages.START.getText(), message, Keyboards.rconKeyboard()); - } - - else if (text.equals("/getid")) { - manager.sendMessage(String.format(Messages.USER_ID.getText(), message.getFromId()), message); - } - - else if (text.equals("rcon") || text.equals("/rcon")){ - if (config.getList("allowed-users").contains(message.getFromId())) { - if (config.getList("fast-commands").size() != 0) { - manager.sendMessage(Messages.RCON_WITH_COMMANDS.getText(), message, Keyboards.commandsKeyboard()); - } else { - manager.sendMessage(Messages.RCON.getText(), message, Keyboards.commandsKeyboard()); - } - } else { - manager.sendMessage(Messages.USER_NOT_WHITELISTED.getText(), message); - } - } - - else if (text.length() > 1) { - if (text.substring(0, 1).equals(config.getString("command-prefix")) || config.getString("command-prefix").equals("")) { - - if (!config.getList("allowed-users").contains(message.getFromId())) { - manager.sendMessage(Messages.USER_NOT_WHITELISTED.getText(), message); - return; - } - - if (config.getList("blocked-commands").contains(text.replace("/", "").replace(commandPrefix, ""))) { - manager.sendMessage(String.format(Messages.COMMAND_BLOCKED.getText(), text), message); - return; - } - - String response = RconManager.command(text.replace("/", "").replace(commandPrefix, "")); - if (response.equals("Connection error")) { - response = Messages.FAILED_TO_CONNECT.getText(); - } else if (response.equals("Authentication error")) { - response = Messages.FAILED_TO_AUTHENTICATE.getText(); - } else if (response.equals("")) { - response = Messages.RESPONSE_NULL.getText(); - } else { - response = String.format(Messages.COMMAND_SENDED.getText(), response.replaceAll("§", "")); - } - - manager.sendMessage(response, message); - } - } - }); - } - - ts = manager.getTs(); - Thread.sleep(500); - - } catch (InterruptedException | ClientException | ApiException e) { - e.printStackTrace(); - } - } - } - }); - thread.start(); - } -} diff --git a/src/main/java/theoni/vkbot/Messages.java b/src/main/java/theoni/vkbot/Messages.java deleted file mode 100644 index 27f0354..0000000 --- a/src/main/java/theoni/vkbot/Messages.java +++ /dev/null @@ -1,32 +0,0 @@ -package theoni.vkbot; - -import java.io.File; - -import lombok.Getter; -import theoni.vkbot.managers.ConfigManager; - -public enum Messages { - - BOT_STARTED("Бот успешно запущен."), - START(), - USER_ID(), - RCON(), - RCON_WITH_COMMANDS(), - USER_NOT_WHITELISTED(), - COMMAND_BLOCKED(), - FAILED_TO_CONNECT(), - FAILED_TO_AUTHENTICATE(), - COMMAND_SENDED(), - RESPONSE_NULL(); - - @Getter private String text; - - Messages(String text) { - this.text = text; - } - - Messages() { - this.text = new ConfigManager(new File("messages.yml")).getString(name()); - } - -} diff --git a/src/main/java/theoni/vkbot/managers/BotManager.java b/src/main/java/theoni/vkbot/managers/BotManager.java deleted file mode 100644 index 7dd1695..0000000 --- a/src/main/java/theoni/vkbot/managers/BotManager.java +++ /dev/null @@ -1,91 +0,0 @@ -package theoni.vkbot.managers; - -import java.util.Random; -import com.vk.api.sdk.client.VkApiClient; -import com.vk.api.sdk.client.actors.GroupActor; -import com.vk.api.sdk.exceptions.ApiException; -import com.vk.api.sdk.exceptions.ClientException; -import com.vk.api.sdk.httpclient.HttpTransportClient; -import com.vk.api.sdk.objects.messages.*; -import com.vk.api.sdk.queries.messages.MessagesGetLongPollHistoryQuery; - -public class BotManager { - - private VkApiClient vk; - private GroupActor actor; - private Random random; - - public BotManager(GroupActor actor) { - this.vk = new VkApiClient(new HttpTransportClient()); - this.actor = actor; - this.random = new Random(); - } - - public void sendMessage(String text, Message message, Keyboard keyboard) { - try { - int length = text.length(); - int max = 4000; - for (int i = 0; i < length; i += max) { - int endIndex = Math.min(i + max, length); - vk.messages().send(actor).message(text.substring(i, endIndex)).peerId(message.getPeerId()).randomId(random.nextInt(10000)).keyboard(keyboard).execute(); - } - } catch (ApiException | ClientException e) { - e.printStackTrace(); - } - } - - public void sendMessage(String text, Message message) { - try { - int length = text.length(); - int max = 4000; - for (int i = 0; i < length; i += max) { - int endIndex = Math.min(i + max, length); - vk.messages().send(actor).message(text.substring(i, endIndex)).peerId(message.getPeerId()).randomId(random.nextInt(10000)).execute(); - } - } catch (ApiException | ClientException e) { - e.printStackTrace(); - } - } - - public void sendMessage(String text, Integer id, Keyboard keyboard) { - try { - int length = text.length(); - int max = 4000; - for (int i = 0; i < length; i += max) { - int endIndex = Math.min(i + max, length); - vk.messages().send(actor).message(text.substring(i, endIndex)).peerId(id).randomId(random.nextInt(10000)).keyboard(keyboard).execute(); - } - } catch (ApiException | ClientException e) { - e.printStackTrace(); - } - } - - public void sendMessage(String text, Integer id) { - try { - int length = text.length(); - int max = 4000; - for (int i = 0; i < length; i += max) { - int endIndex = Math.min(i + max, length); - vk.messages().send(actor).message(text.substring(i, endIndex)).peerId(id).randomId(random.nextInt(10000)).execute(); - } - } catch (ApiException | ClientException e) { - e.printStackTrace(); - } - } - - public Integer getTs() { - try { - Integer ts = vk.messages().getLongPollServer(actor).execute().getTs(); - return ts; - } catch (ApiException | ClientException e) { - e.printStackTrace(); - } - return 0; - } - - public MessagesGetLongPollHistoryQuery getHistoryQuery(Integer ts) { - MessagesGetLongPollHistoryQuery historyQuery = vk.messages().getLongPollHistory(actor).ts(ts); - return historyQuery; - } - -} diff --git a/src/main/java/theoni/vkbot/managers/ConfigManager.java b/src/main/java/theoni/vkbot/managers/ConfigManager.java deleted file mode 100644 index 60e9bf6..0000000 --- a/src/main/java/theoni/vkbot/managers/ConfigManager.java +++ /dev/null @@ -1,81 +0,0 @@ -package theoni.vkbot.managers; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; - -import org.yaml.snakeyaml.Yaml; - -import theoni.vkbot.Bot; - -public class ConfigManager { - - private Yaml yaml; - private Map config; - - public ConfigManager(File configFile) { - try { - this.yaml = new Yaml(); - this.config = yaml.load(new FileInputStream(configFile)); - } catch(FileNotFoundException e) {} - } - - public String getString(String key) { - return (String) getValue(key); - } - - public int getInt(String key) { - return (int) getValue(key); - } - - public boolean getBoolean(String key) { - return (boolean) getValue(key); - } - - public List getList(String key) { - return (List) getValue(key); - } - - private Object getValue(String key) { - String[] keys = key.split("\\."); - Map value = config; - for (int i = 0; i < keys.length - 1; i++) { - value = (Map) value.get(keys[i]); - } - return value.get(keys[keys.length - 1]); - } - - public static void saveResource(String target) { - Path currentPath = Paths.get("").toAbsolutePath(); - String configPath = "/" + target; - File file = new File(currentPath.toString(), target); - - if (!file.exists()) { - try { - - InputStream inputStream = Bot.class.getResourceAsStream(configPath); - OutputStream outputStream = new FileOutputStream(file); - - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - - inputStream.close(); - outputStream.close(); - - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} \ No newline at end of file diff --git a/src/main/java/theoni/vkbot/managers/RconManager.java b/src/main/java/theoni/vkbot/managers/RconManager.java deleted file mode 100644 index 47f32ec..0000000 --- a/src/main/java/theoni/vkbot/managers/RconManager.java +++ /dev/null @@ -1,23 +0,0 @@ -package theoni.vkbot.managers; - -import java.io.File; -import java.io.IOException; -import net.kronos.rkon.core.Rcon; -import net.kronos.rkon.core.ex.AuthenticationException; - -public class RconManager { - - public static String command(String name) { - ConfigManager config = new ConfigManager(new File("config.yml")); - try { - Rcon rcon = new Rcon(config.getString("rcon.host"), config.getInt("rcon.port"), config.getString("rcon.password").getBytes()); - String result = rcon.command(name.getBytes("UTF-8")); - return result; - } catch (IOException e) { - return "Connection error"; - } catch (AuthenticationException e) { - return "Authentication error"; - } - } - -} diff --git a/src/main/java/theoni/vkbot/utils/Keyboards.java b/src/main/java/theoni/vkbot/utils/Keyboards.java deleted file mode 100644 index f2ce036..0000000 --- a/src/main/java/theoni/vkbot/utils/Keyboards.java +++ /dev/null @@ -1,55 +0,0 @@ -package theoni.vkbot.utils; - -import com.vk.api.sdk.objects.messages.Keyboard; -import com.vk.api.sdk.objects.messages.KeyboardButton; -import com.vk.api.sdk.objects.messages.KeyboardButtonAction; -import com.vk.api.sdk.objects.messages.KeyboardButtonColor; -import com.vk.api.sdk.objects.messages.TemplateActionTypeNames; - -import theoni.vkbot.managers.ConfigManager; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class Keyboards { - - public static Keyboard rconKeyboard() { - Keyboard keyboard = new Keyboard(); - - keyboard.setButtons(Arrays.asList( - Arrays.asList( - new KeyboardButton() - .setColor(KeyboardButtonColor.POSITIVE) - .setAction(new KeyboardButtonAction() - .setLabel("Rcon") - .setType(TemplateActionTypeNames.TEXT)) - ) - )); - keyboard.setInline(true); - - return keyboard; - } - - public static Keyboard commandsKeyboard() { - Keyboard keyboard = new Keyboard(); - ConfigManager config = new ConfigManager(new File("config.yml")); - - List list = new ArrayList<>(); - for (Object command : config.getList("fast-commands")) { - list.add( - new KeyboardButton() - .setColor(KeyboardButtonColor.POSITIVE) - .setAction(new KeyboardButtonAction() - .setLabel((config.getString("command-prefix").equals("") ? "/" : config.getString("command-prefix")) + command) - .setType(TemplateActionTypeNames.TEXT)) - ); - } - - keyboard.setButtons(Arrays.asList(list)); - keyboard.setInline(true); - - return keyboard; - } -} diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt new file mode 100644 index 0000000..73a0c21 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt @@ -0,0 +1,32 @@ +package com.mefrreex.vkbot + +import com.mefrreex.vkbot.config.ConfigManager +import com.mefrreex.vkbot.logger.Logger +import java.io.File + +val logger = Logger.getInstance() +val configManager = ConfigManager() + +var server: Server? = null + +class Bootstrap { + +} + +fun main() { + logger.info("Starting the bot...") + val bootstrap = Bootstrap(); + + val resources = listOf(File("config.yml"), File("allow_list.yml"), File("messages.yml")) + if (!resources.all { it.exists() }) { + logger.info("Saving resources.") + configManager.saveResource("allow_list.yml") + configManager.saveResource("messages.yml") + configManager.saveResource("config.yml") + } + + server = Server(bootstrap) + server!!.start() + + logger.info("Bot is started!") +} diff --git a/src/main/kotlin/com/mefrreex/vkbot/Server.kt b/src/main/kotlin/com/mefrreex/vkbot/Server.kt new file mode 100644 index 0000000..e75667a --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/Server.kt @@ -0,0 +1,45 @@ +package com.mefrreex.vkbot + +import com.mefrreex.vkbot.config.Config +import com.mefrreex.vkbot.config.defaults.Settings +import com.mefrreex.vkbot.handler.EventHandler +import com.vk.api.sdk.client.VkApiClient +import com.vk.api.sdk.client.actors.GroupActor +import com.vk.api.sdk.httpclient.HttpTransportClient + +class Server(val bootstrap: Bootstrap) { + + private val config = Settings.getConfig() + private val groupId = Settings.GROUP_ID.int() + private val accessToken = Settings.ACCESS_TOKEN.string() + + fun start() { + + val httpClient: HttpTransportClient = HttpTransportClient.getInstance() + val vk = VkApiClient(httpClient) + + val groupActor = GroupActor(groupId, accessToken) + + vk.groupsLongPoll().setLongPollSettings(groupActor, groupId) + .enabled(true) + .messageNew(true) + .execute() + + val handler = EventHandler(vk, groupActor, 1, this) + handler.run(); + + } + + fun getConfig(): Config { + return config + } + + fun getGroupId(): Int { + return groupId + } + + fun getAccessToken(): String { + return accessToken + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/Config.kt b/src/main/kotlin/com/mefrreex/vkbot/config/Config.kt new file mode 100644 index 0000000..c499056 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/config/Config.kt @@ -0,0 +1,95 @@ +package com.mefrreex.vkbot.config + +import org.yaml.snakeyaml.DumperOptions +import org.yaml.snakeyaml.Yaml +import java.io.* +import java.nio.charset.StandardCharsets + + +class Config(private val file: File) { + + constructor(fileName: String) : this(File(fileName)) + + private var yaml: Yaml? = null + private var config: MutableMap? = null + + init { + if (!file.exists()) { + file.createNewFile() + } + val dumperOptions = DumperOptions(); + dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK; + this.yaml = Yaml(dumperOptions) + this.config = yaml!!.load(FileInputStream(file)) as MutableMap? + } + + fun getString(key: String): String? { + return getValue(key) as String? + } + + fun getInt(key: String): Int { + return getValue(key) as Int + } + + fun getBoolean(key: String): Boolean { + return getValue(key) as Boolean + } + + fun getList(key: String): List<*>? { + return getValue(key) as List<*>? + } + + fun getAll(): Map? { + return config + } + + fun getValue(key: String): Any? { + val keys = key.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + var value = config as Map? + for (i in 0 until keys.size - 1) { + value = value!![keys[i]] as Map? + } + return value!![keys[keys.size - 1]] + } + + fun set(key: String, value: Any) { + val keys = key.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + var current = config + for (i in 0 until keys.size - 1) { + val next = current!![keys[i]] as MutableMap? + if (next == null) { + val newMap = mutableMapOf() + current[keys[i]] = newMap + current = newMap + } else { + current = next + } + } + current!![keys[keys.size - 1]] = value + } + + fun save() { + val content = yaml!!.dump(config) + val lines = mutableListOf() + + // Считываем старое содержимое файла + if (file.exists()) { + file.forEachLine { line -> + lines.add(line) + } + } + + // Обновляем содержимое файла + val writer = file.bufferedWriter() + for (line in lines) { + if (line.trim().startsWith("#")) { + writer.write(line) + writer.newLine() + } + } + writer.write(content) + writer.flush() + writer.close() + } + +} diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt b/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt new file mode 100644 index 0000000..8ba298e --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt @@ -0,0 +1,33 @@ +package com.mefrreex.vkbot.config + +import java.nio.file.Paths.* +import java.io.* + + +class ConfigManager { + + fun saveResource(target: String) { + + val currentPath = get("").toAbsolutePath() + val configPath = "/$target" + val file = File(currentPath.toString(), target) + + if (!file.exists()) { + try { + val inputStream: InputStream = javaClass.getResourceAsStream(configPath) + val outputStream: OutputStream = FileOutputStream(file) + val buffer = ByteArray(1024) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + outputStream.write(buffer, 0, bytesRead) + } + inputStream.close() + outputStream.close() + + } catch (e: IOException) { + e.printStackTrace() + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt new file mode 100644 index 0000000..6d65140 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt @@ -0,0 +1,26 @@ +package com.mefrreex.vkbot.config.defaults + +import com.mefrreex.vkbot.config.Config + +enum class AllowList(private val key: String) { + + ALLOWED_USERS("allowed-users"); + + companion object { + + private val CONFIG = Config("allow_list.yml") + + fun getConfig(): Config { + return CONFIG + } + } + + private fun getValue(key: String): Any? { + return CONFIG.getList(key) + } + + @Suppress("UNCHECKED_CAST") + fun get(): List { + return this.getValue(key) as List + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt new file mode 100644 index 0000000..9db696f --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt @@ -0,0 +1,34 @@ +package com.mefrreex.vkbot.config.defaults + +import com.mefrreex.vkbot.config.Config + +enum class Messages(private val key: String) { + + USER_ID("user_id"), + USER_NOT_WHITELISTED("user_not_whitelisted"), + + START("start"), + + RCON("rcon"), + RCON_WITH_COMMANDS("rcon_with_commands"), + + COMMAND_BLOCKED("command_blocked"), + FAILED_TO_CONNECT("failed_to_connect"), + FAILED_TO_AUTHENTICATE("failed_to_authenticate"), + + COMMAND_SENDED("command_sended"), + RESPONSE_NULL("response_null"); + + companion object { + + private val CONFIG = Config("messages.yml") + + fun getConfig(): Config { + return CONFIG + } + } + + fun get(): String { + return CONFIG.getString(key)!! + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt new file mode 100644 index 0000000..4fcf515 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt @@ -0,0 +1,46 @@ +package com.mefrreex.vkbot.config.defaults + +import com.mefrreex.vkbot.config.Config + +enum class Settings(private val key: String) { + + GROUP_ID("vk.groupId"), + ACCESS_TOKEN("vk.accessToken"), + + RCON_HOST("rcon.host"), + RCON_PORT("rcon.port"), + RCON_PASSWORD("rcon.password"), + + COMMAND_PREFIX("commands.prefix"), + FAST_COMMANDS("commands.fast-commands"), + BLOCKED_COMMANDS("commands.blocked-commands"); + + companion object { + + private val CONFIG = Config("config.yml") + + fun getConfig(): Config { + return CONFIG + } + } + + private fun getValue(key: String): Any? { + return CONFIG.getValue(key) + } + + fun string(): String { + return this.getValue(key) as String + } + + fun int(): Int { + return this.getValue(key) as Int + } + + fun list(): List<*> { + return this.getValue(key) as List<*> + } + + fun get(): Any? { + return this.getValue(key) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt new file mode 100644 index 0000000..f86de34 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt @@ -0,0 +1,144 @@ +package com.mefrreex.vkbot.handler + +import com.mefrreex.vkbot.Server +import com.mefrreex.vkbot.config.defaults.AllowList +import com.mefrreex.vkbot.config.defaults.Messages +import com.mefrreex.vkbot.config.defaults.Settings +import com.mefrreex.vkbot.logger.Logger +import com.mefrreex.vkbot.objects.MessageFix +import com.mefrreex.vkbot.rcon.Rcon +import com.mefrreex.vkbot.utils.Keyboards +import com.mefrreex.vkbot.utils.TextFormat +import com.vk.api.sdk.client.VkApiClient +import com.vk.api.sdk.client.actors.GroupActor +import com.vk.api.sdk.events.Events +import com.vk.api.sdk.events.longpoll.GroupLongPollApi +import com.vk.api.sdk.objects.callback.messages.CallbackMessage +import com.vk.api.sdk.objects.messages.Message +import net.kronos.rkon.core.ex.AuthenticationException +import java.io.IOException +import kotlin.random.Random + +class EventHandler( + + private val client: VkApiClient, + private val actor: GroupActor, + private val waitTime: Int, + private val server: Server + +) : GroupLongPollApi(client, actor, waitTime) { + + val logger = Logger.getInstance() + + fun messageNewFix(groupId: Int, message: Message) { + + val text = message.text.lowercase() + + when { + + text == "начать" || text == "start" || text == "rcon" -> { + if (!AllowList.ALLOWED_USERS.get().contains(message.fromId)) { + client.messages().send(actor) + .message(Messages.USER_NOT_WHITELISTED.get()) + .keyboard(Keyboards.commandsKeyboard()) + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + return + } + client.messages().send(actor) + .message(Messages.START.get()) + .keyboard(Keyboards.commandsKeyboard()) + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + } + + text.startsWith(Settings.COMMAND_PREFIX.string()) -> { + + val command = text.substring(Settings.COMMAND_PREFIX.string().length, text.length) + val commandName = command.split("\\s+".toRegex())[0].removePrefix("/") + + when(command) { + + "getid" -> { + client.messages().send(actor) + .message(Messages.USER_ID.get().format(message.fromId)) + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + } + + else -> { + + if (!AllowList.ALLOWED_USERS.get().contains(message.fromId)) { + return + } + + if (Settings.BLOCKED_COMMANDS.list().contains(commandName)) { + client.messages().send(actor) + .message(Messages.COMMAND_BLOCKED.get().format(command)) + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + logger.warn("User ${message.fromId} tried to use the blocked command ${TextFormat.RED}`$command`${TextFormat.RESET}.") + return + } + + try { + Rcon.command(command, response = { + if (it.length < 4000) { + client.messages().send(actor) + .message( + if (it != "") { + Messages.COMMAND_SENDED.get().format(it) + } else { + Messages.RESPONSE_NULL.get() + } + ) + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + } else { + client.messages().send(actor) + .message("The command's response is too long.") + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + } + logger.info("Used ${TextFormat.YELLOW}`$command`${TextFormat.RESET} command by user ${message.fromId}") + }) + + } catch (e: IOException) { + client.messages().send(actor) + .message(Messages.FAILED_TO_CONNECT.get()) + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + logger.error("Unhandled exception when trying to connect to RCON:", e) + + } catch (e: AuthenticationException) { + client.messages().send(actor) + .message(Messages.FAILED_TO_AUTHENTICATE.get()) + .peerId(message.peerId) + .randomId(Random.nextInt(10000)) + .execute() + logger.error("Unhandled exception on RCON authentication attempt:", e) + } + } + } + } + } + } + + override fun parse(message: CallbackMessage): String? { + if (message.type == Events.MESSAGE_NEW) { + gson.fromJson(message.getObject(), MessageFix::class.java).message?.let { + this.messageNewFix(message.groupId, it) + } + return "OK" + } + return super.parse(message) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt b/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt new file mode 100644 index 0000000..f07ef07 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt @@ -0,0 +1,50 @@ +package com.mefrreex.vkbot.logger + +import com.mefrreex.vkbot.utils.TextFormat +import java.text.SimpleDateFormat +import java.util.* + + +class Logger { + + private val DATE_FORMAT = SimpleDateFormat("HH:mm:ss.SSS") + + fun info(message: String) { + log(TextFormat.BLUE + "INFO" + TextFormat.RESET, message) + } + + fun warn(message: String) { + log(TextFormat.RED + "WARN" + TextFormat.RESET, message) + } + + fun error(message: String) { + log(TextFormat.RED + "ERROR" + TextFormat.RESET, message) + } + + fun error(message: String, throwable: Throwable) { + log(TextFormat.RED + "ERROR" + TextFormat.RESET, message, throwable) + } + + fun debug(message: String) { + log(TextFormat.BLUE + "DEBUG" + TextFormat.RESET, message) + } + + private fun log(level: String, message: String) { + log(level, message, null) + } + + private fun log(level: String, message: String, throwable: Throwable?) { + val date = DATE_FORMAT.format(Date()) + println("${TextFormat.CYAN}$date ${TextFormat.WHITE}[$level] $message") + throwable?.printStackTrace() + } + + companion object { + + private val instance = Logger() + + fun getInstance(): Logger { + return instance + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt b/src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt new file mode 100644 index 0000000..cffce71 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt @@ -0,0 +1,11 @@ +package com.mefrreex.vkbot.objects + +import com.google.gson.annotations.SerializedName +import com.vk.api.sdk.objects.messages.Message + +class MessageFix { + + @SerializedName("message") + val message: Message? = null + +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt new file mode 100644 index 0000000..a0c98a2 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt @@ -0,0 +1,24 @@ +package com.mefrreex.vkbot.rcon + +import com.mefrreex.vkbot.config.defaults.Settings +import net.kronos.rkon.core.ex.AuthenticationException +import net.kronos.rkon.core.Rcon as RconManager +import kotlin.jvm.Throws +import java.io.IOException +import java.util.function.Consumer + +class Rcon { + + companion object { + + fun command(name: String, response: Consumer) { + response.accept(command(name)); + } + + @Throws(IOException::class, AuthenticationException::class) + fun command(name: String): String { + val rcon = RconManager(Settings.RCON_HOST.string(), Settings.RCON_PORT.int(), Settings.RCON_PASSWORD.string().toByteArray()) + return rcon.command(name.toByteArray(charset("UTF-8"))); + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt new file mode 100644 index 0000000..3dc2ee0 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt @@ -0,0 +1,29 @@ +package com.mefrreex.vkbot.utils + +import com.mefrreex.vkbot.config.defaults.Settings +import com.vk.api.sdk.objects.messages.* +import java.util.* + +object Keyboards { + + @Suppress("UNCHECKED_CAST") + fun commandsKeyboard(): Keyboard { + val keyboard = Keyboard() + + val list: MutableList = ArrayList() + for (command in Settings.FAST_COMMANDS.list() as List) { + list.add( + KeyboardButton() + .setColor(KeyboardButtonColor.POSITIVE) + .setAction( + KeyboardButtonAction() + .setLabel(Settings.COMMAND_PREFIX.string() + command) + .setType(TemplateActionTypeNames.TEXT) + ) + ) + } + keyboard.buttons = listOf>(list) + keyboard.inline = true + return keyboard + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt new file mode 100644 index 0000000..6ec71c6 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt @@ -0,0 +1,27 @@ +package com.mefrreex.vkbot.utils + +object TextFormat { + + val BLACK = "\u001b[0;30m" + val RED = "\u001b[0;31m" + val GREEN = "\u001b[0;92m" + val YELLOW = "\u001b[0;33m" + val BLUE = "\u001b[0;34m" + val MAGENTA = "\u001b[0;35m" + val CYAN = "\u001b[0;36m" + val WHITE = "\u001b[0;37m" + + val BLACK_BACKGROUND = "\u001b[40m" + val RED_BACKGROUND = "\u001b[41m" + val GREEN_BACKGROUND = "\u001b[42m" + val YELLOW_BACKGROUND = "\u001b[43m" + val BLUE_BACKGROUND = "\u001b[44m" + val MAGENTA_BACKGROUND = "\u001b[45m" + val CYAN_BACKGROUND = "\u001b[46m" + val WHITE_BACKGROUND = "\u001b[47m" + + val RESET = "\u001b[0m" + val BOLD = "\u001b[1m" + val UNDERLINE = "\u001b[4m" + val REVERSED = "\u001b[7m" +} \ No newline at end of file diff --git a/src/main/java/net/kronos/rkon/core/Rcon.java b/src/main/kotlin/net/kronos/rkon/core/Rcon.java similarity index 95% rename from src/main/java/net/kronos/rkon/core/Rcon.java rename to src/main/kotlin/net/kronos/rkon/core/Rcon.java index b150156..3d32ea5 100644 --- a/src/main/java/net/kronos/rkon/core/Rcon.java +++ b/src/main/kotlin/net/kronos/rkon/core/Rcon.java @@ -1,133 +1,133 @@ -package net.kronos.rkon.core; - -import java.io.IOException; -import java.net.Socket; -import java.nio.charset.Charset; -import java.util.Random; - -import net.kronos.rkon.core.ex.AuthenticationException; - -public class Rcon { - - private final Object sync = new Object(); - private final Random rand = new Random(); - - private int requestId; - private Socket socket; - - private Charset charset; - - /** - * Create, connect and authenticate a new Rcon object - * - * @param host Rcon server address - * @param port Rcon server port - * @param password Rcon server password - * - * @throws IOException - * @throws AuthenticationException - */ - public Rcon(String host, int port, byte[] password) throws IOException, AuthenticationException { - // Default charset is utf8 - this.charset = Charset.forName("UTF-8"); - - // Connect to host - this.connect(host, port, password); - } - - /** - * Connect to a rcon server - * - * @param host Rcon server address - * @param port Rcon server port - * @param password Rcon server password - * - * @throws IOException - * @throws AuthenticationException - */ - public void connect(String host, int port, byte[] password) throws IOException, AuthenticationException { - if(host == null || host.trim().isEmpty()) { - throw new IllegalArgumentException("Host can't be null or empty"); - } - - if(port < 1 || port > 65535) { - throw new IllegalArgumentException("Port is out of range"); - } - - // Connect to the rcon server - synchronized(sync) { - // New random request id - this.requestId = rand.nextInt(); - - // We can't reuse a socket, so we need a new one - this.socket = new Socket(host, port); - } - - // Send the auth packet - RconPacket res = this.send(RconPacket.SERVERDATA_AUTH, password); - - // Auth failed - if(res.getRequestId() == -1) { - throw new AuthenticationException("Password rejected by server"); - } - } - - /** - * Disconnect from the current server - * - * @throws IOException - */ - public void disconnect() throws IOException { - synchronized(sync) { - this.socket.close(); - } - } - - /** - * Send a command to the server - * - * @param payload The command to send - * @return The payload of the response - * - * @throws IOException - */ - public String command(String payload) throws IOException { - if(payload == null || payload.trim().isEmpty()) { - throw new IllegalArgumentException("Payload can't be null or empty"); - } - - RconPacket response = this.send(RconPacket.SERVERDATA_EXECCOMMAND, payload.getBytes()); - - return new String(response.getPayload(), this.getCharset()); - } - - public String command(byte[] payload) throws IOException { - - RconPacket response = this.send(RconPacket.SERVERDATA_EXECCOMMAND, payload); - - return new String(response.getPayload(), this.getCharset()); - } - - private RconPacket send(int type, byte[] payload) throws IOException { - synchronized(sync) { - return RconPacket.send(this, type, payload); - } - } - - public int getRequestId() { - return requestId; - } - - public Socket getSocket() { - return socket; - } - - public Charset getCharset() { - return charset; - } - - public void setCharset(Charset charset) { - this.charset = charset; - } - -} +package net.kronos.rkon.core; + +import java.io.IOException; +import java.net.Socket; +import java.nio.charset.Charset; +import java.util.Random; + +import net.kronos.rkon.core.ex.AuthenticationException; + +public class Rcon { + + private final Object sync = new Object(); + private final Random rand = new Random(); + + private int requestId; + private Socket socket; + + private Charset charset; + + /** + * Create, connect and authenticate a new Rcon object + * + * @param host Rcon server address + * @param port Rcon server port + * @param password Rcon server password + * + * @throws IOException + * @throws AuthenticationException + */ + public Rcon(String host, int port, byte[] password) throws IOException, AuthenticationException { + // Default charset is utf8 + this.charset = Charset.forName("UTF-8"); + + // Connect to host + this.connect(host, port, password); + } + + /** + * Connect to a rcon server + * + * @param host Rcon server address + * @param port Rcon server port + * @param password Rcon server password + * + * @throws IOException + * @throws AuthenticationException + */ + public void connect(String host, int port, byte[] password) throws IOException, AuthenticationException { + if(host == null || host.trim().isEmpty()) { + throw new IllegalArgumentException("Host can't be null or empty"); + } + + if(port < 1 || port > 65535) { + throw new IllegalArgumentException("Port is out of range"); + } + + // Connect to the rcon server + synchronized(sync) { + // New random request id + this.requestId = rand.nextInt(); + + // We can't reuse a socket, so we need a new one + this.socket = new Socket(host, port); + } + + // Send the auth packet + RconPacket res = this.send(RconPacket.SERVERDATA_AUTH, password); + + // Auth failed + if(res.getRequestId() == -1) { + throw new AuthenticationException("Password rejected by server"); + } + } + + /** + * Disconnect from the current server + * + * @throws IOException + */ + public void disconnect() throws IOException { + synchronized(sync) { + this.socket.close(); + } + } + + /** + * Send a command to the server + * + * @param payload The command to send + * @return The payload of the response + * + * @throws IOException + */ + public String command(String payload) throws IOException { + if(payload == null || payload.trim().isEmpty()) { + throw new IllegalArgumentException("Payload can't be null or empty"); + } + + RconPacket response = this.send(RconPacket.SERVERDATA_EXECCOMMAND, payload.getBytes()); + + return new String(response.getPayload(), this.getCharset()); + } + + public String command(byte[] payload) throws IOException { + + RconPacket response = this.send(RconPacket.SERVERDATA_EXECCOMMAND, payload); + + return new String(response.getPayload(), this.getCharset()); + } + + private RconPacket send(int type, byte[] payload) throws IOException { + synchronized(sync) { + return RconPacket.send(this, type, payload); + } + } + + public int getRequestId() { + return requestId; + } + + public Socket getSocket() { + return socket; + } + + public Charset getCharset() { + return charset; + } + + public void setCharset(Charset charset) { + this.charset = charset; + } + +} diff --git a/src/main/java/net/kronos/rkon/core/RconPacket.java b/src/main/kotlin/net/kronos/rkon/core/RconPacket.java similarity index 96% rename from src/main/java/net/kronos/rkon/core/RconPacket.java rename to src/main/kotlin/net/kronos/rkon/core/RconPacket.java index fdd0e87..b2deec2 100644 --- a/src/main/java/net/kronos/rkon/core/RconPacket.java +++ b/src/main/kotlin/net/kronos/rkon/core/RconPacket.java @@ -1,152 +1,152 @@ -package net.kronos.rkon.core; - -import java.io.DataInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.SocketException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import net.kronos.rkon.core.ex.MalformedPacketException; - -public class RconPacket { - - public static final int SERVERDATA_EXECCOMMAND = 2; - public static final int SERVERDATA_AUTH = 3; - - private int requestId; - private int type; - private byte[] payload; - - private RconPacket(int requestId, int type, byte[] payload) { - this.requestId = requestId; - this.type = type; - this.payload = payload; - } - - public int getRequestId() { - return requestId; - } - - public int getType() { - return type; - } - - public byte[] getPayload() { - return payload; - } - - /** - * Send a Rcon packet and fetch the response - * - * @param rcon Rcon instance - * @param type The packet type - * @param payload The payload (password, command, etc.) - * @return A RconPacket object containing the response - * - * @throws IOException - * @throws MalformedPacketException - */ - protected static RconPacket send(Rcon rcon, int type, byte[] payload) throws IOException { - try { - RconPacket.write(rcon.getSocket().getOutputStream(), rcon.getRequestId(), type, payload); - } - catch(SocketException se) { - // Close the socket if something happens - rcon.getSocket().close(); - - // Rethrow the exception - throw se; - } - - return RconPacket.read(rcon.getSocket().getInputStream()); - } - - /** - * Write a rcon packet on an outputstream - * - * @param out The OutputStream to write on - * @param requestId The request id - * @param type The packet type - * @param payload The payload - * - * @throws IOException - */ - private static void write(OutputStream out, int requestId, int type, byte[] payload) throws IOException { - int bodyLength = RconPacket.getBodyLength(payload.length); - int packetLength = RconPacket.getPacketLength(bodyLength); - - ByteBuffer buffer = ByteBuffer.allocate(packetLength); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - buffer.putInt(bodyLength); - buffer.putInt(requestId); - buffer.putInt(type); - buffer.put(payload); - - // Null bytes terminators - buffer.put((byte)0); - buffer.put((byte)0); - - // Woosh! - out.write(buffer.array()); - out.flush(); - } - - /** - * Read an incoming rcon packet - * - * @param in The InputStream to read on - * @return The read RconPacket - * - * @throws IOException - * @throws MalformedPacketException - */ - private static RconPacket read(InputStream in) throws IOException { - // Header is 3 4-bytes ints - byte[] header = new byte[4 * 3]; - - // Read the 3 ints - in.read(header); - - try { - // Use a bytebuffer in little endian to read the first 3 ints - ByteBuffer buffer = ByteBuffer.wrap(header); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - int length = buffer.getInt(); - int requestId = buffer.getInt(); - int type = buffer.getInt(); - - // Payload size can be computed now that we have its length - byte[] payload = new byte[length - 4 - 4 - 2]; - - DataInputStream dis = new DataInputStream(in); - - // Read the full payload - dis.readFully(payload); - - // Read the null bytes - dis.read(new byte[2]); - - return new RconPacket(requestId, type, payload); - } - catch(BufferUnderflowException | EOFException e) { - throw new MalformedPacketException("Cannot read the whole packet"); - } - } - - private static int getPacketLength(int bodyLength) { - // 4 bytes for length + x bytes for body length - return 4 + bodyLength; - } - - private static int getBodyLength(int payloadLength) { - // 4 bytes for requestId, 4 bytes for type, x bytes for payload, 2 bytes for two null bytes - return 4 + 4 + payloadLength + 2; - } - -} +package net.kronos.rkon.core; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import net.kronos.rkon.core.ex.MalformedPacketException; + +public class RconPacket { + + public static final int SERVERDATA_EXECCOMMAND = 2; + public static final int SERVERDATA_AUTH = 3; + + private int requestId; + private int type; + private byte[] payload; + + private RconPacket(int requestId, int type, byte[] payload) { + this.requestId = requestId; + this.type = type; + this.payload = payload; + } + + public int getRequestId() { + return requestId; + } + + public int getType() { + return type; + } + + public byte[] getPayload() { + return payload; + } + + /** + * Send a Rcon packet and fetch the response + * + * @param rcon Rcon instance + * @param type The packet type + * @param payload The payload (password, command, etc.) + * @return A RconPacket object containing the response + * + * @throws IOException + * @throws MalformedPacketException + */ + protected static RconPacket send(Rcon rcon, int type, byte[] payload) throws IOException { + try { + RconPacket.write(rcon.getSocket().getOutputStream(), rcon.getRequestId(), type, payload); + } + catch(SocketException se) { + // Close the socket if something happens + rcon.getSocket().close(); + + // Rethrow the exception + throw se; + } + + return RconPacket.read(rcon.getSocket().getInputStream()); + } + + /** + * Write a rcon packet on an outputstream + * + * @param out The OutputStream to write on + * @param requestId The request id + * @param type The packet type + * @param payload The payload + * + * @throws IOException + */ + private static void write(OutputStream out, int requestId, int type, byte[] payload) throws IOException { + int bodyLength = RconPacket.getBodyLength(payload.length); + int packetLength = RconPacket.getPacketLength(bodyLength); + + ByteBuffer buffer = ByteBuffer.allocate(packetLength); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + buffer.putInt(bodyLength); + buffer.putInt(requestId); + buffer.putInt(type); + buffer.put(payload); + + // Null bytes terminators + buffer.put((byte)0); + buffer.put((byte)0); + + // Woosh! + out.write(buffer.array()); + out.flush(); + } + + /** + * Read an incoming rcon packet + * + * @param in The InputStream to read on + * @return The read RconPacket + * + * @throws IOException + * @throws MalformedPacketException + */ + private static RconPacket read(InputStream in) throws IOException { + // Header is 3 4-bytes ints + byte[] header = new byte[4 * 3]; + + // Read the 3 ints + in.read(header); + + try { + // Use a bytebuffer in little endian to read the first 3 ints + ByteBuffer buffer = ByteBuffer.wrap(header); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + int length = buffer.getInt(); + int requestId = buffer.getInt(); + int type = buffer.getInt(); + + // Payload size can be computed now that we have its length + byte[] payload = new byte[length - 4 - 4 - 2]; + + DataInputStream dis = new DataInputStream(in); + + // Read the full payload + dis.readFully(payload); + + // Read the null bytes + dis.read(new byte[2]); + + return new RconPacket(requestId, type, payload); + } + catch(BufferUnderflowException | EOFException e) { + throw new MalformedPacketException("Cannot read the whole packet"); + } + } + + private static int getPacketLength(int bodyLength) { + // 4 bytes for length + x bytes for body length + return 4 + bodyLength; + } + + private static int getBodyLength(int payloadLength) { + // 4 bytes for requestId, 4 bytes for type, x bytes for payload, 2 bytes for two null bytes + return 4 + 4 + payloadLength + 2; + } + +} diff --git a/src/main/java/net/kronos/rkon/core/ex/AuthenticationException.java b/src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java similarity index 94% rename from src/main/java/net/kronos/rkon/core/ex/AuthenticationException.java rename to src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java index 9b31de8..45ef113 100644 --- a/src/main/java/net/kronos/rkon/core/ex/AuthenticationException.java +++ b/src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java @@ -1,9 +1,9 @@ -package net.kronos.rkon.core.ex; - -public class AuthenticationException extends Exception { - - public AuthenticationException(String message) { - super(message); - } - -} +package net.kronos.rkon.core.ex; + +public class AuthenticationException extends Exception { + + public AuthenticationException(String message) { + super(message); + } + +} diff --git a/src/main/java/net/kronos/rkon/core/ex/MalformedPacketException.java b/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java similarity index 94% rename from src/main/java/net/kronos/rkon/core/ex/MalformedPacketException.java rename to src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java index 8ec8c13..ab2254b 100644 --- a/src/main/java/net/kronos/rkon/core/ex/MalformedPacketException.java +++ b/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java @@ -1,11 +1,11 @@ -package net.kronos.rkon.core.ex; - -import java.io.IOException; - -public class MalformedPacketException extends IOException { - - public MalformedPacketException(String message) { - super(message); - } - -} +package net.kronos.rkon.core.ex; + +import java.io.IOException; + +public class MalformedPacketException extends IOException { + + public MalformedPacketException(String message) { + super(message); + } + +} diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..fe490c3 --- /dev/null +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.mefrreex.vkbot.BootstrapKt + diff --git a/src/main/resources/allow_list.yml b/src/main/resources/allow_list.yml new file mode 100644 index 0000000..5b76d5f --- /dev/null +++ b/src/main/resources/allow_list.yml @@ -0,0 +1,4 @@ +# Список разрешенных пользователей +# (id пользователей которые могут использовать RCON) +allowed-users: + - 1 # Цифровые ID \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d477506..400a7cb 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,24 +1,20 @@ -groupId: 123 # ID группы вк -token: "" # Токен группы ВК - -# Символ перед командой для отправки комманды. Например "/". -# Оставьте пустым если не требуется. -command-prefix: "/" +vk: + groupId: 123 # Id группы ВК + accessToken: "token" # Токен группы ВК rcon: host: "localhost" # Адрес RCON port: 19132 # Порт RCON - password: "5YzNmYjUyM" # Пароль RCON + password: "password" # Пароль RCON -allowed-users: - - 1 # Цифровые ID +commands: + # Символ перед командой для отправки комманды. Например '/'. + # Оставьте пустым если не требуется. + prefix: '/' -# Быстрые команды, будут отображатся при вводе Rcon -fast-commands: - - "ver" - - "status" - - "stop" + # Быстрые команды + # (Отображаются при вводе "Начать", "Rcon") + fast-commands: ["ver:", "status", "stop"] -# Заблокированные команды -blocked-commands: - - "stop" + # Заблокированные команды + blocked-commands: ["stop"] diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index b1446bf..f93df06 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -1,10 +1,15 @@ -USER_ID: "Ваш ID: %s" -START: "Введите команду или используйте кнопку ниже." -RCON: "Введите команду для отправки на сервер." -RCON_WITH_COMMANDS: "Введите команду для отправки на сервер.\n\nБыстрые команды:" -USER_NOT_WHITELISTED: "Вас нет в списке пользователей, которые могут использовать RCON." -COMMAND_BLOCKED: "Данная команда заблокирована." -FAILED_TO_CONNECT: "Не удалось подключится к серверу. Возможно он оффлайн." -FAILED_TO_AUTHENTICATE: "Не удалось пройти аутентификацию RCON." -COMMAND_SENDED: "Команда отправлена.\nОтвет сервера: %s" -RESPONSE_NULL: "Команда отправлена. Сервер не вернул ответа." \ No newline at end of file +user_id: "Ваш ID: %s" + +start: "Введите команду или используйте кнопку ниже." + +rcon: "Введите команду для отправки на сервер." +rcon_with_commands: "Введите команду для отправки на сервер.\n\nБыстрые команды:" + +user_not_whitelisted: "Вас нет в списке пользователей, которые могут использовать RCON." + +command_blocked: "Данная команда заблокирована." +failed_to_connect: "Не удалось подключится к серверу. Возможно он оффлайн." +failed_to_authenticate: "Не удалось пройти аутентификацию RCON." + +command_sended: "Команда отправлена.\nОтвет сервера: %s" +response_null: "Команда отправлена. Сервер не вернул ответа." \ No newline at end of file