Procházet zdrojové kódy

Moved bot logic to controller

Vadik Sirekanyan před 4 roky
rodič
revize
069ab5b05d

+ 99 - 0
src/main/kotlin/com/sirekanyan/andersrobot/AndersController.kt

@@ -0,0 +1,99 @@
+package com.sirekanyan.andersrobot
+
+import com.sirekanyan.andersrobot.api.Forecast
+import com.sirekanyan.andersrobot.api.WeatherApi
+import com.sirekanyan.andersrobot.extensions.sendPhoto
+import com.sirekanyan.andersrobot.extensions.sendText
+import com.sirekanyan.andersrobot.extensions.sendWeather
+import com.sirekanyan.andersrobot.repository.CityRepository
+import com.sirekanyan.andersrobot.repository.supportedLanguages
+import jetbrains.letsPlot.export.ggsave
+import org.telegram.telegrambots.meta.api.objects.Location
+import org.telegram.telegrambots.meta.api.objects.Update
+import org.telegram.telegrambots.meta.bots.AbsSender
+import java.io.File
+import java.util.*
+
+private const val DEFAULT_CITY_ID = 524901L // Moscow
+
+class AndersController(
+    private val api: WeatherApi,
+    private val repository: CityRepository,
+    private val sender: AbsSender,
+    update: Update,
+) {
+
+    private val chatId = update.message.chatId
+    private val languageCode = update.message.from?.languageCode
+    private val language = languageCode?.takeIf { it in supportedLanguages }
+    private val locale = Locale.forLanguageTag(languageCode.orEmpty())
+
+    fun onLocationCommand(location: Location) {
+        val weather = api.getWeather(location, language)
+        if (weather == null) {
+            sender.sendText(chatId, "Не знаю такого места")
+        } else {
+            sender.sendWeather(chatId, weather)
+        }
+    }
+
+    fun onCityCommand(city: String) {
+        val weather = api.getWeather(city, language)
+        if (weather == null) {
+            sender.sendText(chatId, "Не знаю такого города")
+        } else {
+            sender.sendWeather(chatId, weather)
+        }
+    }
+
+    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) {
+            sender.sendText(chatId, "Не знаю такого города")
+        } else {
+            repository.putCity(chatId, weather.id)
+            showWeather(chatId, language)
+        }
+    }
+
+    fun onDeleteCity(city: String) {
+        val temperature = api.getWeather(city, language)
+        when {
+            temperature == null -> sender.sendText(chatId, "Не знаю такого города")
+            repository.deleteCity(chatId, temperature.id) -> sender.sendText(chatId, "Удалено")
+            else -> sender.sendText(chatId, "Нет такого города")
+        }
+    }
+
+    fun onCelsiusCommand() {
+        sender.sendText(chatId, "Можешь звать меня просто Андерс")
+    }
+
+    fun onWeatherCommand() {
+        showWeather(chatId, language)
+    }
+
+    private fun showWeather(chatId: Long, language: String?) {
+        val dbCities = repository.getCities(chatId)
+        val cities = dbCities.ifEmpty { listOf(DEFAULT_CITY_ID) }
+        val temperatures = api.getWeathers(cities, language)
+        check(temperatures.isNotEmpty())
+        sender.sendText(chatId, temperatures.joinToString("\n") { it.format() })
+    }
+
+    private fun showForecast(chatId: Long, forecast: Forecast, locale: Locale) {
+        val city = forecast.city.name
+        ggsave(plotForecast(forecast, locale), "$city.png")
+        sender.sendPhoto(chatId, File("lets-plot-images/$city.png"))
+    }
+
+}

+ 17 - 0
src/main/kotlin/com/sirekanyan/andersrobot/AndersControllerFactory.kt

@@ -0,0 +1,17 @@
+package com.sirekanyan.andersrobot
+
+import com.sirekanyan.andersrobot.api.WeatherApi
+import com.sirekanyan.andersrobot.config.Config
+import com.sirekanyan.andersrobot.config.ConfigKey.DB_URL
+import com.sirekanyan.andersrobot.repository.CityRepositoryImpl
+import org.telegram.telegrambots.meta.api.objects.Update
+
+class AndersControllerFactory {
+
+    private val api = WeatherApi()
+    private val repository = CityRepositoryImpl(Config[DB_URL])
+
+    fun createController(sender: AndersRobot, update: Update): AndersController =
+        AndersController(api, repository, sender, update)
+
+}

+ 13 - 82
src/main/kotlin/com/sirekanyan/andersrobot/AndersRobot.kt

@@ -1,30 +1,21 @@
 package com.sirekanyan.andersrobot
 
-import com.sirekanyan.andersrobot.api.Forecast
-import com.sirekanyan.andersrobot.api.WeatherApi
 import com.sirekanyan.andersrobot.config.Config
 import com.sirekanyan.andersrobot.config.ConfigKey.*
-import com.sirekanyan.andersrobot.extensions.*
-import com.sirekanyan.andersrobot.repository.CityRepositoryImpl
-import com.sirekanyan.andersrobot.repository.supportedLanguages
-import jetbrains.letsPlot.export.ggsave
+import com.sirekanyan.andersrobot.extensions.logError
+import com.sirekanyan.andersrobot.extensions.logInfo
 import org.telegram.telegrambots.bots.DefaultAbsSender
 import org.telegram.telegrambots.bots.DefaultBotOptions
 import org.telegram.telegrambots.meta.api.objects.Update
 import org.telegram.telegrambots.meta.generics.LongPollingBot
 import org.telegram.telegrambots.util.WebhookUtils
