diff --git a/.idea/artifacts/moztw_space_bot_jar.xml b/.idea/artifacts/moztw_space_bot_jar.xml
index 6b8304d..5b6a718 100644
--- a/.idea/artifacts/moztw_space_bot_jar.xml
+++ b/.idea/artifacts/moztw_space_bot_jar.xml
@@ -2,55 +2,62 @@
$PROJECT_DIR$/out/artifacts/moztw_space_bot_jar
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
+
+
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
-
-
+
+
+
-
-
-
-
-
+
-
+
+
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/moztw-space-bot.main.iml b/.idea/modules/moztw-space-bot.main.iml
index 40b7226..02f6461 100644
--- a/.idea/modules/moztw-space-bot.main.iml
+++ b/.idea/modules/moztw-space-bot.main.iml
@@ -2,23 +2,34 @@
-
-
+
+
+
+
+
-
-
+
+
-
@@ -34,11 +45,15 @@
-
+
-
-
+
+
+
+
+
+
@@ -52,6 +67,9 @@
+
+
+
diff --git a/.idea/modules/moztw-space-bot.test.iml b/.idea/modules/moztw-space-bot.test.iml
index f6bf55d..5015084 100644
--- a/.idea/modules/moztw-space-bot.test.iml
+++ b/.idea/modules/moztw-space-bot.test.iml
@@ -2,23 +2,40 @@
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
-
@@ -33,12 +50,16 @@
-
+
+
+
-
-
+
+
+
+
@@ -52,9 +73,12 @@
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 412b465..8fa3ab1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
var kotlin_version: String by extra
- kotlin_version = "1.2.30"
+ kotlin_version = "1.3.50"
repositories {
mavenCentral()
@@ -27,12 +27,15 @@ val testImplementation by configurations
repositories {
mavenCentral()
+ jcenter()
}
dependencies {
implementation(kotlin("stdlib-jdk8", kotlin_version))
implementation("commons-cli", "commons-cli", "1.4")
implementation("org.telegram", "telegrambots", "3.6")
+ implementation("com.squareup.okhttp3", "okhttp", "4.2.1")
+ implementation("com.beust", "klaxon", "5.0.13")
testImplementation("org.junit.jupiter", "junit-jupiter-api", "5.1.0")
}
diff --git a/src/main/kotlin/org/moztw/bot/telegram/space/Bot.kt b/src/main/kotlin/org/moztw/bot/telegram/space/Bot.kt
index 02f6129..bd6665f 100644
--- a/src/main/kotlin/org/moztw/bot/telegram/space/Bot.kt
+++ b/src/main/kotlin/org/moztw/bot/telegram/space/Bot.kt
@@ -1,7 +1,13 @@
package org.moztw.bot.telegram.space
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.moztw.bot.telegram.space.co2.SpaceCo2
+import org.telegram.telegrambots.api.methods.ActionType
import org.telegram.telegrambots.api.methods.groupadministration.SetChatTitle
+import org.telegram.telegrambots.api.methods.send.SendChatAction
import org.telegram.telegrambots.api.methods.send.SendMessage
+import org.telegram.telegrambots.api.methods.send.SendPhoto
import org.telegram.telegrambots.api.objects.CallbackQuery
import org.telegram.telegrambots.api.objects.Chat
import org.telegram.telegrambots.api.objects.Message
@@ -9,6 +15,9 @@ import org.telegram.telegrambots.api.objects.Update
import org.telegram.telegrambots.bots.TelegramLongPollingBot
import org.telegram.telegrambots.exceptions.TelegramApiException
import java.text.SimpleDateFormat
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
import java.util.*
internal class Bot(val username: String, val token: String) : TelegramLongPollingBot() {
@@ -32,26 +41,82 @@ internal class Bot(val username: String, val token: String) : TelegramLongPollin
}
private fun onMessageReceived(message: Message): Boolean {
- if (isAdminChat(message.chat) && message.hasText()) {
- if (isCommandOpen(message.text)) {
- if (tryExecute(Caption().getCaptionOpened(chatId = generalChatId))
- && tryExecute(Reply().getGeneralMessageOpen(chatId = generalChatId, operator = message.from))
- && tryExecute(Reply().getMessageOpen(message = message))) {
- for (chatId in adminChats)
- if (chatId != message.chatId)
- if (!tryExecute(Reply().getOtherMessageOpen(message = message, chatId = chatId, operator = message.from)))
- return false
- return true
+ if (message.hasText()) {
+ if (isCommandCO2(message.text)) {
+ try {
+ execute(SendChatAction().apply {
+ chatId = message.chat.id.toString()
+ action = ActionType.TYPING
+ })
+ } catch (e: TelegramApiException) {
+ e.printStackTrace()
}
- } else if (isCommandClose(message.text)) {
- if (tryExecute(Caption().getCaptionClosed(chatId = generalChatId))
- && tryExecute(Reply().getGeneralMessageClose(chatId = generalChatId, operator = message.from))
- && tryExecute(Reply().getMessageClose(message = message))) {
- for (chatId in adminChats)
- if (chatId != message.chatId)
- if (!tryExecute(Reply().getOtherMessageClose(message = message, chatId = chatId, operator = message.from)))
- return false
- return true
+
+ var messageText = "二氧化碳含量取得失敗,本服務暫時無法使用。"
+ var imageUrl = ""
+ try {
+ SpaceCo2().fetchCo2()?.let { co2 ->
+ val last = co2.feeds.last()
+ val lastUpdate = ZonedDateTime
+ .parse(last.created_at)
+ .withZoneSameInstant(ZoneId.of("Asia/Taipei"))
+ .format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"))
+ messageText = "目前的二氧化碳含量為 ${last.field1}
ppm\n" +
+ "更新日期:$lastUpdate
"
+ imageUrl = SpaceCo2().chartPng(co2)
+ }
+ } finally {
+ if (imageUrl.isEmpty()) {
+ tryExecute(SendMessage().apply {
+ chatId = message.chat.id.toString()
+ replyToMessageId = message.messageId
+ text = messageText
+ setParseMode("HTML")
+ })
+ } else {
+ try {
+ execute(SendChatAction().apply {
+ chatId = message.chat.id.toString()
+ action = ActionType.UPLOADPHOTO
+ })
+
+ sendPhoto(SendPhoto().apply {
+ caption = messageText
+ chatId = message.chat.id.toString()
+ parseMode = "HTML"
+ replyToMessageId = message.messageId
+ setNewPhoto("moztw-space-co2.png", imageUrl.let {
+ OkHttpClient()
+ .newCall(Request.Builder().url(it).build())
+ .execute().body?.byteStream()
+ })
+ })
+ } catch (e: TelegramApiException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ } else if (isAdminChat(message.chat)) {
+ if (isCommandOpen(message.text)) {
+ if (tryExecute(Caption().getCaptionOpened(chatId = generalChatId))
+ && tryExecute(Reply().getGeneralMessageOpen(chatId = generalChatId, operator = message.from))
+ && tryExecute(Reply().getMessageOpen(message = message))) {
+ for (chatId in adminChats)
+ if (chatId != message.chatId)
+ if (!tryExecute(Reply().getOtherMessageOpen(message = message, chatId = chatId, operator = message.from)))
+ return false
+ return true
+ }
+ } else if (isCommandClose(message.text)) {
+ if (tryExecute(Caption().getCaptionClosed(chatId = generalChatId))
+ && tryExecute(Reply().getGeneralMessageClose(chatId = generalChatId, operator = message.from))
+ && tryExecute(Reply().getMessageClose(message = message))) {
+ for (chatId in adminChats)
+ if (chatId != message.chatId)
+ if (!tryExecute(Reply().getOtherMessageClose(message = message, chatId = chatId, operator = message.from)))
+ return false
+ return true
+ }
}
}
}
@@ -89,6 +154,10 @@ internal class Bot(val username: String, val token: String) : TelegramLongPollin
return text.matches("^/space_open(?:\\s|$)".toRegex()) || text.matches("^/space_open@$botUsername(?:\\s|$)".toRegex())
}
+ private fun isCommandCO2(text: String): Boolean {
+ return text.matches("^/co2(?:\\s|$)".toRegex()) || text.matches("^/co2@$botUsername(?:\\s|$)".toRegex())
+ }
+
private fun isAdminChat(chat: Chat) = adminChats.contains(chat.id)
override fun getBotUsername() = username
diff --git a/src/main/kotlin/org/moztw/bot/telegram/space/co2/SpaceCo2.kt b/src/main/kotlin/org/moztw/bot/telegram/space/co2/SpaceCo2.kt
new file mode 100644
index 0000000..ede1731
--- /dev/null
+++ b/src/main/kotlin/org/moztw/bot/telegram/space/co2/SpaceCo2.kt
@@ -0,0 +1,23 @@
+package org.moztw.bot.telegram.space.co2
+
+import com.beust.klaxon.Klaxon
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.moztw.bot.telegram.space.co2.model.Chart
+import org.moztw.bot.telegram.space.co2.model.Co2
+import java.io.IOException
+import java.time.ZoneId
+import java.time.ZonedDateTime
+
+class SpaceCo2 {
+ private val url = "https://thingspeak.com/channels/631210/feed.json"
+
+ @Throws(IOException::class)
+ fun fetchCo2() = OkHttpClient()
+ .newCall(Request.Builder().url(url).build())
+ .execute().body?.string()?.let { json -> Klaxon().parse(json) }
+
+ fun chartPng(co2: Co2) = Chart(co2.feeds.map {
+ Pair(ZonedDateTime.parse(it.created_at).withZoneSameInstant(ZoneId.of("Asia/Taipei")), it.field1)
+ }.toList()).url
+}
diff --git a/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Channel.kt b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Channel.kt
new file mode 100644
index 0000000..6d006e3
--- /dev/null
+++ b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Channel.kt
@@ -0,0 +1,13 @@
+package org.moztw.bot.telegram.space.co2.model
+
+data class Channel(
+ val id: Int,
+ val name: String,
+ val description: String,
+ val latitude: String,
+ val longitude: String,
+ val field1: String,
+ val created_at: String,
+ val updated_at: String,
+ val last_entry_id: Int
+)
diff --git a/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Chart.kt b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Chart.kt
new file mode 100644
index 0000000..047b9a4
--- /dev/null
+++ b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Chart.kt
@@ -0,0 +1,41 @@
+package org.moztw.bot.telegram.space.co2.model
+
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import javax.ws.rs.core.UriBuilder
+
+data class Chart(
+ val data: List>
+) {
+ private val dataX = data.map { it.first.format(DateTimeFormatter.ofPattern("HH:mm")) }
+ private val dataY = data.map { it.second.toInt() }
+
+ private val dataMin = dataY.minBy { it } ?: 0
+ private val dataMax = dataY.maxBy { it } ?: 0
+ private val dataMid = (dataMin + dataMax) / 2
+ private val dataQuad = arrayOf(dataMin, (dataMin + dataMid) / 2, dataMid, (dataMid + dataMax) / 2, dataMax)
+
+ private val chartData = dataY.joinToString(",", "t:")
+ private val chartDataScale = "$dataMin,$dataMax"
+ private val chartGrid = "10,25"
+ private val chartLabel = dataX.filterIndexed { index, _ -> 99 == index || 0 == (index % 10) }.joinToString("|")
+ private val chartSize = "640x360"
+ private val chartType = "lc"
+ private val chartTitle = "Concentration of CO2 in Mozilla Community Space Taipei"
+ private val chartAxisLabel = "1:|${dataQuad.joinToString("|") { "$it ppm" }}"
+ private val chartAxis = "x,y"
+
+ val url = UriBuilder
+ .fromPath("https://chart.googleapis.com/chart")
+ .queryParam("chd", chartData)
+ .queryParam("chds", chartDataScale)
+ .queryParam("chg", chartGrid)
+ .queryParam("chl", chartLabel)
+ .queryParam("chs", chartSize)
+ .queryParam("cht", chartType)
+ .queryParam("chtt", chartTitle)
+ .queryParam("chxl", chartAxisLabel)
+ .queryParam("chxt", chartAxis)
+ .build()
+ .toASCIIString()!!
+}
diff --git a/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Co2.kt b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Co2.kt
new file mode 100644
index 0000000..4ce3b4b
--- /dev/null
+++ b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Co2.kt
@@ -0,0 +1,24 @@
+package org.moztw.bot.telegram.space.co2.model
+
+data class Co2(
+ val channel: Channel,
+ val feeds: Array
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Co2
+
+ if (channel != other.channel) return false
+ if (!feeds.contentEquals(other.feeds)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = channel.hashCode()
+ result = 31 * result + feeds.contentHashCode()
+ return result
+ }
+}
diff --git a/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Feed.kt b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Feed.kt
new file mode 100644
index 0000000..653c8a8
--- /dev/null
+++ b/src/main/kotlin/org/moztw/bot/telegram/space/co2/model/Feed.kt
@@ -0,0 +1,7 @@
+package org.moztw.bot.telegram.space.co2.model
+
+data class Feed(
+ val created_at: String,
+ val entry_id: Int,
+ val field1: String
+)