diff --git a/.gitignore b/.gitignore index f4bb371..ec22070 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ hs_err_pid* build/ .gradle/ local.properties +.idea/ diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/AsyncJavaPlugin.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/AsyncJavaPlugin.kt index 1d3e24e..4f15506 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/AsyncJavaPlugin.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/AsyncJavaPlugin.kt @@ -2,7 +2,6 @@ package org.kraftwerk28.spigot_tg_bridge import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.Job diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Configuration.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Configuration.kt index d17b2d4..6ee3367 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Configuration.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Configuration.kt @@ -37,7 +37,7 @@ class Configuration(plugin: Plugin) { // plugin.saveResource(C.configFilename, false); throw Exception(C.WARN.noConfigWarning) } - val pluginConfig = plugin.getConfig() + val pluginConfig = plugin.config pluginConfig.load(cfgFile) pluginConfig.getString("minecraftMessageFormat")?.let { diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/IgnAuth.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/IgnAuth.kt index 76eca98..4878c9c 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/IgnAuth.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/IgnAuth.kt @@ -18,8 +18,8 @@ create table if not exists user ( class IgnAuth( fileName: String, - val plugin: Plugin, - var conn: Connection? = null, + private val plugin: Plugin, + private var conn: Connection? = null, ) { init { plugin.launch { @@ -27,7 +27,7 @@ class IgnAuth( } } - suspend fun initializeConnection(fileName: String) = try { + private fun initializeConnection(fileName: String) = try { DriverManager.getConnection("jdbc:sqlite:$fileName").apply { createStatement().execute(INIT_DB_QUERY) }.also { @@ -37,11 +37,11 @@ class IgnAuth( plugin.logger.info(e.message) } - suspend fun close() = conn?.run { + fun close() = conn?.run { close() } - suspend fun linkUser( + fun linkUser( tgId: Long, tgUsername: String? = null, tgFirstName: String, @@ -70,21 +70,21 @@ class IgnAuth( execute() } ?: false - suspend fun getLinkedUserByIgn(ign: String) = + fun getLinkedUserByIgn(ign: String) = conn?.stmt("select * from user where mc_uuid = ?", ign)?.first { toLinkedUser() } - suspend fun getLinkedUserByTgId(id: Long) = + fun getLinkedUserByTgId(id: Long) = conn?.stmt("select * from user where tg_id = ?", id)?.first { toLinkedUser() } - suspend fun unlinkUserByTgId(id: Long) = + fun unlinkUserByTgId(id: Long) = conn?.stmt("delete from user where tg_id = ?", id)?.run { executeUpdate() > 0 } - suspend fun getAllLinkedUsers() = + fun getAllLinkedUsers() = conn?.stmt("select * from user")?.map { toLinkedUser() } } 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 4880eba..200246c 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Plugin.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/Plugin.kt @@ -1,45 +1,55 @@ package org.kraftwerk28.spigot_tg_bridge +import kotlinx.coroutines.delay import org.bukkit.event.HandlerList import java.lang.Exception +import kotlin.system.measureTimeMillis import org.kraftwerk28.spigot_tg_bridge.Constants as C class Plugin : AsyncJavaPlugin() { - var tgBot: TgBot? = null + private var tgBot: TgBot? = null private var eventHandler: EventHandler? = null - var config: Configuration? = null + private var config: Configuration? = null var ignAuth: IgnAuth? = null - override suspend fun onEnableAsync() = try { - config = Configuration(this).also { config -> - if (!config.isEnabled) return - - if (config.enableIgnAuth) { - val dbFilePath = dataFolder.resolve("spigot-tg-bridge.sqlite") - ignAuth = IgnAuth( - fileName = dbFilePath.absolutePath, - plugin = this, - ) - } - - tgBot?.run { stop() } - tgBot = TgBot(this, config).also { bot -> - bot.startPolling() - eventHandler = EventHandler(this, config, bot).also { - server.pluginManager.registerEvents(it, this) + override suspend fun onEnableAsync() { + try { + launch { + config = Configuration(this).also { + initializeWithConfig(it) } } + } catch (e: Exception) { + // Configuration file is missing or incomplete + logger.warning(e.message) + } + } - getCommand(C.COMMANDS.PLUGIN_RELOAD)?.run { - setExecutor(CommandHandler(this@Plugin)) - } - config.serverStartMessage?.let { - tgBot?.sendMessageToTelegram(it) + private suspend fun initializeWithConfig(config: Configuration) { + if (!config.isEnabled) return + + if (config.enableIgnAuth) { + val dbFilePath = dataFolder.resolve("spigot-tg-bridge.sqlite") + ignAuth = IgnAuth( + fileName = dbFilePath.absolutePath, + plugin = this, + ) + } + + tgBot?.run { stop() } + tgBot = TgBot(this, config).also { bot -> + bot.startPolling() + eventHandler = EventHandler(this, config, bot).also { + server.pluginManager.registerEvents(it, this) } } - } catch (e: Exception) { - // Configuration file is missing or incomplete - logger.warning(e.message) + + getCommand(C.COMMANDS.PLUGIN_RELOAD)?.run { + setExecutor(CommandHandler(this@Plugin)) + } + config.serverStartMessage?.let { + tgBot?.sendMessageToTelegram(it) + } } override suspend fun onDisableAsync() { @@ -52,6 +62,7 @@ class Plugin : AsyncJavaPlugin() { eventHandler?.let { HandlerList.unregisterAll(it) } tgBot?.run { stop() } tgBot = null + ignAuth?.close() } } diff --git a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/TgBot.kt b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/TgBot.kt index 725c532..f1d40fb 100644 --- a/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/TgBot.kt +++ b/src/main/kotlin/org/kraftwerk28/spigot_tg_bridge/TgBot.kt @@ -5,30 +5,35 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach -import kotlinx.coroutines.launch import okhttp3.OkHttpClient -import retrofit2.Call import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.time.Duration import org.kraftwerk28.spigot_tg_bridge.Constants as C -typealias UpdateRequest = Call>>? typealias CmdHandler = suspend (HandlerContext) -> Unit data class HandlerContext( val update: Update, val message: Message?, val chat: Chat?, - val commandArgs: List, + val commandArgs: List = listOf(), ) class TgBot( private val plugin: Plugin, private val config: Configuration, ) { - private val api: TgApiService - private val client: OkHttpClient + private val client: OkHttpClient = OkHttpClient + .Builder() + .readTimeout(Duration.ZERO) + .build() + private val api = Retrofit.Builder() + .baseUrl("https://api.telegram.org/bot${config.botToken}/") + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(TgApiService::class.java) private val updateChan = Channel() private var pollJob: Job? = null private var handlerJob: Job? = null @@ -46,24 +51,11 @@ class TgBot( ) } - init { - client = OkHttpClient - .Builder() - .readTimeout(Duration.ZERO) - .build() - api = Retrofit.Builder() - .baseUrl("https://api.telegram.org/bot${config.botToken}/") - .client(client) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(TgApiService::class.java) - } - private suspend fun initialize() { me = api.getMe().result!! // I intentionally don't put optional @username in regex // since bot is only used in group chats - commandRegex = """^\/(\w+)(?:@${me!!.username})(?:\s+(.+))?$""".toRegex() + commandRegex = """^/(\w+)@${me!!.username}(?:\s+(.+))?$""".toRegex() val commands = config.commands.run { listOf(time, online, chatID) } .zip( C.COMMAND_DESC.run { @@ -88,13 +80,13 @@ class TgBot( } private fun initPolling() = plugin.launch { - loop@ while (true) { + loop@while (true) { try { api.getUpdates( offset = currentOffset, timeout = config.pollTimeout, ).result?.let { updates -> - if (!updates.isEmpty()) { + if (updates.isNotEmpty()) { updates.forEach { updateChan.send(it) } currentOffset = updates.last().updateId + 1 } @@ -122,20 +114,19 @@ class TgBot( } } - suspend fun handleUpdate(update: Update) { - // Ignore PM or channel + private suspend fun handleUpdate(update: Update) { + // Ignore private message or channel post if (listOf("private", "channel").contains(update.message?.chat?.type)) return - var ctx = HandlerContext( + val ctx = HandlerContext( update, update.message, update.message?.chat, - listOf(), ) update.message?.text?.let { - commandRegex?.matchEntire(it)?.groupValues?.let { - commandMap.get(it[1])?.run { - val args = it[2].split("\\s+".toRegex()) + commandRegex?.matchEntire(it)?.groupValues?.let { matchList -> + commandMap[matchList[1]]?.run { + val args = matchList[2].split("\\s+".toRegex()) this(ctx.copy(commandArgs = args)) } } ?: run { @@ -204,7 +195,7 @@ class TgBot( private suspend fun linkIgnHandler(ctx: HandlerContext) { val tgUser = ctx.message!!.from!! val mcUuid = getMinecraftUuidByUsername(ctx.message.text!!) - if (mcUuid == null || ctx.commandArgs.size < 1) { + if (mcUuid == null || ctx.commandArgs.isEmpty()) { // Respond... return } @@ -236,7 +227,7 @@ class TgBot( } } - private suspend fun onTextHandler( + private fun onTextHandler( @Suppress("unused_parameter") ctx: HandlerContext ) { val msg = ctx.message!! @@ -258,10 +249,5 @@ class TgBot( config.allowedChats.forEach { chatId -> api.sendMessage(chatId, formatted) } - // plugin.launch { - // config.allowedChats.forEach { chatId -> - // api.sendMessage(chatId, formatted) - // } - // } } }