feature: release 0.0.8

This commit is contained in:
kraftwerk28 2020-07-11 23:57:58 +03:00
parent 7ba216f98c
commit 6cffdf4b33
9 changed files with 173 additions and 195 deletions

View File

@ -47,11 +47,10 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation group: 'junit', name: 'junit', version: '4.12'
compileOnly "org.spigotmc:spigot-api:1.15.2-R0.1-SNAPSHOT" 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' implementation 'com.vdurmont:emoji-java:5.1.1'
// def tgBotVer = '5.0.0' def tgBotVer = '5.0.0'
// implementation "io.github.kotlin-telegram-bot.kotlin-telegram-bot:telegram:$tgBotVer" implementation "io.github.kotlin-telegram-bot.kotlin-telegram-bot:telegram:$tgBotVer"
} }
compileKotlin { compileKotlin {

View File

@ -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<Long>
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 =
"<b>$username</b>: $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 "<a href=\"tg://user?id=${user.id}\">${user.firstName ?: user.lastName}</a>"
}

View File

@ -1,48 +1,48 @@
package org.kraftwerk28.spigot_tg_bridge package org.kraftwerk28.spigot_tg_bridge
object Constants { object Constants {
val configFilename = "config.yml" const val configFilename = "config.yml"
// Config field names // Config field names
object FIELDS { object FIELDS {
val BOT_TOKEN = "botToken" const val BOT_TOKEN = "botToken"
val BOT_USERNAME = "botUsername" const val BOT_USERNAME = "botUsername"
val ALLOWED_CHATS = "chats" const val ALLOWED_CHATS = "chats"
val LOG_FROM_MC_TO_TG = "logFromMCtoTG" const val LOG_FROM_MC_TO_TG = "logFromMCtoTG"
val LOG_FROM_TG_TO_MC = "logFromTGtoMC" const val LOG_FROM_TG_TO_MC = "logFromTGtoMC"
val SERVER_START_MSG = "serverStartMessage" const val SERVER_START_MSG = "serverStartMessage"
val SERVER_STOP_MSG = "serverStopMessage" const val SERVER_STOP_MSG = "serverStopMessage"
val LOG_JOIN_LEAVE = "logJoinLeave" const val LOG_JOIN_LEAVE = "logJoinLeave"
val LOG_PLAYER_DEATH = "logPlayerDeath" const val LOG_PLAYER_DEATH = "logPlayerDeath"
val LOG_PLAYER_ASLEEP = "logPlayerAsleep" const val LOG_PLAYER_ASLEEP = "logPlayerAsleep"
object STRINGS { object STRINGS {
val ONLINE = "strings.online" const val ONLINE = "strings.online"
val OFFLINE = "strings.offline" const val OFFLINE = "strings.offline"
val JOINED = "strings.joined" const val JOINED = "strings.joined"
val LEFT = "strings.left" const val LEFT = "strings.left"
} }
object COMMANDS { object COMMANDS {
val TIME = "commands.time" const val TIME = "commands.time"
val ONLINE = "commands.online" const val ONLINE = "commands.online"
} }
} }
object DEFS { object DEFS {
val logFromMCtoTG = false const val logFromMCtoTG = false
val logFromTGtoMC = false const val logFromTGtoMC = false
val logJoinLeave = false const val logJoinLeave = false
val logPlayerDeath = false const val logPlayerDeath = false
val logPlayerAsleep = false const val logPlayerAsleep = false
object COMMANDS { object COMMANDS {
val TIME = "/time" const val TIME = "time"
val ONLINE = "/online" const val ONLINE = "online"
} }
val playersOnline = "Online" const val playersOnline = "Online"
val nobodyOnline = "Nobody online" const val nobodyOnline = "Nobody online"
val playerJoined = "joined" const val playerJoined = "joined"
val playerLeft = "left" const val playerLeft = "left"
} }
object WARN { object WARN {
val noConfigWarning = "No config file found! Writing default config to config.yml." const val noConfigWarning = "No config file found! Writing default config to config.yml."
val noToken = "Bot token must be defined." const val noToken = "Bot token must be defined."
val noUsername = "Bot username must be defined." const val noUsername = "Bot username must be defined."
} }
} }

View File

@ -31,8 +31,8 @@ class EventHandler(private val plugin: Plugin) : Listener {
@EventHandler @EventHandler
fun onPlayerChat(event: AsyncPlayerChatEvent) { fun onPlayerChat(event: AsyncPlayerChatEvent) {
if (plugin.chatToTG) { if (plugin.chatToTG) {
plugin.tgBot?.sendMessageToTGFrom( plugin.tgBot.sendMessageToTGFrom(
escapeHTML(event.player.displayName), event.message event.player.displayName, event.message
) )
} }
} }
@ -40,15 +40,15 @@ class EventHandler(private val plugin: Plugin) : Listener {
@EventHandler @EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) { fun onPlayerJoin(event: PlayerJoinEvent) {
if (!logJoinLeave) return if (!logJoinLeave) return
val text = "<b>${escapeHTML(event.player.displayName)}</b> $joinStr." val text = "<b>${TgBot.escapeHTML(event.player.displayName)}</b> $joinStr."
plugin.tgBot?.broadcastToTG(text) plugin.tgBot.broadcastToTG(text)
} }
@EventHandler @EventHandler
fun onPlayerLeave(event: PlayerQuitEvent) { fun onPlayerLeave(event: PlayerQuitEvent) {
if (!logJoinLeave) return if (!logJoinLeave) return
val text = "<b>${escapeHTML(event.player.displayName)}</b> $leftStr." val text = "<b>${TgBot.escapeHTML(event.player.displayName)}</b> $leftStr."
plugin.tgBot?.broadcastToTG(text) plugin.tgBot.broadcastToTG(text)
} }
@EventHandler @EventHandler
@ -57,7 +57,7 @@ class EventHandler(private val plugin: Plugin) : Listener {
event.deathMessage?.let { event.deathMessage?.let {
val plName = event.entity.displayName val plName = event.entity.displayName
val text = it.replace(plName, "<b>$plName</b>") val text = it.replace(plName, "<b>$plName</b>")
plugin.tgBot?.broadcastToTG(text) plugin.tgBot.broadcastToTG(text)
} }
} }
@ -66,6 +66,6 @@ class EventHandler(private val plugin: Plugin) : Listener {
if (!logPlayerAsleep) return if (!logPlayerAsleep) return
if (event.bedEnterResult != PlayerBedEnterEvent.BedEnterResult.OK) return if (event.bedEnterResult != PlayerBedEnterEvent.BedEnterResult.OK) return
val text = "<b>${event.player.displayName}</b> fell asleep." val text = "<b>${event.player.displayName}</b> fell asleep."
plugin.tgBot?.broadcastToTG(text) plugin.tgBot.broadcastToTG(text)
} }
} }

