Przeglądaj źródła

added storing user cities in database

Vadik Sirekanyan 5 lat temu
rodzic
commit
ed33284f4e

+ 1 - 0
build.gradle.kts

@@ -15,6 +15,7 @@ dependencies {
     implementation("org.telegram:telegrambots:4.9.1")
     implementation("io.ktor:ktor-client-cio:1.4.0")
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
+    implementation("org.postgresql:postgresql:42.2.18")
     implementation("org.slf4j:slf4j-simple:1.7.30")
     testImplementation("junit:junit:4.13")
 }

+ 33 - 11
src/main/kotlin/com/sirekanyan/andersrobot/AndersRobot.kt

@@ -2,12 +2,9 @@ package com.sirekanyan.andersrobot
 
 import com.sirekanyan.andersrobot.api.WeatherApi
 import com.sirekanyan.andersrobot.config.Config
-import com.sirekanyan.andersrobot.config.ConfigKey.BOT_TOKEN
-import com.sirekanyan.andersrobot.config.ConfigKey.BOT_USERNAME
-import com.sirekanyan.andersrobot.extensions.getCityCommand
-import com.sirekanyan.andersrobot.extensions.isCelsiusCommand
-import com.sirekanyan.andersrobot.extensions.isWeatherCommand
-import com.sirekanyan.andersrobot.extensions.sendText
+import com.sirekanyan.andersrobot.config.ConfigKey.*
+import com.sirekanyan.andersrobot.extensions.*
+import com.sirekanyan.andersrobot.repository.CityRepositoryImpl
 import org.telegram.telegrambots.bots.DefaultAbsSender
 import org.telegram.telegrambots.bots.DefaultBotOptions
 import org.telegram.telegrambots.meta.api.objects.Update
