diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fdffd0c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# Compiled class file
+*.class
+.gradle/
+build/
+bin/
+target/
+.idea/
+.vscode/
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index f4da8f0..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
deleted file mode 100644
index 942f3a2..0000000
--- a/.idea/encodings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index 4d27ef0..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index 0e65cea..0000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 67e1e61..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index f8ee810..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "keyToString": {
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "settings.editor.selected.configurable": "advanced.settings"
- }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1683798584490
-
-
- 1683798584490
-
-
- 1683800991412
-
-
-
- 1683800991412
-
-
- 1683801094541
-
-
-
- 1683801094541
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 36d6c15..0624224 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,70 @@
# Vk-Rcon-Bot
-Simple bot for VK to send commands to the server using RCON
-![example](https://i.imgur.com/DH8hsSV.png)
+A simple bot for sending commands to a Minecraft server via RCON through the VK platform.
-## 📃 Used libraries:
-- [vk-java-sdk](https://github.com/VKCOM/vk-java-sdk)
-- [snakeyaml](https://github.com/snakeyaml/snakeyaml)
-- [rkon-core](https://github.com/kr5ch/rkon-core)
+## 🚀 Features
+- Sends RCON commands to a server directly from VK messages.
+- Easy setup and configuration.
+- Lightweight and simple to use.
+- **Multilingual support**: The bot supports multiple languages, with easy customization of all messages.
+- **Command prefix**: Ability to specify a custom prefix for commands.
+- **Quick commands**: Add predefined quick commands for frequent actions.
+- **Command blocking**: Option to block specific unwanted commands from being executed.
+## 🎮 Running the Bot
+1. Download the latest release of the bot.
+2. Run the following command to start the bot:
+```bash
+java -jar VkRconBot-.jar
+```
-## 🛠 Build JAR file
-- `git clone https://github.com/MEFRREEX/Vk-Rcon-Bot.git`
-- `cd Vk-Rcon-Bot`
-- `mvn clean package`
+## ⚙️ Configuration
+After the first run of the bot, a configuration file config.yml will be generated. You will need to edit this file before the bot can function properly.
+```yaml
+# Available languages: eng (English), rus (Русский)
+language: "rus"
-## 🎮 Run
-`java -jar VkRconBot-1.4.jar`
+# VK settings
+vk:
+ groupId: 123 # VK group Id
+ accessToken: "token" # VK group token
+
+# RCON settings
+rcon:
+ host: "localhost" # RCON address
+ port: 19132 # RCON port
+ password: "password" # RCON password
+
+# Command settings
+commands:
+ # A character before the command to send the command. For example '/'.
+ # Leave blank if not required.
+ prefix: '/'
+
+ # Fast commands
+ # (Displayed when “Start”, “Rcon” is entered)
+ fast-commands: ["ver", "status", "stop"]
+
+ # Blocked commands
+ blocked-commands: ["stop"]
+```
+
+## 🛠 Building the JAR File
+To build the project from source:
+1. Clone the repository:
+```bash
+git clone https://github.com/MEFRREEX/Vk-Rcon-Bot.git
+```
+2. Navigate to the project directory:
+```bash
+cd Vk-Rcon-Bot
+```
+3. Build the JAR file using Gradle:
+```bash
+gradle build
+```
+
+## 📄 Dependencies
+The project uses the following libraries:
+- [Vk-Java-Sdk](https://github.com/VKCOM/vk-java-sdk) - for VK API integration.
+- [Configuration](https://github.com/MEFRREEX/Configuration) - for managing configuration files.
\ No newline at end of file
diff --git a/README_ru.md b/README_ru.md
new file mode 100644
index 0000000..ad8bc0d
--- /dev/null
+++ b/README_ru.md
@@ -0,0 +1,72 @@
+# Vk-Rcon-Bot
+
+Простой бот для отправки команд на сервер через RCON с помощью платформы ВКонтакте.
+
+## 🚀 Основные возможности
+- Отправка команд на сервер через RCON из сообщений ВКонтакте.
+- Простая настройка и конфигурация.
+- Лёгкий в использовании.
+- **Поддержка нескольких языков**: Бот поддерживает несколько языков с простой настройкой всех сообщений.
+- **Префикс для команд**: Возможность указать префикс для команд.
+- **Быстрые команды**: Возможность добавить заранее настроенные быстрые команды для частых действий.
+- **Блокировка команд**: Возможность заблокировать нежелательные для использования команды.
+
+
+## 🎮 Запуск бота
+1. Скачайте последнюю версию бота.
+2. Запустите следующую команду для старта бота:
+```bash
+java -jar VkRconBot-<Версия>.jar
+```
+
+## ⚙️ Конфигурация
+После первого запуска бота будет сгенерирован файл конфигурации config.yml. Вам необходимо отредактировать этот файл перед дальнейшим использованием бота.
+
+```yaml
+# Доступные языки: eng (English), rus (Русский)
+language: "rus"
+
+# Настройки VK
+vk:
+ groupId: 123 # Id группы ВК
+ accessToken: "token" # Токен группы ВК
+
+# Настройки RCON
+rcon:
+ host: "localhost" # Адрес RCON
+ port: 19132 # Порт RCON
+ password: "password" # Пароль RCON
+
+# Настройки команд
+commands:
+ # Символ перед командой для отправки комманды. Например '/'.
+ # Оставьте пустым если не требуется.
+ prefix: '/'
+
+ # Быстрые команды
+ # (Отображаются при вводе "Начать", "Rcon")
+ fast-commands: ["ver", "status", "stop"]
+
+ # Заблокированные команды
+ blocked-commands: ["stop"]
+```
+
+## 🛠 Сборка JAR-файла
+Чтобы собрать проект из исходного кода:
+1. Клонируйте репозиторий:
+```bash
+git clone https://github.com/MEFRREEX/Vk-Rcon-Bot.git
+```
+2. Перейдите в директорию проекта:
+```bash
+cd Vk-Rcon-Bot
+```
+3. Соберите JAR-файл с помощью Gradle:
+```bash
+gradle build
+```
+
+## 📄 Зависимости
+Проект использует следующие библиотеки:
+- [Vk-Java-Sdk](https://github.com/VKCOM/vk-java-sdk) - для интеграции с API ВКонтакте.
+- [Configuration](https://github.com/MEFRREEX/Configuration) - для управления файлами конфигурации.
\ No newline at end of file
diff --git a/api/build.gradle.kts b/api/build.gradle.kts
new file mode 100644
index 0000000..c95d2c6
--- /dev/null
+++ b/api/build.gradle.kts
@@ -0,0 +1,11 @@
+plugins {
+ kotlin("jvm") version "1.9.21"
+}
+
+tasks.withType {
+ archiveFileName.set("Vk-Rcon-Bot-API-${project.version}.jar")
+}
+
+kotlin {
+ jvmToolchain(17)
+}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt
new file mode 100644
index 0000000..a176b9b
--- /dev/null
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt
@@ -0,0 +1,7 @@
+package com.mefrreex.vkbot.command
+
+abstract class Command(
+ val name: String,
+ val description: String = "",
+ val aliases: Array = emptyArray()
+)
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt
new file mode 100644
index 0000000..97e6ee8
--- /dev/null
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt
@@ -0,0 +1,14 @@
+package com.mefrreex.vkbot.command
+
+interface CommandService {
+
+ fun getCommands(): Map
+
+ fun getCommand(name: String): Command?
+
+ fun register(vararg commands: Command) {
+ commands.forEach { register(it)}
+ }
+
+ fun register(command: Command)
+}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt
new file mode 100644
index 0000000..d10cdc0
--- /dev/null
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt
@@ -0,0 +1,13 @@
+package com.mefrreex.vkbot.command
+
+import com.vk.api.sdk.objects.messages.Message
+
+abstract class SlashCommand(
+ name: String,
+ description: String = "",
+ aliases: Array = emptyArray()
+) :
+ Command(name, description, aliases) {
+
+ abstract fun execute(message: Message, label: String, args: List)
+}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt
new file mode 100644
index 0000000..3d3082a
--- /dev/null
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt
@@ -0,0 +1,12 @@
+package com.mefrreex.vkbot.command
+
+import com.vk.api.sdk.objects.messages.Message
+
+abstract class WordCommand(
+ name: String,
+ aliases: Array = emptyArray()
+) :
+ Command(name, "", aliases)
+{
+ abstract fun execute(message: Message)
+}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/mefrreex/vkbot/command/data/CommandData.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/data/CommandData.kt
new file mode 100644
index 0000000..ec8976a
--- /dev/null
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/data/CommandData.kt
@@ -0,0 +1,3 @@
+package com.mefrreex.vkbot.command.data
+
+class CommandData(val name: String, val args: List)
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/mefrreex/vkbot/command/parser/CommandParser.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/parser/CommandParser.kt
new file mode 100644
index 0000000..a756bce
--- /dev/null
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/parser/CommandParser.kt
@@ -0,0 +1,71 @@
+package com.mefrreex.vkbot.command.parser
+
+import com.mefrreex.vkbot.command.data.CommandData
+
+
+object CommandParser {
+
+ fun canParse(command: String): Boolean {
+ return command.startsWith("/")
+ }
+
+ fun parse(command: String?): CommandData? {
+ var command = command
+ if (command == null || command.isEmpty()) {
+ return null
+ }
+
+ if (command.startsWith("/")) {
+ command = command.substring(1)
+ }
+
+ val arguments = parseArguments(command)
+ if (arguments.isEmpty()) {
+ return null
+ }
+
+ val name = arguments[0]
+ val args = arguments.subList(1, arguments.size)
+
+ return CommandData(name, args)
+ }
+
+ fun parseArguments(command: String?): List {
+ if (command == null || command.isEmpty()) {
+ return emptyList()
+ }
+
+ val arguments: MutableList = ArrayList()
+
+ var quotes = false
+
+ var currentArgument = StringBuilder()
+ for (i in 0 until command.length) {
+ val c = command[i]
+ if (c == ' ') {
+ if (quotes) {
+ currentArgument.append(c)
+ } else {
+ if (!currentArgument.isEmpty()) {
+ arguments.add(currentArgument.toString())
+ currentArgument = StringBuilder()
+ }
+ }
+ } else if (c == '"') {
+ quotes = !quotes
+ } else {
+ currentArgument.append(c)
+ }
+ }
+
+ if (quotes) {
+ throw RuntimeException()
+ }
+
+ if (!currentArgument.isEmpty()) {
+ arguments.add(currentArgument.toString())
+ }
+
+ return arguments
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt b/api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt
similarity index 84%
rename from src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt
rename to api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt
index cffce71..016d843 100644
--- a/src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt
@@ -1,4 +1,4 @@
-package com.mefrreex.vkbot.objects
+package com.mefrreex.vkbot.handler
import com.google.gson.annotations.SerializedName
import com.vk.api.sdk.objects.messages.Message
diff --git a/api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt b/api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt
new file mode 100644
index 0000000..cc76fc6
--- /dev/null
+++ b/api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt
@@ -0,0 +1,27 @@
+package com.mefrreex.vkbot.handler
+
+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
+
+abstract class MessageHandler(
+ client: VkApiClient,
+ actor: GroupActor,
+ waitTime: Int
+) : GroupLongPollApi(client, actor, waitTime) {
+
+ abstract fun onMessageNew(message: Message);
+
+ override fun parse(message: CallbackMessage): String? {
+ if (message.type == Events.MESSAGE_NEW) {
+ gson.fromJson(message.getObject(), MessageFix::class.java).message?.let {
+ this.onMessageNew(it)
+ }
+ return "OK"
+ }
+ return super.parse(message)
+ }
+}
\ No newline at end of file
diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts
new file mode 100644
index 0000000..0459051
--- /dev/null
+++ b/bot/build.gradle.kts
@@ -0,0 +1,19 @@
+plugins {
+ kotlin("jvm") version "1.9.21"
+}
+
+dependencies {
+ api("com.github.MEFRREEX:configuration:main-SNAPSHOT")
+ api(project(":api"))
+}
+
+tasks.withType {
+ archiveFileName.set("Vk-Rcon-Bot-${project.version}.jar")
+ manifest {
+ attributes["Main-Class"] = "com.mefrreex.vkbot.BootstrapKt"
+ }
+}
+
+kotlin {
+ jvmToolchain(17)
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt
new file mode 100644
index 0000000..bba5110
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt
@@ -0,0 +1,41 @@
+package com.mefrreex.vkbot
+
+import com.mefrreex.vkbot.logger.Logger
+import com.mefrreex.vkbot.utils.ConfigHelper
+import java.io.File
+
+val logger = Logger()
+
+fun main() {
+ logger.info("Starting the bot...")
+
+ val resources = listOf(ConfigHelper.CONFIG, ConfigHelper.ALLOW_LIST)
+ resources.forEach {
+ if (!File(it).exists()) {
+ ConfigHelper.saveResource(it)
+ logger.info("Resource $it saved")
+ }
+ ConfigHelper.loadConfig(it)
+ }
+
+ val config = ConfigHelper.getConfig(ConfigHelper.CONFIG)
+ if (config == null) {
+ logger.error("Failed to start bot. File config.yml not found")
+ return
+ }
+
+ try {
+ val token = config.nodes("vk.accessToken").asString()
+ if (token == "token") {
+ logger.warn("Specify a valid bot token in the vk.accessToken parameter")
+ return
+ }
+
+ Bot(config, config.nodes("vk.groupId").asInt(), token)
+ } catch (e: Exception) {
+ logger.error("Failed to start the bot", e)
+ return
+ }
+
+ logger.info("Bot is started!")
+}
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/Bot.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/Bot.kt
new file mode 100644
index 0000000..facabfd
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/Bot.kt
@@ -0,0 +1,79 @@
+package com.mefrreex.vkbot
+
+import com.mefrreex.config.Config
+import com.mefrreex.vkbot.command.CommandService
+import com.mefrreex.vkbot.command.CommandServiceImpl
+import com.mefrreex.vkbot.command.impl.GetIDCommand
+import com.mefrreex.vkbot.command.impl.StartCommand
+import com.mefrreex.vkbot.handler.CommandMessageHandler
+import com.mefrreex.vkbot.handler.DefaultMessageHandler
+import com.mefrreex.vkbot.logger.Logger
+import com.mefrreex.vkbot.translation.TranslationService
+import com.mefrreex.vkbot.translation.TranslationServiceImpl
+import com.mefrreex.vkbot.utils.ConfigHelper
+import com.vk.api.sdk.client.VkApiClient
+import com.vk.api.sdk.client.actors.GroupActor
+import com.vk.api.sdk.httpclient.HttpTransportClient
+import com.vk.api.sdk.objects.messages.Keyboard
+import kotlin.random.Random
+
+class Bot(config: Config, groupId: Int, accessToken: String) {
+
+ private val allowList = ConfigHelper.getConfigNotNull(ConfigHelper.ALLOW_LIST)
+
+ private val vkClient: VkApiClient
+ private val groupActor: GroupActor
+
+ val logger = Logger()
+ val settings = BotSettings(config)
+
+ val translationService: TranslationService
+ val commandService: CommandService
+
+ init {
+ instance = this
+
+ val httpClient: HttpTransportClient = HttpTransportClient.getInstance()
+
+ vkClient = VkApiClient(httpClient)
+ groupActor = GroupActor(groupId, accessToken)
+
+ vkClient.groupsLongPoll().setLongPollSettings(groupActor, groupId)
+ .enabled(true)
+ .messageNew(true)
+ .execute()
+
+ translationService = TranslationServiceImpl(config)
+ commandService = CommandServiceImpl()
+ commandService.register(
+ StartCommand(),
+ GetIDCommand()
+ )
+
+ DefaultMessageHandler(vkClient, groupActor, 1, this).run()
+ CommandMessageHandler(vkClient, groupActor, 1, this).run()
+ }
+
+ fun isUserAllowed(userId: Int): Boolean {
+ return userId in allowList.node("allowed-users").asList()
+ }
+
+ fun sendMessage(userId: Int, message: String, keyboard: Keyboard? = null) {
+ vkClient.messages().send(groupActor)
+ .message(message)
+ .apply {
+ keyboard?.let { keyboard(it) }
+ }
+ .peerId(userId)
+ .randomId(Random.nextInt(10000))
+ .execute()
+ }
+
+ companion object {
+ private lateinit var instance: Bot
+
+ fun getInstance(): Bot {
+ return instance
+ }
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt
new file mode 100644
index 0000000..89e87b1
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt
@@ -0,0 +1,14 @@
+package com.mefrreex.vkbot
+
+import com.mefrreex.config.Config
+
+data class BotSettings(
+ private val config: Config
+) {
+ val rconHost: String = config.nodes("rcon.host").asString()
+ val rconPort: Int = config.nodes("rcon.port").asInt()
+ val rconPassword: String = config.nodes("rcon.password").asString()
+ val commandPrefix: String = config.nodes("commands.prefix").asString()
+ val fastCommands: List = config.nodes("commands.fast-commands").asList()
+ val blockedCommands: List = config.nodes("commands.blocked-commands").asList()
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt
new file mode 100644
index 0000000..b111b29
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt
@@ -0,0 +1,26 @@
+package com.mefrreex.vkbot.command
+
+class CommandServiceImpl : CommandService {
+ val commands = HashMap()
+
+ override fun getCommands(): Map {
+ return commands
+ }
+
+ override fun getCommand(name: String): Command? {
+ return commands[name]
+ }
+
+ override fun register(vararg commands: Command) {
+ commands.forEach { register(it)}
+ }
+
+ override fun register(command: Command) {
+ commands[command.name.lowercase()] = command
+ command.aliases.forEach {
+ if (commands[it.lowercase()] == null) {
+ commands[it.lowercase()] = command
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt
new file mode 100644
index 0000000..48acda4
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt
@@ -0,0 +1,15 @@
+package com.mefrreex.vkbot.command.impl
+
+import com.mefrreex.vkbot.Bot
+import com.mefrreex.vkbot.command.SlashCommand
+import com.vk.api.sdk.objects.messages.Message
+
+class GetIDCommand : SlashCommand("getid") {
+
+ private val bot = Bot.getInstance()
+ private val translationService = bot.translationService
+
+ override fun execute(message: Message, label: String, args: List) {
+ bot.sendMessage(message.peerId, translationService.translate("command-userid-success", message.fromId))
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt
new file mode 100644
index 0000000..da37f05
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt
@@ -0,0 +1,20 @@
+package com.mefrreex.vkbot.command.impl
+
+import com.mefrreex.vkbot.Bot
+import com.mefrreex.vkbot.command.WordCommand
+import com.mefrreex.vkbot.utils.Keyboards
+import com.vk.api.sdk.objects.messages.Message
+
+class StartCommand : WordCommand("start", arrayOf("rcon", "начать")) {
+
+ private val bot = Bot.getInstance()
+ private val translationService = bot.translationService
+
+ override fun execute(message: Message) {
+ if (!bot.isUserAllowed(message.peerId)) {
+ bot.sendMessage(message.peerId, translationService.translate("generic-user-not-whitelisted"))
+ return
+ }
+ bot.sendMessage(message.peerId, translationService.translate("command-start-success"), Keyboards.commandsKeyboard())
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt
new file mode 100644
index 0000000..cbb5351
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt
@@ -0,0 +1,42 @@
+package com.mefrreex.vkbot.handler
+
+import com.mefrreex.vkbot.Bot
+import com.mefrreex.vkbot.command.parser.CommandParser
+import com.mefrreex.vkbot.command.SlashCommand
+import com.mefrreex.vkbot.command.WordCommand
+import com.vk.api.sdk.client.VkApiClient
+import com.vk.api.sdk.client.actors.GroupActor
+import com.vk.api.sdk.objects.messages.Message
+
+class CommandMessageHandler(
+ client: VkApiClient,
+ actor: GroupActor,
+ waitTime: Int,
+ private val bot: Bot
+) : MessageHandler(client, actor, waitTime) {
+
+ private val commandService = bot.commandService
+
+ override fun onMessageNew(message: Message) {
+ val wordCommand = commandService.getCommand(message.text)
+ if (wordCommand is WordCommand) {
+ wordCommand.execute(message)
+ return
+ }
+
+ if (!CommandParser.canParse(message.text)) {
+ return
+ }
+
+ try {
+ val data = CommandParser.parse(message.text)
+ val command = commandService.getCommand(data!!.name)
+
+ if (command is SlashCommand) {
+ command.execute(message, data.name, data.args)
+ }
+ } catch (e: Exception) {
+ bot.sendMessage(message.peerId, e.message!!)
+ }
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt
new file mode 100644
index 0000000..0faad3f
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt
@@ -0,0 +1,70 @@
+package com.mefrreex.vkbot.handler
+
+import com.mefrreex.vkbot.Bot
+import com.mefrreex.vkbot.rcon.RconClient
+import com.mefrreex.vkbot.rcon.exception.ConnectionException
+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.objects.messages.Message
+
+class DefaultMessageHandler(
+ client: VkApiClient,
+ actor: GroupActor,
+ waitTime: Int,
+ private val bot: Bot
+) : MessageHandler(client, actor, waitTime) {
+
+ private val translationService = bot.translationService
+
+ override fun onMessageNew(message: Message) {
+ val text = message.text.lowercase()
+
+ if (text.startsWith(bot.settings.commandPrefix)) {
+ val command = text.substring(bot.settings.commandPrefix.length, text.length)
+ val commandName = command.split("\\s+".toRegex())[0].removePrefix("/")
+
+ if (bot.commandService.getCommand(commandName) != null) {
+ return
+ }
+
+ if (!bot.isUserAllowed(message.peerId)) {
+ return
+ }
+
+ if (bot.settings.blockedCommands.contains(commandName)) {
+ bot.sendMessage(message.peerId, translationService.translate("generic-command-blocked", command))
+ bot.logger.warn("User ${message.peerId} tried to use the blocked command ${TextFormat.RED}/$command${TextFormat.RESET}.")
+ return
+ }
+
+ val rcon = RconClient(bot.settings.rconHost, bot.settings.rconPort)
+
+ try {
+ rcon.connect(bot.settings.rconPassword)
+ } catch (e: Exception) {
+ if (e is ConnectionException) {
+ bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-connect"))
+ bot.logger.error("Unhandled exception when trying to connect to RCON:", e)
+ } else {
+ e.printStackTrace()
+ bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-authenticate"))
+ bot.logger.error("Unhandled exception on RCON authentication attempt:", e)
+ }
+ }
+
+ rcon.sendCommand(command).let {
+ if (it.length <= 4000) {
+ bot.sendMessage(message.peerId, if (it.isNotBlank()) {
+ translationService.translate("rcon-command-sent", it)
+ } else {
+ translationService.translate("rcon-response-empty")
+ })
+ } else {
+ bot.sendMessage(message.peerId, translationService.translate("rcon-command-response-too-long"))
+ }
+ bot.logger.info("User ${message.fromId} used the command ${TextFormat.YELLOW}/$command${TextFormat.RESET}")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt
similarity index 94%
rename from src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt
rename to bot/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt
index 5554ffc..4022b7a 100644
--- a/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt
@@ -34,8 +34,4 @@ class Logger {
println("${TextFormat.CYAN}$date ${TextFormat.WHITE}[$level] $message")
throwable?.printStackTrace()
}
-
- companion object {
- val instance = Logger()
- }
}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt
new file mode 100644
index 0000000..b619233
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt
@@ -0,0 +1,91 @@
+package com.mefrreex.vkbot.rcon
+
+import com.mefrreex.vkbot.rcon.exception.AuthenticationException
+import com.mefrreex.vkbot.rcon.exception.ConnectionException
+import com.mefrreex.vkbot.rcon.packet.RconPacket
+import com.mefrreex.vkbot.rcon.packet.RconPacketType
+import java.io.IOException
+import java.net.InetSocketAddress
+import java.nio.charset.Charset
+
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+
+class RconClient(
+ host: String,
+ port: Int,
+ private val charset: Charset = StandardCharsets.UTF_8
+) {
+ private val address = InetSocketAddress(host, port)
+ private var socketChannel: SocketChannel? = null
+ private var password: String? = null
+
+ /**
+ * Connect to the server using the provided password
+ * @param password Password for authentication
+ */
+ fun connect(password: String?) {
+ this.password = password
+ this.connect()
+ }
+
+ /**
+ * Connect to the server using the stored password
+ * @throws AuthenticationException If authentication fails
+ */
+ fun connect() {
+ try {
+ socketChannel = SocketChannel.open().apply {
+ socket().setSoTimeout(1500)
+ connect(address)
+ }
+ } catch (e: IOException) {
+ throw AuthenticationException("Authorization error", e)
+ }
+
+ val packet = RconPacket(RconPacketType.AUTH_REQUEST)
+ packet.payload = password!!.toByteArray()
+
+ val response = this.sendPacket(packet) ?: throw ConnectionException("Server is offline")
+
+ if (response.requestId == -1) {
+ throw AuthenticationException("Wrong password")
+ }
+ }
+
+ /**
+ * Send a command to the server
+ * @param command Command to send
+ * @return Server response
+ */
+ fun sendCommand(command: String): String {
+ val packet = RconPacket(RconPacketType.COMMAND_EXECUTE)
+ packet.payload = command.toByteArray(charset)
+
+ val response = sendPacket(packet)
+ return String(response!!.payload, charset)
+ }
+
+ /**
+ * Send the packet to the server
+ * @param packet RconPacket to send
+ * @param socketChannel SocketChannel to use for sending
+ * @return A packet sent by the server in response
+ */
+ private fun sendPacket(packet: RconPacket, socketChannel: SocketChannel?): RconPacket? {
+ return try {
+ packet.send(socketChannel!!)
+ } catch (e: IOException) {
+ null
+ }
+ }
+
+ /**
+ * Send the packet to the socketChannel
+ * @param packet RconPacket to send
+ * @return A packet sent by the server in response
+ */
+ fun sendPacket(packet: RconPacket): RconPacket? {
+ return this.sendPacket(packet, socketChannel)
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt
new file mode 100644
index 0000000..e4c63ec
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt
@@ -0,0 +1,3 @@
+package com.mefrreex.vkbot.rcon.exception
+
+class AuthenticationException(message: String?, cause: Throwable? = null) : RuntimeException(message, cause)
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt
new file mode 100644
index 0000000..246f60a
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt
@@ -0,0 +1,3 @@
+package com.mefrreex.vkbot.rcon.exception
+
+class ConnectionException(message: String?, cause: Throwable? = null) : RuntimeException(message, cause)
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt
new file mode 100644
index 0000000..e50e2d3
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt
@@ -0,0 +1,67 @@
+package com.mefrreex.vkbot.rcon.packet
+
+import java.io.DataInputStream
+import java.io.IOException
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.nio.channels.SocketChannel
+import java.util.*
+
+
+class RconPacket(
+ val packetType: Int,
+ val requestId: Int = Random().nextInt()
+) {
+ var payload = ByteArray(1)
+
+ @Throws(IOException::class)
+ fun send(socketChannel: SocketChannel): RconPacket {
+ try {
+ this.write(socketChannel)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+
+ return read(socketChannel)
+ }
+
+ @Throws(IOException::class)
+ private fun write(socketChannel: SocketChannel) {
+ val length = payload.size + 14
+
+ val buffer = ByteBuffer.allocate(length)
+ buffer.order(ByteOrder.LITTLE_ENDIAN)
+
+ buffer.putInt(length - 4)
+ buffer.putInt(requestId)
+ buffer.putInt(packetType)
+ buffer.put(payload)
+
+ buffer.put(0.toByte())
+ buffer.put(0.toByte())
+
+ socketChannel.write(ByteBuffer.wrap(buffer.array()))
+ }
+
+ @Throws(IOException::class)
+ private fun read(socketChannel: SocketChannel): RconPacket {
+ val stream = socketChannel.socket().getInputStream()
+
+ val buffer = ByteBuffer.allocate(4 * 3)
+ stream.read(buffer.array())
+ buffer.order(ByteOrder.LITTLE_ENDIAN)
+
+ val length = buffer.getInt()
+ val requestId = buffer.getInt()
+ val type = buffer.getInt()
+
+ val payload = ByteArray(length - 10)
+ val dataStream = DataInputStream(stream)
+ dataStream.readFully(payload)
+ dataStream.read(ByteArray(2))
+
+ val packet = RconPacket(type, requestId)
+ packet.payload = payload
+ return packet
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt
new file mode 100644
index 0000000..e48e02a
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt
@@ -0,0 +1,8 @@
+package com.mefrreex.vkbot.rcon.packet
+
+object RconPacketType {
+ const val AUTH_REQUEST = 3
+ const val AUTH_RESPONSE = 2
+ const val COMMAND_EXECUTE = 2
+ const val COMMAND_RESPONSE = 0
+}
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt
new file mode 100644
index 0000000..0e08701
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt
@@ -0,0 +1,8 @@
+package com.mefrreex.vkbot.translation
+
+interface TranslationService {
+
+ fun getLanguage(): String
+
+ fun translate(key: String, vararg replacements: Any?): String
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt
new file mode 100644
index 0000000..ab8c01f
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt
@@ -0,0 +1,40 @@
+package com.mefrreex.vkbot.translation
+
+import com.mefrreex.config.Config
+import com.mefrreex.config.YamlConfig
+import com.mefrreex.vkbot.utils.ConfigHelper
+import java.io.File
+
+class TranslationServiceImpl(config: Config) : TranslationService {
+
+ private val language: String = config.node("language").asString()
+ private val languages: Map
+
+ init {
+ ConfigHelper.saveResource("lang/$language.yml")
+ val languageConfig = YamlConfig(File("lang", "$language.yml"))
+ this.languages = languageConfig.root().asMap()
+ }
+
+ /**
+ * Get selected language
+ */
+ override fun getLanguage(): String {
+ return language
+ }
+
+ /**
+ * Get translation by key
+ * @param key Message key
+ * @param replacements Message parameters
+ */
+ override fun translate(key: String, vararg replacements: Any?): String {
+ var translated = languages[key] ?: key
+
+ for ((i, replacement) in replacements.withIndex()) {
+ translated = translated.replace("[$i]", replacement.toString())
+ }
+
+ return translated
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt
new file mode 100644
index 0000000..e7e4d90
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt
@@ -0,0 +1,35 @@
+package com.mefrreex.vkbot.utils
+
+import com.mefrreex.config.Config
+import com.mefrreex.config.ConfigType
+import java.io.File
+
+object ConfigHelper {
+
+ const val CONFIG = "config.yml"
+ const val ALLOW_LIST = "allow_list.yml"
+
+ private val configs: MutableMap = mutableMapOf()
+
+ fun getConfig(name: String): Config? {
+ return configs[name]
+ }
+
+ fun getConfigNotNull(name: String): Config {
+ return configs[name]!!
+ }
+
+ fun loadConfig(name: String) {
+ configs[name] = ConfigType.DETECT.createOf(name);
+ }
+
+ fun saveResource(target: String, output: String = target, replace: Boolean = false) {
+ val resourceStream = javaClass.classLoader.getResourceAsStream(target)
+ val outputFile = File(output)
+ if (resourceStream != null && (replace || !outputFile.exists())) {
+ outputFile.parentFile?.mkdirs()
+ outputFile.createNewFile()
+ outputFile.outputStream().use { resourceStream.copyTo(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt
new file mode 100644
index 0000000..a87693a
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt
@@ -0,0 +1,25 @@
+package com.mefrreex.vkbot.utils
+
+import com.mefrreex.vkbot.Bot
+import com.vk.api.sdk.objects.messages.*
+
+object Keyboards {
+
+ private val settings = Bot.getInstance().settings
+
+ fun commandsKeyboard(): Keyboard {
+ return Keyboard()
+ .setInline(true)
+ .setButtons(listOf(
+ settings.fastCommands.map { command ->
+ KeyboardButton()
+ .setColor(KeyboardButtonColor.POSITIVE)
+ .setAction(
+ KeyboardButtonAction()
+ .setLabel(settings.commandPrefix + command)
+ .setType(TemplateActionTypeNames.TEXT)
+ )
+ }
+ ))
+ }
+}
diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt
new file mode 100644
index 0000000..274d6b5
--- /dev/null
+++ b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt
@@ -0,0 +1,16 @@
+package com.mefrreex.vkbot.utils
+
+object TextFormat {
+ const val BLACK = "\u001b[0;30m"
+ const val RED = "\u001b[0;31m"
+ const val GREEN = "\u001b[0;92m"
+ const val YELLOW = "\u001b[0;33m"
+ const val BLUE = "\u001b[0;34m"
+ const val MAGENTA = "\u001b[0;35m"
+ const val CYAN = "\u001b[0;36m"
+ const val WHITE = "\u001b[0;37m"
+ const val RESET = "\u001b[0m"
+ const val BOLD = "\u001b[1m"
+ const val UNDERLINE = "\u001b[4m"
+ const val REVERSED = "\u001b[7m"
+}
\ No newline at end of file
diff --git a/src/main/resources/allow_list.yml b/bot/src/main/resources/allow_list.yml
similarity index 100%
rename from src/main/resources/allow_list.yml
rename to bot/src/main/resources/allow_list.yml
diff --git a/src/main/resources/config.yml b/bot/src/main/resources/config.yml
similarity index 79%
rename from src/main/resources/config.yml
rename to bot/src/main/resources/config.yml
index 15b2922..2d6694d 100644
--- a/src/main/resources/config.yml
+++ b/bot/src/main/resources/config.yml
@@ -1,20 +1,26 @@
-vk:
- groupId: 123 # Id группы ВК
- accessToken: "token" # Токен группы ВК
-
-rcon:
- host: "localhost" # Адрес RCON
- port: 19132 # Порт RCON
- password: "password" # Пароль RCON
-
-commands:
- # Символ перед командой для отправки комманды. Например '/'.
- # Оставьте пустым если не требуется.
- prefix: '/'
-
- # Быстрые команды
- # (Отображаются при вводе "Начать", "Rcon")
- fast-commands: ["ver", "status", "stop"]
-
- # Заблокированные команды
- blocked-commands: ["stop"]
+# Доступные языки: eng (English), rus (Русский)
+language: "rus"
+
+# Настройки VK
+vk:
+ groupId: 123 # Id группы ВК
+ accessToken: "token" # Токен группы ВК
+
+# Настройки RCON
+rcon:
+ host: "localhost" # Адрес RCON
+ port: 19132 # Порт RCON
+ password: "password" # Пароль RCON
+
+# Настройки команд
+commands:
+ # Символ перед командой для отправки комманды. Например '/'.
+ # Оставьте пустым если не требуется.
+ prefix: '/'
+
+ # Быстрые команды
+ # (Отображаются при вводе "Начать", "Rcon")
+ fast-commands: ["ver", "status", "stop"]
+
+ # Заблокированные команды
+ blocked-commands: ["stop"]
diff --git a/bot/src/main/resources/lang/eng.yml b/bot/src/main/resources/lang/eng.yml
new file mode 100644
index 0000000..f30d655
--- /dev/null
+++ b/bot/src/main/resources/lang/eng.yml
@@ -0,0 +1,14 @@
+# Generic messages
+generic-user-not-whitelisted: "You do not have permission to use RCON"
+generic-command-blocked: "This command is blocked"
+
+# Command messages
+command-userid-success: "Your ID: [0]"
+command-start-success: "Type a command or use the button below"
+
+# RCON Messages
+rcon-failed-to-connect: "Failed to connect to the server"
+rcon-failed-to-authenticate: "Failed to authenticate"
+rcon-command-sent: "Command sent.\nServer response: [0]"
+rcon-response-empty: "Command sent. Server didn't return a response"
+rcon-command-response-too-long: "Command sent. Server response is too long"
diff --git a/bot/src/main/resources/lang/rus.yml b/bot/src/main/resources/lang/rus.yml
new file mode 100644
index 0000000..e1e49b7
--- /dev/null
+++ b/bot/src/main/resources/lang/rus.yml
@@ -0,0 +1,14 @@
+# Общие сообщения
+generic-user-not-whitelisted: "У вас нет разрешения на использование RCON"
+generic-command-blocked: "Данная команда заблокирована"
+
+# Сообщения команд
+command-userid-success: "Ваш ID: [0]"
+command-start-success: "Введите команду или используйте кнопку ниже"
+
+# Сообщения RCON
+rcon-failed-to-connect: "Не удалось подключиться к серверу"
+rcon-failed-to-authenticate: "Не удалось пройти аутентификацию"
+rcon-command-sent: "Команда отправлена.\nОтвет сервера: [0]"
+rcon-response-empty: "Команда отправлена. Сервер не вернул ответ"
+rcon-command-response-too-long: "Ответ сервера слишком длинный"
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..b059243
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,42 @@
+plugins {
+ `java-library`
+ id("com.github.johnrengelman.shadow") version "8.1.1"
+}
+
+java.sourceCompatibility = JavaVersion.VERSION_17
+
+tasks.build {
+ dependsOn(tasks.shadowJar)
+}
+
+allprojects {
+ group = "com.mefrreex.vkbot"
+ description = "vkrconbot"
+ version = "1.5.1"
+}
+
+subprojects {
+
+ apply {
+ plugin("java-library")
+ }
+
+ repositories {
+ mavenLocal()
+ maven("https://repo1.maven.org/maven2")
+ maven("https://repo.maven.apache.org/maven2/")
+ maven("https://jitpack.io")
+ }
+
+ dependencies {
+ api("com.vk.api:sdk:1.0.14")
+ }
+
+ tasks.withType {
+ options.encoding = "UTF-8"
+ }
+
+ tasks.withType {
+ options.encoding = "UTF-8"
+ }
+}
\ No newline at end of file
diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
deleted file mode 100644
index 8e4eadf..0000000
--- a/dependency-reduced-pom.xml
+++ /dev/null
@@ -1,128 +0,0 @@
-
-
- 4.0.0
- 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
-
-
- 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
-
-
-
- maven-compiler-plugin
-
-
- 17
-
-
-
-
-
-
- mavenCentral
- https://repo1.maven.org/maven2/
-
-
-
-
- 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
-
-
-
-
-
- official
- UTF-8
- 17
-
-
-
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e644113
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b82aa23
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# 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"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..25da30d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+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% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index 7e0cb04..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
- 4.0.0
-
- vkrconbot
- com.mefrreex.vkbot
- 1.4.0.1
- 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
-
-
-
-
-
-
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..f511559
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
+}
+
+rootProject.name = "Vk-Rcon-Bot"
+
+include("api")
+include("bot")
diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt
deleted file mode 100644
index c66e777..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.mefrreex.vkbot
-
-import com.mefrreex.vkbot.config.ConfigManager
-import com.mefrreex.vkbot.logger.Logger
-import java.io.File
-
-val logger = Logger.instance
-val configManager = ConfigManager()
-
-lateinit var server: Server
-
-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"))
- resources.forEach {
- if (!it.exists()) {
- configManager.saveResource(it.name)
- logger.info("Resource `${it.name}` saved.")
- }
- }
-
- 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
deleted file mode 100644
index 648f8a3..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/Server.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-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) {
-
- val config = Settings.config
- val groupId = Settings.GROUP_ID.int()
- 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();
-
- }
-}
\ 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
deleted file mode 100644
index 5fd45d7..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/config/Config.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-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) {
- // TODO
- }
-
- fun save() {
- // TODO
- }
-
-}
diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt b/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt
deleted file mode 100644
index 8be1e26..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-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
deleted file mode 100644
index 6a34b6c..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-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 {
- val config = Config("allow_list.yml")
- }
-
- 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
deleted file mode 100644
index c6fd1a2..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-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 {
- val config = Config("messages.yml")
- }
-
- 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
deleted file mode 100644
index b07da1a..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-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 {
- val config = Config("config.yml")
- }
-
- 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
deleted file mode 100644
index 970ca3c..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-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.instance
-
- fun messageNewFix(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(it)
- }
- return "OK"
- }
- return super.parse(message)
- }
-
-}
\ 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
deleted file mode 100644
index 5b934ea..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-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
-
-object Rcon {
-
- 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
deleted file mode 100644
index 3dc2ee0..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-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
deleted file mode 100644
index 6ec71c6..0000000
--- a/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-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/kotlin/net/kronos/rkon/core/Rcon.java b/src/main/kotlin/net/kronos/rkon/core/Rcon.java
deleted file mode 100644
index 3d32ea5..0000000
--- a/src/main/kotlin/net/kronos/rkon/core/Rcon.java
+++ /dev/null
@@ -1,133 +0,0 @@
-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/kotlin/net/kronos/rkon/core/RconPacket.java b/src/main/kotlin/net/kronos/rkon/core/RconPacket.java
deleted file mode 100644
index b2deec2..0000000
--- a/src/main/kotlin/net/kronos/rkon/core/RconPacket.java
+++ /dev/null
@@ -1,152 +0,0 @@
-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/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java b/src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java
deleted file mode 100644
index 45ef113..0000000
--- a/src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.kronos.rkon.core.ex;
-
-public class AuthenticationException extends Exception {
-
- public AuthenticationException(String message) {
- super(message);
- }
-
-}
diff --git a/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java b/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java
deleted file mode 100644
index ab2254b..0000000
--- a/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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
deleted file mode 100644
index fe490c3..0000000
--- a/src/main/resources/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-Main-Class: com.mefrreex.vkbot.BootstrapKt
-
diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml
deleted file mode 100644
index f93df06..0000000
--- a/src/main/resources/messages.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-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