Vadik Sirekanyan пре 4 година
родитељ
комит
87953b5eac

+ 19 - 9
src/main/kotlin/com/sirekanyan/andersrobot/AndersController.kt

@@ -2,6 +2,7 @@ package com.sirekanyan.andersrobot
 
 import com.sirekanyan.andersrobot.api.Forecast
 import com.sirekanyan.andersrobot.api.WeatherApi
+import com.sirekanyan.andersrobot.command.Command
 import com.sirekanyan.andersrobot.extensions.logError
 import com.sirekanyan.andersrobot.extensions.sendPhoto
 import com.sirekanyan.andersrobot.extensions.sendText
@@ -50,15 +51,6 @@ class AndersController(
         }
     }
 
-    fun onForecastCommand(city: String) {
-        val forecast = api.getForecast(city, language)
-        if (forecast == null) {
-            sender.sendText(chatId, "Не знаю такого города")
-        } else {
-            showForecast(chatId, forecast, locale)
-        }
-    }
-
     fun onAddCity(city: String) {
         val weather = api.getWeather(city, language)
         if (weather == null) {
@@ -78,6 +70,15 @@ class AndersController(
         }
     }
 
+    fun onForecastCommand(city: String) {
+        val forecast = api.getForecast(city, language)
+        if (forecast == null) {
+            sender.sendText(chatId, "Не знаю такого города")
+        } else {
+            showForecast(chatId, forecast, locale)
+        }
+    }
+
     fun onCelsiusCommand() {
         sender.sendText(chatId, "Можешь звать меня просто Андерс")
     }
@@ -86,11 +87,20 @@ class AndersController(
         showWeather(chatId, language)
     }
 
+    fun onCityMissing(command: Command) {
+        delayedCommands[chatId] = command
+        sender.sendText(chatId, "Какой город?")
+    }
+
     private fun showWeather(chatId: Long, language: String?) {
         val dbCities = repository.getCities(chatId)
         val cities = dbCities.ifEmpty { listOf(DEFAULT_CITY_ID) }
         val weathers = api.getWeathers(cities, language)
         check(weathers.isNotEmpty())
+        weathers.singleOrNull()?.let { weather ->
+            sender.sendWeather(chatId, weather)
+            return
+        }
         try {
             val file = File("weather-$chatId.png")
             ImageIO.write(generateImage(weathers), "png", file)

+ 26 - 5
src/main/kotlin/com/sirekanyan/andersrobot/AndersRobot.kt

@@ -1,5 +1,9 @@
 package com.sirekanyan.andersrobot
 
+import com.sirekanyan.andersrobot.command.CityCommand
+import com.sirekanyan.andersrobot.command.Command
+import com.sirekanyan.andersrobot.command.LocationCommand
+import com.sirekanyan.andersrobot.command.RegexCommand
 import com.sirekanyan.andersrobot.config.Config
 import com.sirekanyan.andersrobot.config.ConfigKey.*
 import com.sirekanyan.andersrobot.extensions.logError
@@ -12,6 +16,18 @@ import org.telegram.telegrambots.util.WebhookUtils
 
 val botName = Config[BOT_USERNAME]
 val adminId = Config[ADMIN_ID].toLong()
+val delayedCommands = mutableMapOf<Long, Command>()
+private val userCommands: List<Command> =
+    listOf(
+        LocationCommand,
+        RegexCommand("^(/temp(@$botName)?|погода)$", AndersController::onWeatherCommand),
+        CityCommand("/temp", "погода", AndersController::onCityCommand),
+        CityCommand("/add", "добавить город", AndersController::onAddCity),
+        CityCommand("/del", "удалить город", AndersController::onDeleteCity),
+        CityCommand("/forecast", "прогноз", AndersController::onForecastCommand),
+        RegexCommand("\\b(celsi|цельси)", AndersController::onCelsiusCommand),
+        RegexCommand("\\b((андерс|anders|погод[аеуы])\\b|градус)", AndersController::onWeatherCommand),
+    )
 
 class AndersRobot : DefaultAbsSender(DefaultBotOptions()), LongPollingBot {
 
@@ -31,16 +47,21 @@ class AndersRobot : DefaultAbsSender(DefaultBotOptions()), LongPollingBot {
     }
 
     private fun onUpdate(update: Update) {
-        val message = update.message
-        if (update.hasMessage()) {
-            println("${message.from?.id} (chat ${message.chatId}) => ${message.text}")
-        } else {
+        val message = update.message ?: run {
             println("Update is ignored: message is empty")
             return
         }
+        val chatId = message.chatId
+        val text = message.text
+        println("${message.from?.id} (chat $chatId) => $text")
         val controller = factory.createController(this, update)
+        delayedCommands[chatId]?.let { command ->
+            delayedCommands.remove(chatId)
+            command.execute(controller, text)
+            return
+        }
         for (command in userCommands) {
-            if (command.execute(controller, update.message)) {
+            if (command.execute(controller, message)) {
                 return
             }
         }

+ 0 - 95
src/main/kotlin/com/sirekanyan/andersrobot/Command.kt

@@ -1,95 +0,0 @@
-package com.sirekanyan.andersrobot
-
-import com.sirekanyan.andersrobot.extensions.isCelsiusCommand
-import com.sirekanyan.andersrobot.extensions.isWeatherCommand
-import com.sirekanyan.andersrobot.extensions.parseCityArgument
-import org.telegram.telegrambots.meta.api.objects.Message
-
-val userCommands =
-    listOf(
-        LocationCommand,
-        CityCommand,
-        ForecastCommand,
-        AddCityCommand,
-        DeleteCityCommand,
-        CelsiusCommand,
-        WeatherCommand,
-    )
-
-interface Command {
-    fun execute(controller: AndersController, message: Message): Boolean
-}
-
-object LocationCommand : Command {
-    override fun execute(controller: AndersController, message: Message): Boolean {
-        if (message.hasLocation()) {
-            controller.onLocationCommand(message.location)
-            return true
-        }
-        return false
-    }
-}
-
-object CityCommand : Command {
-    override fun execute(controller: AndersController, message: Message): Boolean {
-        val city = parseCityArgument(message.text, "/temp", "погода")
-        when {
-            city.isNullOrEmpty() -> return false
-            else -> controller.onCityCommand(city)
-        }
-        return true
-    }
-}
-
-object ForecastCommand : Command {
-    override fun execute(controller: AndersController, message: Message): Boolean {
-        val city = parseCityArgument(message.text, "/forecast", "прогноз")
-        when {
-            city.isNullOrEmpty() -> return false
-            else -> controller.onForecastCommand(city)
-        }
-        return true
-    }
-}
-
-object AddCityCommand : Command {
-    override fun execute(controller: AndersController, message: Message): Boolean {
-        val city = parseCityArgument(message.text, "/add", "добавить город")
-        when {
-            city.isNullOrEmpty() -> return false
-            else -> controller.onAddCity(city)
-        }
-        return true
-    }
-}
-
-object DeleteCityCommand : Command {
-    override fun execute(controller: AndersController, message: Message): Boolean {
-        val city = parseCityArgument(message.text, "/del", "удалить город")
-        when {
-            city.isNullOrEmpty() -> return false
-            else -> controller.onDeleteCity(city)
-        }
-        return true
-    }
-}
-
-object CelsiusCommand : Command {
-    override fun execute(controller: AndersController, message: Message): Boolean {
-        if (isCelsiusCommand(message.text)) {
-            controller.onCelsiusCommand()
-            return true
-        }
-        return false
-    }
-}
-
-object WeatherCommand : Command {
-    override fun execute(controller: AndersController, message: Message): Boolean {
-        if (isWeatherCommand(message.text)) {
-            controller.onWeatherCommand()
-            return true
-        }
-        return false
-    }
-}

+ 30 - 0
src/main/kotlin/com/sirekanyan/andersrobot/command/CityCommand.kt

@@ -0,0 +1,30 @@
+package com.sirekanyan.andersrobot.command
+
+import com.sirekanyan.andersrobot.AndersController
+import com.sirekanyan.andersrobot.botName
+import org.telegram.telegrambots.meta.api.objects.Message
+
+class CityCommand(
+    private val en: String,
+    private val ru: String,
+    private val action: (AndersController, String) -> Unit,
+) : Command {
+
+    override fun execute(controller: AndersController, message: Message): Boolean =
+        execute(controller, parseCityArgument(message.text, en, ru))
+
+    override fun execute(controller: AndersController, arguments: String?): Boolean {
+        when {
+            arguments == null -> return false
+            arguments.isBlank() -> controller.onCityMissing(this)
+            else -> action(controller, arguments)
+        }
+        return true
+    }
+
+    private fun parseCityArgument(text: String?, en: String, ru: String): String? {
+        val regex = Regex("($en(@$botName)?|$ru) ?(.*)", RegexOption.IGNORE_CASE)
+        return regex.matchEntire(text.orEmpty())?.groupValues?.get(3)
+    }
+
+}

+ 14 - 0
src/main/kotlin/com/sirekanyan/andersrobot/command/Command.kt

@@ -0,0 +1,14 @@
+package com.sirekanyan.andersrobot.command
+
+import com.sirekanyan.andersrobot.AndersController
+import org.telegram.telegrambots.meta.api.objects.Message
+
+interface Command {
+
+    fun execute(controller: AndersController, message: Message): Boolean
+
+    fun execute(controller: AndersController, arguments: String?): Boolean {
+        throw UnsupportedOperationException()
+    }
+
+}

+ 14 - 0
src/main/kotlin/com/sirekanyan/andersrobot/command/LocationCommand.kt

@@ -0,0 +1,14 @@
+package com.sirekanyan.andersrobot.command
+
+import com.sirekanyan.andersrobot.AndersController
+import org.telegram.telegrambots.meta.api.objects.Message
+
+object LocationCommand : Command {
+    override fun execute(controller: AndersController, message: Message): Boolean {
+        if (message.hasLocation()) {
+            controller.onLocationCommand(message.location)
+            return true
+        }
+        return false
+    }
+}

+ 18 - 0
src/main/kotlin/com/sirekanyan/andersrobot/command/RegexCommand.kt

@@ -0,0 +1,18 @@
+package com.sirekanyan.andersrobot.command
+
+import com.sirekanyan.andersrobot.AndersController
+import org.telegram.telegrambots.meta.api.objects.Message
+import kotlin.text.RegexOption.IGNORE_CASE
+
+class RegexCommand(
+    private val pattern: String,
+    private val action: (AndersController) -> Unit,
+) : Command {
+    override fun execute(controller: AndersController, message: Message): Boolean {
+        if (message.text?.contains(Regex(pattern, IGNORE_CASE)) == true) {
+            action(controller)
+            return true
+        }
+        return false
+    }
+}

+ 0 - 20
src/main/kotlin/com/sirekanyan/andersrobot/extensions/String.kt

@@ -1,20 +0,0 @@
-package com.sirekanyan.andersrobot.extensions
-
-import com.sirekanyan.andersrobot.botName
-import kotlin.text.RegexOption.IGNORE_CASE
-
-private val REGEX = Regex("\\b(андерс|anders|погод[аеуы])\\b", IGNORE_CASE)
-private val DEGREE_REGEX = Regex("\\bградус", IGNORE_CASE)
-private val CELSIUS_REGEX = Regex("\\b(Celsi|Цельси)", IGNORE_CASE)
-
-fun isWeatherCommand(text: String?): Boolean =
-    text?.contains(REGEX) == true || text?.contains(DEGREE_REGEX) == true ||
-            text == "/temp" || text == "/temp@$botName"
-
-fun isCelsiusCommand(text: String?): Boolean =
-    text?.contains(CELSIUS_REGEX) == true
-
-fun parseCityArgument(text: String?, en: String, ru: String): String? {
-    val regex = Regex("($en(@${botName})?|$ru) +(.+)", IGNORE_CASE)
-    return regex.matchEntire(text.orEmpty())?.groupValues?.get(3)
-}