@@ -19,6 +16,7 @@ val botName = Config[BOT_USERNAME]
 class AndersRobot : DefaultAbsSender(DefaultBotOptions()), LongPollingBot {
 
     private val weather = WeatherApi()
+    private val repository = CityRepositoryImpl(Config[DB_URL])
 
     override fun getBotUsername(): String = botName
 
@@ -27,10 +25,12 @@ class AndersRobot : DefaultAbsSender(DefaultBotOptions()), LongPollingBot {
     override fun onUpdateReceived(update: Update) {
         val message = update.message
         val chatId = message.chatId
-        println("${message.from?.id} => ${message.text}")
+        println("${message.from?.id} (chat $chatId) => ${message.text}")
         val isBetterAccuracy = message.chatId == 314085103L || message.chatId == 106547051L
         val accuracy = if (isBetterAccuracy) 1 else 0
         val cityCommand = getCityCommand(message.text)
+        val addCityCommand = getAddCityCommand(message.text)
+        val delCityCommand = getDelCityCommand(message.text)
         when {
             !cityCommand.isNullOrEmpty() -> {
                 val temperature = weather.getTemperature(cityCommand, accuracy)
@@ -40,18 +40,40 @@ class AndersRobot : DefaultAbsSender(DefaultBotOptions()), LongPollingBot {
                     sendText(chatId, listOf(temperature).joinToString("\n"))
                 }
             }
+            !addCityCommand.isNullOrEmpty() -> {
+                val temperature = weather.getTemperature(addCityCommand, accuracy)
+                if (temperature == null) {
+                    sendText(chatId, "Не знаю такого города")
+                } else {
+                    repository.putCity(chatId, addCityCommand)
+                    showWeather(chatId, accuracy)
+                }
+            }
+            !delCityCommand.isNullOrEmpty() -> {
+                if (repository.deleteCity(chatId, delCityCommand)) {
+                    sendText(chatId, "Удалено")
+                } else {
+                    sendText(chatId, "Нет такого города")
+                }
+            }
             isCelsiusCommand(message.text) -> {
                 sendText(chatId, "Можешь звать меня просто Андерс")
             }
             isWeatherCommand(message.text) -> {
-                val temperatures = weather.getTemperatures(accuracy)
-                if (temperatures.isNotEmpty()) {
-                    sendText(chatId, temperatures.joinToString("\n"))
-                }
+                showWeather(chatId, accuracy)
             }
         }
     }
 
+    private fun showWeather(chatId: Long, accuracy: Int) {
+        val dbCities = repository.getCities(chatId)
+        val cities = if (dbCities.isEmpty()) listOf("Moscow") else dbCities
+        val temperatures = weather.getTemperatures(cities, accuracy)
+        if (temperatures.isNotEmpty()) {
+            sendText(chatId, temperatures.joinToString("\n"))
+        }
+    }
+
     override fun clearWebhook() {
         WebhookUtils.clearWebhook(this)
     }

+ 2 - 9
src/main/kotlin/com/sirekanyan/andersrobot/api/WeatherApi.kt

@@ -10,13 +10,6 @@ import kotlinx.serialization.decodeFromString
 import kotlinx.serialization.json.Json
 
 private const val WEATHER_URL = "https://api.openweathermap.org/data/2.5/weather"
-private val CITIES = listOf(
-    "Miass",
-    "Chelyabinsk",
-    "Moscow",
-    "Yerevan",
-    "Boston",
-)
 
 class WeatherApi {
 
@@ -30,8 +23,8 @@ class WeatherApi {
         }
     }
 
-    fun getTemperatures(accuracy: Int): List<String> = runBlocking {
-        CITIES.associateWith { city ->
+    fun getTemperatures(cities: List<String>, accuracy: Int): List<String> = runBlocking {
+        cities.associateWith { city ->
             async { getWeather(city) }
         }.map { (city, weather) ->
             weather.await()?.let {

+ 1 - 0
src/main/kotlin/com/sirekanyan/andersrobot/config/ConfigKey.kt

@@ -5,4 +5,5 @@ enum class ConfigKey {
     BOT_TOKEN,
     ADMIN_ID,
     WEATHER_API_KEY,
+    DB_URL,
 }

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

@@ -7,6 +7,8 @@ private val REGEX = Regex("\\b(андерс|anders|погод[аеуы])\\b", IG
 private val DEGREE_REGEX = Regex("\\bградус", IGNORE_CASE)
 private val CELSIUS_REGEX = Regex("\\b(Celsi|Цельси)", IGNORE_CASE)
 private val CITY_REGEX = Regex("(/temp|погода) +(.+)", IGNORE_CASE)
+private val ADD_CITY_REGEX = Regex("(/add|добавить город) +(.+)", IGNORE_CASE)
+private val DEL_CITY_REGEX = Regex("(/del|удалить город) +(.+)", IGNORE_CASE)
 
 fun isWeatherCommand(text: String?): Boolean =
     text?.contains(REGEX) == true || text?.contains(DEGREE_REGEX) == true ||
@@ -17,3 +19,9 @@ fun isCelsiusCommand(text: String?): Boolean =
 
 fun getCityCommand(text: String?): String? =
     CITY_REGEX.matchEntire(text.orEmpty())?.groupValues?.get(2)
+
+fun getAddCityCommand(text: String?): String? =
+    ADD_CITY_REGEX.matchEntire(text.orEmpty())?.groupValues?.get(2)
+
+fun getDelCityCommand(text: String?): String? =
+    DEL_CITY_REGEX.matchEntire(text.orEmpty())?.groupValues?.get(2)

+ 68 - 0
src/main/kotlin/com/sirekanyan/andersrobot/repository/CityRepository.kt

@@ -0,0 +1,68 @@
+package com.sirekanyan.andersrobot.repository
+
+import java.sql.DriverManager
+import java.sql.ResultSet
+
+private const val CREATE_TABLE = """
+    create table if not exists cities (
+        chat bigint,
+        name varchar(200),
+        primary key (chat, name)
+    );
+    """
+private const val SELECT_CITIES =
+    "select name from cities where chat = ?"
+private const val INSERT_CITY =
+    "insert into cities (chat, name) values (?, ?) on conflict do nothing"
+private const val DELETE_CITY =
+    "delete from cities where chat = ? and name = ?"
+
+interface CityRepository {
+    fun getCities(chat: Long): List<String>
+    fun putCity(chat: Long, name: String): Boolean
+    fun deleteCity(chat: Long, name: String): Boolean
+}
+
+class CityRepositoryImpl(url: String) : CityRepository {
+
+    private val connection by lazy {
+        DriverManager.getConnection(url).also {
+            it.createStatement().execute(CREATE_TABLE)
+        }
+    }
+
+    override fun getCities(chat: Long): List<String> =
+        connection.prepareStatement(SELECT_CITIES)
+            .run {
+                setLong(1, chat)
+                executeQuery()
+            }
+            .map {
+                getString(1)
+            }
+
+    override fun putCity(chat: Long, name: String): Boolean =
+        connection.prepareStatement(INSERT_CITY)
+            .run {
+                setLong(1, chat)
+                setString(2, name)
+                executeUpdate() == 1
+            }
+
+    override fun deleteCity(chat: Long, name: String): Boolean =
+        connection.prepareStatement(DELETE_CITY)
+            .run {
+                setLong(1, chat)
+                setString(2, name)
+                executeUpdate() == 1
+            }
+
+    private fun <T> ResultSet.map(transform: ResultSet.() -> T): List<T> {
+        val list = mutableListOf<T>()
+        while (next()) {
+            list.add(transform())
+        }
+        return list
+    }
+
+}