diff --git a/build.gradle b/build.gradle index b4ade65..7481708 100644 --- a/build.gradle +++ b/build.gradle @@ -47,11 +47,10 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" testImplementation group: 'junit', name: 'junit', version: '4.12' compileOnly "org.spigotmc:spigot-api:1.15.2-R0.1-SNAPSHOT" - implementation group: 'org.telegram', name: 'telegrambots', version: '4.6' implementation 'com.vdurmont:emoji-java:5.1.1' -// def tgBotVer = '5.0.0' -// implementation "io.github.kotlin-telegram-bot.kotlin-telegram-bot:telegram:$tgBotVer" + def tgBotVer = '5.0.0' + implementation "io.github.kotlin-telegram-bot.kotlin-telegram-bot:telegram:$tgBotVer" } compileKotlin { diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Bot.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Bot.kt deleted file mode 100644 index 54c55e3..0000000 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Bot.kt +++ /dev/null @@ -1,125 +0,0 @@ -package org.kraftwerk28.spigot_tg_bridge - -import org.telegram.telegrambots.bots.TelegramLongPollingBot -import org.telegram.telegrambots.meta.api.methods.ParseMode -import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatAdministrators -import org.telegram.telegrambots.meta.api.methods.send.SendMessage -import org.telegram.telegrambots.meta.api.objects.Message -import org.telegram.telegrambots.meta.api.objects.Update -import org.telegram.telegrambots.meta.api.objects.User -import org.telegram.telegrambots.meta.exceptions.TelegramApiException -import org.kraftwerk28.spigot_tg_bridge.Constants as C - -class Bot(private var plugin: Plugin) : TelegramLongPollingBot() { - private val allowedChats: List - private val chatToMC: Boolean - private val botToken: String - private val botUsername: String - private val cmd = Commands(plugin) - - init { - plugin.config.run { - allowedChats = getLongList(C.FIELDS.ALLOWED_CHATS) - chatToMC = getBoolean(C.FIELDS.LOG_FROM_TG_TO_MC, C.DEFS.logFromTGtoMC) - botToken = getString(C.FIELDS.BOT_TOKEN) ?: throw Exception(C.WARN.noToken) - botUsername = getString(C.FIELDS.BOT_USERNAME) ?: throw Exception(C.WARN.noUsername) - } - } - - override fun getBotToken() = botToken - override fun getBotUsername() = botUsername - - override fun onUpdateReceived(update: Update?) { - val msg = update?.message - plugin.logger.info("Chat id: ${msg?.chatId}, Message id: ${msg?.messageId}.") - if (msg == null || msg.text == null) return - if (!allowedChats.contains(msg.chatId)) return - - // cmd shows online players - if (msg.text.startsWith(cmd.online)) { - val playerList = plugin.server.onlinePlayers - val playerStr = plugin.server - .onlinePlayers - .mapIndexed { i, s -> "${i + 1}. ${s.displayName}" } - .joinToString("\n") - val onlineStr = plugin.config.getString( - C.FIELDS.STRINGS.ONLINE, - C.DEFS.playersOnline - )!! - val offlineStr = plugin.config.getString( - C.FIELDS.STRINGS.OFFLINE, - C.DEFS.nobodyOnline - )!! - val text = - if (playerList.isNotEmpty()) "$onlineStr:\n$playerStr" - else offlineStr - reply(msg, text) { it.replyToMessageId = msg.messageId } - } - if (msg.text.startsWith(cmd.time)) { - val t = plugin.server.worlds[0].time - var text = when { - t <= 12000 -> "\uD83C\uDFDE Day" - t <= 13800 -> "\uD83C\uDF06 Sunset" - t <= 22200 -> "\uD83C\uDF03 Night" - t <= 24000 -> "\uD83C\uDF05 Sunrise" - else -> "" - } - text += " ($t)" - reply(msg, text) { it.replyToMessageId = msg.messageId } - } - - // stop, if no command matched: - if (msg.text!!.startsWith("/")) return - - if (chatToMC) - plugin.sendMessageToMCFrom(rawUserMention(msg.from), msg.text) - } - - fun sendMessageToTGFrom(username: String, text: String) { - allowedChats.forEach { - try { - val msg = SendMessage(it, mcMessageStr(username, text)) - .setParseMode(ParseMode.HTML) - execute(msg) - } catch (e: TelegramApiException) { - } - } - } - - fun broadcastToTG(text: String) { - allowedChats.forEach { chatID -> - try { - val msg = SendMessage(chatID, text).setParseMode(ParseMode.HTML) - execute(msg) - } catch (e: TelegramApiException) { - } - } - } - - private fun reply( - msg: Message, - text: String, - prep: ((sender: SendMessage) -> Unit)? = null - ): Message { - val snd = SendMessage(msg.chatId, text).setParseMode(ParseMode.HTML) - if (prep != null) prep(snd) - return execute(snd) - } - - private fun checkAdmin(msg: Message): Boolean { - val admins = execute(GetChatAdministrators().setChatId(msg.chatId)) - return admins.any { it.user.id == msg.from.id } - } - - private fun mcMessageStr(username: String, text: String): String = - "$username: $text" - - private fun rawUserMention(user: User): String = - (if (user.firstName.length < 2) null else user.firstName) - ?: user.userName - ?: user.lastName - - private fun telegramUserMention(user: User): String = - if (user.userName != null) "@${user.userName}" - else "${user.firstName ?: user.lastName}" -} diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Constants.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Constants.kt index fd71f84..d8e9024 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Constants.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Constants.kt @@ -1,48 +1,48 @@ package org.kraftwerk28.spigot_tg_bridge object Constants { - val configFilename = "config.yml" + const val configFilename = "config.yml" // Config field names object FIELDS { - val BOT_TOKEN = "botToken" - val BOT_USERNAME = "botUsername" - val ALLOWED_CHATS = "chats" - val LOG_FROM_MC_TO_TG = "logFromMCtoTG" - val LOG_FROM_TG_TO_MC = "logFromTGtoMC" - val SERVER_START_MSG = "serverStartMessage" - val SERVER_STOP_MSG = "serverStopMessage" - val LOG_JOIN_LEAVE = "logJoinLeave" - val LOG_PLAYER_DEATH = "logPlayerDeath" - val LOG_PLAYER_ASLEEP = "logPlayerAsleep" + const val BOT_TOKEN = "botToken" + const val BOT_USERNAME = "botUsername" + const val ALLOWED_CHATS = "chats" + const val LOG_FROM_MC_TO_TG = "logFromMCtoTG" + const val LOG_FROM_TG_TO_MC = "logFromTGtoMC" + const val SERVER_START_MSG = "serverStartMessage" + const val SERVER_STOP_MSG = "serverStopMessage" + const val LOG_JOIN_LEAVE = "logJoinLeave" + const val LOG_PLAYER_DEATH = "logPlayerDeath" + const val LOG_PLAYER_ASLEEP = "logPlayerAsleep" object STRINGS { - val ONLINE = "strings.online" - val OFFLINE = "strings.offline" - val JOINED = "strings.joined" - val LEFT = "strings.left" + const val ONLINE = "strings.online" + const val OFFLINE = "strings.offline" + const val JOINED = "strings.joined" + const val LEFT = "strings.left" } object COMMANDS { - val TIME = "commands.time" - val ONLINE = "commands.online" + const val TIME = "commands.time" + const val ONLINE = "commands.online" } } object DEFS { - val logFromMCtoTG = false - val logFromTGtoMC = false - val logJoinLeave = false - val logPlayerDeath = false - val logPlayerAsleep = false + const val logFromMCtoTG = false + const val logFromTGtoMC = false + const val logJoinLeave = false + const val logPlayerDeath = false + const val logPlayerAsleep = false object COMMANDS { - val TIME = "/time" - val ONLINE = "/online" + const val TIME = "time" + const val ONLINE = "online" } - val playersOnline = "Online" - val nobodyOnline = "Nobody online" - val playerJoined = "joined" - val playerLeft = "left" + const val playersOnline = "Online" + const val nobodyOnline = "Nobody online" + const val playerJoined = "joined" + const val playerLeft = "left" } object WARN { - val noConfigWarning = "No config file found! Writing default config to config.yml." - val noToken = "Bot token must be defined." - val noUsername = "Bot username must be defined." + const val noConfigWarning = "No config file found! Writing default config to config.yml." + const val noToken = "Bot token must be defined." + const val noUsername = "Bot username must be defined." } } diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/EventHandler.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/EventHandler.kt index 90dffbe..02fad2d 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/EventHandler.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/EventHandler.kt @@ -31,8 +31,8 @@ class EventHandler(private val plugin: Plugin) : Listener { @EventHandler fun onPlayerChat(event: AsyncPlayerChatEvent) { if (plugin.chatToTG) { - plugin.tgBot?.sendMessageToTGFrom( - escapeHTML(event.player.displayName), event.message + plugin.tgBot.sendMessageToTGFrom( + event.player.displayName, event.message ) } } @@ -40,15 +40,15 @@ class EventHandler(private val plugin: Plugin) : Listener { @EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { if (!logJoinLeave) return - val text = "${escapeHTML(event.player.displayName)} $joinStr." - plugin.tgBot?.broadcastToTG(text) + val text = "${TgBot.escapeHTML(event.player.displayName)} $joinStr." + plugin.tgBot.broadcastToTG(text) } @EventHandler fun onPlayerLeave(event: PlayerQuitEvent) { if (!logJoinLeave) return - val text = "${escapeHTML(event.player.displayName)} $leftStr." - plugin.tgBot?.broadcastToTG(text) + val text = "${TgBot.escapeHTML(event.player.displayName)} $leftStr." + plugin.tgBot.broadcastToTG(text) } @EventHandler @@ -57,7 +57,7 @@ class EventHandler(private val plugin: Plugin) : Listener { event.deathMessage?.let { val plName = event.entity.displayName val text = it.replace(plName, "$plName") - plugin.tgBot?.broadcastToTG(text) + plugin.tgBot.broadcastToTG(text) } } @@ -66,6 +66,6 @@ class EventHandler(private val plugin: Plugin) : Listener { if (!logPlayerAsleep) return if (event.bedEnterResult != PlayerBedEnterEvent.BedEnterResult.OK) return val text = "${event.player.displayName} fell asleep." - plugin.tgBot?.broadcastToTG(text) + plugin.tgBot.broadcastToTG(text) } } \ No newline at end of file diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Plugin.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Plugin.kt index dc04359..a5370c0 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Plugin.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Plugin.kt @@ -2,14 +2,12 @@ package org.kraftwerk28.spigot_tg_bridge import com.vdurmont.emoji.EmojiParser import org.bukkit.plugin.java.JavaPlugin -import org.telegram.telegrambots.ApiContextInitializer -import org.telegram.telegrambots.meta.TelegramBotsApi import java.io.File import org.kraftwerk28.spigot_tg_bridge.Constants as C class Plugin : JavaPlugin() { - var tgBot: Bot? = null + lateinit var tgBot: TgBot var chatToTG: Boolean = false init { @@ -32,26 +30,19 @@ class Plugin : JavaPlugin() { return } - ApiContextInitializer.init() - val botsApi = TelegramBotsApi() - tgBot = Bot(this) - - botsApi.registerBot(tgBot) - + tgBot = TgBot(this) server.pluginManager.registerEvents(EventHandler(this), this) // Notify everything about server start config.getString(C.FIELDS.SERVER_START_MSG, null)?.let { - logger.info("Server start message: $it") - tgBot?.broadcastToTG(it) + tgBot.broadcastToTG(it) } logger.info("Plugin started.") } override fun onDisable() { config.getString(C.FIELDS.SERVER_STOP_MSG, null)?.let { - logger.info("Server stop message: $it") - tgBot?.broadcastToTG(it) + tgBot.broadcastToTG(it) } logger.info("Plugin stopped.") } @@ -62,10 +53,10 @@ class Plugin : JavaPlugin() { } fun sendMessageToMCFrom(username: String, text: String) { - val text = run { - val text = "<${escapeHTML(username)}> $text" - EmojiParser.parseToAliases(text) - } - server.broadcastMessage(text) + server.broadcastMessage( + EmojiParser.parseToAliases( + "<${TgBot.escapeHTML(username)}> $text" + ) + ) } } diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/TgBot.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/TgBot.kt new file mode 100644 index 0000000..62ee7ad --- /dev/null +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/TgBot.kt @@ -0,0 +1,120 @@ +package org.kraftwerk28.spigot_tg_bridge + +import com.github.kotlintelegrambot.Bot +import com.github.kotlintelegrambot.bot +import com.github.kotlintelegrambot.dispatch +import com.github.kotlintelegrambot.dispatcher.command +import com.github.kotlintelegrambot.dispatcher.text +import com.github.kotlintelegrambot.entities.ParseMode +import com.github.kotlintelegrambot.entities.Update +import com.github.kotlintelegrambot.entities.User +import okhttp3.logging.HttpLoggingInterceptor + +class TgBot(val plugin: Plugin) { + + private val commands = Commands(plugin) + private val bot: Bot + private val allowedChats: List + private val chatToMC: Boolean + private val botToken: String + private val botUsername: String + + init { + plugin.config.run { + allowedChats = getLongList(Constants.FIELDS.ALLOWED_CHATS) + chatToMC = getBoolean(Constants.FIELDS.LOG_FROM_TG_TO_MC, Constants.DEFS.logFromTGtoMC) + botToken = getString(Constants.FIELDS.BOT_TOKEN) ?: throw Exception(Constants.WARN.noToken) + botUsername = getString(Constants.FIELDS.BOT_USERNAME) ?: throw Exception(Constants.WARN.noUsername) + } + val slashRegex = "^/+".toRegex() + + bot = bot { + token = botToken + logLevel = HttpLoggingInterceptor.Level.NONE + dispatch { + text(null, ::onText) + command(commands.time.replace(slashRegex, ""), ::time) + command(commands.online.replace(slashRegex, ""), ::online) + } + } + bot.startPolling() + } + + private fun time(bot: Bot, update: Update) { + val t = plugin.server.worlds[0].time + var text = when { + t <= 12000 -> "\uD83C\uDFDE Day" + t <= 13800 -> "\uD83C\uDF06 Sunset" + t <= 22200 -> "\uD83C\uDF03 Night" + t <= 24000 -> "\uD83C\uDF05 Sunrise" + else -> "" + } + text += " ($t)" + val msg = update.message!! + bot.sendMessage( + msg.chat.id, text, + replyToMessageId = msg.messageId, + parseMode = ParseMode.HTML + ) + } + + private fun online(bot: Bot, update: Update) { + val playerList = plugin.server.onlinePlayers + val playerStr = plugin.server + .onlinePlayers + .mapIndexed { i, s -> "${i + 1}. ${s.displayName}" } + .joinToString("\n") + val onlineStr = plugin.config.getString( + Constants.FIELDS.STRINGS.ONLINE, + Constants.DEFS.playersOnline + )!! + val offlineStr = plugin.config.getString( + Constants.FIELDS.STRINGS.OFFLINE, + Constants.DEFS.nobodyOnline + )!! + val text = + if (playerList.isNotEmpty()) "$onlineStr:\n$playerStr" + else offlineStr + val msg = update.message!! + bot.sendMessage( + msg.chat.id, text, + replyToMessageId = msg.messageId, + parseMode = ParseMode.HTML + ) + } + + fun broadcastToTG(text: String) { + allowedChats.forEach { chatID -> + bot.sendMessage(chatID, text, parseMode = ParseMode.HTML) + } + } + + fun sendMessageToTGFrom(username: String, text: String) { + allowedChats.forEach { chatID -> + bot.sendMessage( + chatID, + mcMessageStr(username, text), + parseMode = ParseMode.HTML + ) + } + } + + private fun onText(bot: Bot, update: Update) { + if (!chatToMC) return + val msg = update.message!! + plugin.sendMessageToMCFrom(rawUserMention(msg.from!!), msg.text!!) + } + + private fun mcMessageStr(username: String, text: String): String = + "${escapeHTML(username)}: $text" + + private fun rawUserMention(user: User): String = + (if (user.firstName.length < 2) null else user.firstName) + ?: user.username + ?: user.lastName!! + + companion object { + fun escapeHTML(s: String): String = + s.replace("&", "&").replace(">", ">").replace("<", "<") + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Utils.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Utils.kt deleted file mode 100644 index e2864af..0000000 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Utils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.kraftwerk28.spigot_tg_bridge - -import org.telegram.telegrambots.meta.api.objects.User - -fun escapeHTML(s: String): String = - s.replace("&", "&").replace(">", ">").replace("<", "<") - diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d3740a7..3e3b9b0 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -15,5 +15,5 @@ strings: joined: 'joined' left: 'left' commands: - time: '/time' - online: '/online' \ No newline at end of file + time: 'time' + online: 'online' \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 825d4fd..09340ed 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: SpigotTGBridge -version: 0.0.7 +version: 0.0.8 api-version: '1.15' main: org.kraftwerk28.spigot_tg_bridge.Plugin description: Telegram <-> Minecraft communication plugin for Spigot.