-import java.io.File
-import java.util.*
-
-private const val DEFAULT_CITY_ID = 524901L // Moscow
 
 val botName = Config[BOT_USERNAME]
 val adminId = Config[ADMIN_ID].toLong()
 
 class AndersRobot : DefaultAbsSender(DefaultBotOptions()), LongPollingBot {
 
-    private val api = WeatherApi()
-    private val repository = CityRepositoryImpl(Config[DB_URL])
+    private val factory = AndersControllerFactory()
 
     override fun getBotUsername(): String = botName
 
@@ -41,80 +32,20 @@ class AndersRobot : DefaultAbsSender(DefaultBotOptions()), LongPollingBot {
 
     private fun onUpdate(update: Update) {
         val message = update.message
-        val chatId = message.chatId
-        val languageCode = message.from?.languageCode
-        val language = languageCode?.takeIf { it in supportedLanguages }
-        val locale = Locale.forLanguageTag(languageCode.orEmpty())
-        println("${message.from?.id} (chat $chatId) => ${message.text}")
-        val cityCommand = getCityCommand(message.text)
-        val forecastCommand = getForecastCommand(message.text)
-        val addCityCommand = getAddCityCommand(message.text)
-        val delCityCommand = getDelCityCommand(message.text)
-        when {
-            message.hasLocation() -> {
-                val weather = api.getWeather(message.location, language)
-                if (weather == null) {
-                    sendText(chatId, "Не знаю такого места")
-                } else {
-                    sendWeather(chatId, weather)
-                }
-            }
-            !cityCommand.isNullOrEmpty() -> {
-                val weather = api.getWeather(cityCommand, language)
-                if (weather == null) {
-                    sendText(chatId, "Не знаю такого города")
-                } else {
-                    sendWeather(chatId, weather)
-                }
-            }
-            !forecastCommand.isNullOrEmpty() -> {
-                val forecast = api.getForecast(forecastCommand, language)
-                if (forecast == null) {
-                    sendText(chatId, "Не знаю такого города")
-                } else {
-                    showForecast(chatId, forecast, locale)
-                }
-            }
-            !addCityCommand.isNullOrEmpty() -> {
-                val weather = api.getWeather(addCityCommand, language)
-                if (weather == null) {
-                    sendText(chatId, "Не знаю такого города")
-                } else {
-                    repository.putCity(chatId, weather.id)
-                    showWeather(chatId, language)
-                }
-            }
-            !delCityCommand.isNullOrEmpty() -> {
-                val temperature = api.getWeather(delCityCommand, language)
-                when {
-                    temperature == null -> sendText(chatId, "Не знаю такого города")
-                    repository.deleteCity(chatId, temperature.id) -> sendText(chatId, "Удалено")
-                    else -> sendText(chatId, "Нет такого города")
-                }
-            }
-            isCelsiusCommand(message.text) -> {
-                sendText(chatId, "Можешь звать меня просто Андерс")
-            }
-            isWeatherCommand(message.text) -> {
-                showWeather(chatId, language)
+        if (update.hasMessage()) {
+            println("${message.from?.id} (chat ${message.chatId}) => ${message.text}")
+        } else {
+            println("Update is ignored: message is empty")
+            return
+        }
+        val controller = factory.createController(this, update)
+        for (command in userCommands) {
+            if (command.execute(controller, update.message)) {
+                return
             }
         }
     }
 
-    private fun showWeather(chatId: Long, language: String?) {
-        val dbCities = repository.getCities(chatId)
-        val cities = if (dbCities.isEmpty()) listOf(DEFAULT_CITY_ID) else dbCities
-        val temperatures = api.getWeathers(cities, language)
-        check(temperatures.isNotEmpty())
-        sendText(chatId, temperatures.joinToString("\n") { it.format() })
-    }
-
-    private fun showForecast(chatId: Long, forecast: Forecast, locale: Locale) {
-        val city = forecast.city.name
-        ggsave(plotForecast(forecast, locale), "$city.png")
-        sendPhoto(chatId, File("lets-plot-images/$city.png"))
-    }
-
     override fun clearWebhook() {
         logInfo("Cleared.")
         WebhookUtils.clearWebhook(this)

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

@@ -0,0 +1,93 @@
+package com.sirekanyan.andersrobot
+
+import com.sirekanyan.andersrobot.extensions.*
+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 = getCityCommand(message.text)
+        when {
+            city.isNullOrEmpty() -> return false
+            else -> controller.onCityCommand(city)
+        }
+        return true
+    }
+}
+
+object ForecastCommand : Command {
+    override fun execute(controller: AndersController, message: Message): Boolean {
+        val city = getForecastCommand(message.text)
+        when {
+            city.isNullOrEmpty() -> return false
+            else -> controller.onForecastCommand(city)
+        }
+        return true
+    }
+}
+
+object AddCityCommand : Command {
+    override fun execute(controller: AndersController, message: Message): Boolean {
+        val city = getAddCityCommand(message.text)
+        when {
+            city.isNullOrEmpty() -> return false
+            else -> controller.onAddCity(city)
+        }
+        return true
+    }
+}
+
+object DeleteCityCommand : Command {
+    override fun execute(controller: AndersController, message: Message): Boolean {
+        val city = getDelCityCommand(message.text)
+        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
+    }
+}