View File

@ -2,14 +2,12 @@ package org.kraftwerk28.spigot_tg_bridge
import com.vdurmont.emoji.EmojiParser import com.vdurmont.emoji.EmojiParser
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.telegram.telegrambots.ApiContextInitializer
import org.telegram.telegrambots.meta.TelegramBotsApi
import java.io.File import java.io.File
import org.kraftwerk28.spigot_tg_bridge.Constants as C import org.kraftwerk28.spigot_tg_bridge.Constants as C
class Plugin : JavaPlugin() { class Plugin : JavaPlugin() {
var tgBot: Bot? = null lateinit var tgBot: TgBot
var chatToTG: Boolean = false var chatToTG: Boolean = false
init { init {
@ -32,26 +30,19 @@ class Plugin : JavaPlugin() {
return return
} }
ApiContextInitializer.init() tgBot = TgBot(this)
val botsApi = TelegramBotsApi()
tgBot = Bot(this)
botsApi.registerBot(tgBot)
server.pluginManager.registerEvents(EventHandler(this), this) server.pluginManager.registerEvents(EventHandler(this), this)
// Notify everything about server start // Notify everything about server start
config.getString(C.FIELDS.SERVER_START_MSG, null)?.let { 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.") logger.info("Plugin started.")
} }
override fun onDisable() { override fun onDisable() {
config.getString(C.FIELDS.SERVER_STOP_MSG, null)?.let { 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.") logger.info("Plugin stopped.")
} }
@ -62,10 +53,10 @@ class Plugin : JavaPlugin() {
} }
fun sendMessageToMCFrom(username: String, text: String) { fun sendMessageToMCFrom(username: String, text: String) {
val text = run { server.broadcastMessage(
val text = "<${escapeHTML(username)}> $text" EmojiParser.parseToAliases(
EmojiParser.parseToAliases(text) "<${TgBot.escapeHTML(username)}> $text"
} )
server.broadcastMessage(text) )
} }
} }

View File

@ -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<Long>
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 =
"<b>${escapeHTML(username)}</b>: $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("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
}
}

View File

@ -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("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")

View File

@ -15,5 +15,5 @@ strings:
joined: 'joined' joined: 'joined'
left: 'left' left: 'left'
commands: commands:
time: '/time' time: 'time'
online: '/online' online: 'online'

View File

@ -1,5 +1,5 @@
name: SpigotTGBridge name: SpigotTGBridge
version: 0.0.7 version: 0.0.8
api-version: '1.15' api-version: '1.15'
main: org.kraftwerk28.spigot_tg_bridge.Plugin main: org.kraftwerk28.spigot_tg_bridge.Plugin
description: Telegram <-> Minecraft communication plugin for Spigot. description: Telegram <-> Minecraft communication plugin for Spigot.