mirror of
https://github.com/dalbodeule/chibot-chzzk-bot.git
synced 2025-08-11 06:41:14 +00:00
Compare commits
9 Commits
develop
...
593b98b7fb
Author | SHA1 | Date | |
---|---|---|---|
|
593b98b7fb | ||
|
95676e9b39 | ||
|
722b5972d9 | ||
|
a88f994ccd | ||
|
49541f7289 | ||
|
ba9fb052cd | ||
|
51232ad593 | ||
|
77eecaca34 | ||
|
90230c4691 |
@@ -1,11 +1,7 @@
|
|||||||
package space.mori.chzzk_bot.chatbot.chzzk
|
package space.mori.chzzk_bot.chatbot.chzzk
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.future.await
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -17,7 +13,8 @@ import space.mori.chzzk_bot.common.models.User
|
|||||||
import space.mori.chzzk_bot.common.services.LiveStatusService
|
import space.mori.chzzk_bot.common.services.LiveStatusService
|
||||||
import space.mori.chzzk_bot.common.services.TimerConfigService
|
import space.mori.chzzk_bot.common.services.TimerConfigService
|
||||||
import space.mori.chzzk_bot.common.services.UserService
|
import space.mori.chzzk_bot.common.services.UserService
|
||||||
import space.mori.chzzk_bot.common.utils.*
|
import space.mori.chzzk_bot.common.utils.getChzzkChannelId
|
||||||
|
import space.mori.chzzk_bot.common.utils.getUptime
|
||||||
import xyz.r2turntrue.chzzk4j.ChzzkClient
|
import xyz.r2turntrue.chzzk4j.ChzzkClient
|
||||||
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionBuilder
|
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionBuilder
|
||||||
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionSubscriptionType
|
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionSubscriptionType
|
||||||
@@ -25,10 +22,11 @@ import xyz.r2turntrue.chzzk4j.session.ChzzkUserSession
|
|||||||
import xyz.r2turntrue.chzzk4j.session.event.SessionChatMessageEvent
|
import xyz.r2turntrue.chzzk4j.session.event.SessionChatMessageEvent
|
||||||
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
|
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
|
||||||
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveDetail
|
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveDetail
|
||||||
import java.lang.Exception
|
import java.lang.Runnable
|
||||||
|
import java.lang.Thread
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
object ChzzkHandler {
|
object ChzzkHandler {
|
||||||
private val handlers = mutableListOf<UserHandler>()
|
private val handlers = mutableListOf<UserHandler>()
|
||||||
@@ -46,7 +44,7 @@ object ChzzkHandler {
|
|||||||
UserService.getAllUsers().map {
|
UserService.getAllUsers().map {
|
||||||
if(!it.isDisabled)
|
if(!it.isDisabled)
|
||||||
try {
|
try {
|
||||||
Connector.getChannel(it.token)?.let { token -> addUser(token, it) }
|
getChannel(it.token)?.let { token -> addUser(token, it) }
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
logger.info("Exception: ${it.token}(${it.username}) not found. ${e.stackTraceToString()}")
|
logger.info("Exception: ${it.token}(${it.username}) not found. ${e.stackTraceToString()}")
|
||||||
}
|
}
|
||||||
@@ -83,10 +81,12 @@ object ChzzkHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun disable() {
|
fun disable() {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
handlers.forEach { handler ->
|
handlers.forEach { handler ->
|
||||||
handler.disable()
|
handler.disable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun reloadCommand(chzzkChannel: ChzzkChannel) {
|
internal fun reloadCommand(chzzkChannel: ChzzkChannel) {
|
||||||
val handler = handlers.firstOrNull { it.channel.channelId == chzzkChannel.channelId }
|
val handler = handlers.firstOrNull { it.channel.channelId == chzzkChannel.channelId }
|
||||||
@@ -118,7 +118,7 @@ object ChzzkHandler {
|
|||||||
try {
|
try {
|
||||||
it.isActive(true, streamInfo)
|
it.isActive(true, streamInfo)
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
logger.info("Exception: ${e.stackTraceToString()}")
|
logger.info("Thread 1 Exception: ${e.stackTraceToString()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (streamInfo?.isOnline == false && it.isActive) it.isActive(false, streamInfo)
|
if (streamInfo?.isOnline == false && it.isActive) it.isActive(false, streamInfo)
|
||||||
@@ -146,14 +146,14 @@ object ChzzkHandler {
|
|||||||
try {
|
try {
|
||||||
it.isActive(true, streamInfo)
|
it.isActive(true, streamInfo)
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
logger.info("Exception: ${e.stackTraceToString()}")
|
logger.info("Thread 2 Exception: ${e.stackTraceToString()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (streamInfo?.isOnline == false && it.isActive) it.isActive(false, streamInfo)
|
if (streamInfo?.isOnline == false && it.isActive) it.isActive(false, streamInfo)
|
||||||
} catch (e: SocketTimeoutException) {
|
} catch (e: SocketTimeoutException) {
|
||||||
logger.info("Thread 1 Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
logger.info("Thread 2 Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.info("Thread 1 Exception: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
logger.info("Thread 2 Exception: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
||||||
} finally {
|
} finally {
|
||||||
Thread.sleep(5000)
|
Thread.sleep(5000)
|
||||||
}
|
}
|
||||||
@@ -204,10 +204,10 @@ class UserHandler(
|
|||||||
private var user: User,
|
private var user: User,
|
||||||
var streamStartTime: LocalDateTime?,
|
var streamStartTime: LocalDateTime?,
|
||||||
) {
|
) {
|
||||||
var messageHandler: MessageHandler
|
lateinit var messageHandler: MessageHandler
|
||||||
var client: ChzzkClient
|
lateinit var client: ChzzkClient
|
||||||
var listener: ChzzkUserSession
|
lateinit var listener: ChzzkUserSession
|
||||||
var chatChannelId: String?
|
lateinit var chatChannelId: String
|
||||||
|
|
||||||
private val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
|
private val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
|
||||||
private var _isActive: Boolean
|
private var _isActive: Boolean
|
||||||
@@ -216,28 +216,31 @@ class UserHandler(
|
|||||||
LiveStatusService.updateOrCreate(user, value)
|
LiveStatusService.updateOrCreate(user, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
private suspend fun connect() {
|
||||||
val user = UserService.getUser(channel.channelId)
|
val user = UserService.getUser(channel.channelId)
|
||||||
|
|
||||||
if(user?.accessToken == null || user.refreshToken == null) {
|
if(user?.accessToken == null || user.refreshToken == null) {
|
||||||
throw RuntimeException("AccessToken or RefreshToken is not valid.")
|
throw RuntimeException("AccessToken or RefreshToken is not valid.")
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
val tokens = Connector.client.refreshAccessToken(user.refreshToken!!)
|
val tokens = user.refreshToken?.let { token -> Connector.client.refreshAccessToken(token)}
|
||||||
|
if(tokens == null) {
|
||||||
|
throw RuntimeException("AccessToken is not valid.")
|
||||||
|
}
|
||||||
client = Connector.getClient(tokens.first, tokens.second)
|
client = Connector.getClient(tokens.first, tokens.second)
|
||||||
UserService.setRefreshToken(user, tokens.first, tokens.second)
|
UserService.setRefreshToken(user, tokens.first, tokens.second)
|
||||||
chatChannelId = getChzzkChannelId(channel.channelId)
|
chatChannelId = getChzzkChannelId(channel.channelId) ?: throw RuntimeException("Chat Channel ID is not found.")
|
||||||
|
|
||||||
client.loginAsync().join()
|
client.loginAsync().await()
|
||||||
listener = ChzzkSessionBuilder(client).buildUserSession()
|
listener = ChzzkSessionBuilder(client).buildUserSession()
|
||||||
listener.createAndConnectAsync().join()
|
listener.createAndConnectAsync().await()
|
||||||
messageHandler = MessageHandler(this@UserHandler)
|
|
||||||
|
delay(1000L)
|
||||||
|
|
||||||
listener.on(SessionChatMessageEvent::class.java) {
|
listener.on(SessionChatMessageEvent::class.java) {
|
||||||
messageHandler.handle(it.message, user)
|
messageHandler.handle(it.message, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalScope.launch {
|
|
||||||
val timer = TimerConfigService.getConfig(user)
|
val timer = TimerConfigService.getConfig(user)
|
||||||
if (timer?.option == TimerType.UPTIME.value)
|
if (timer?.option == TimerType.UPTIME.value)
|
||||||
dispatcher.post(
|
dispatcher.post(
|
||||||
@@ -254,16 +257,12 @@ class UserHandler(
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
messageHandler = MessageHandler(this@UserHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch(e: Exception) {
|
internal suspend fun disable() {
|
||||||
logger.error("Exception(${user.username}): ${e.stackTraceToString()}")
|
listener.disconnectAsync().await()
|
||||||
throw RuntimeException("Exception: ${e.stackTraceToString()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun disable() {
|
|
||||||
listener.disconnectAsync().join()
|
|
||||||
_isActive = false
|
_isActive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +280,8 @@ class UserHandler(
|
|||||||
internal fun isActive(value: Boolean, status: ChzzkLiveDetail) {
|
internal fun isActive(value: Boolean, status: ChzzkLiveDetail) {
|
||||||
if(value) {
|
if(value) {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
connect()
|
||||||
|
|
||||||
logger.info("${user.username} is live.")
|
logger.info("${user.username} is live.")
|
||||||
|
|
||||||
reloadUser(UserService.getUser(user.id.value)!!)
|
reloadUser(UserService.getUser(user.id.value)!!)
|
||||||
|
@@ -1,24 +1,15 @@
|
|||||||
package space.mori.chzzk_bot.chatbot.utils
|
package space.mori.chzzk_bot.chatbot.utils
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.reflect.TypeToken
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import space.mori.chzzk_bot.chatbot.chzzk.dotenv
|
||||||
|
import space.mori.chzzk_bot.common.utils.IData
|
||||||
import space.mori.chzzk_bot.common.utils.client
|
import space.mori.chzzk_bot.common.utils.client
|
||||||
import xyz.r2turntrue.chzzk4j.ChzzkClient
|
import xyz.r2turntrue.chzzk4j.ChzzkClient
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
val client = OkHttpClient.Builder()
|
|
||||||
.addNetworkInterceptor { chain ->
|
|
||||||
chain.proceed(
|
|
||||||
chain.request()
|
|
||||||
.newBuilder()
|
|
||||||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
val gson = Gson()
|
|
||||||
|
|
||||||
data class RefreshTokenResponse(
|
data class RefreshTokenResponse(
|
||||||
val accessToken: String,
|
val accessToken: String,
|
||||||
@@ -29,25 +20,25 @@ data class RefreshTokenResponse(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun ChzzkClient.refreshAccessToken(refreshToken: String): Pair<String, String> {
|
fun ChzzkClient.refreshAccessToken(refreshToken: String): Pair<String, String> {
|
||||||
val url = "https://chzzk.naver.com/auth/v1/token"
|
val url = "https://openapi.chzzk.naver.com/auth/v1/token"
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.post(gson.toJson(mapOf(
|
.post(gson.toJson(mapOf(
|
||||||
"grantType" to "refresh_token",
|
"grantType" to "refresh_token",
|
||||||
"refreshToken" to refreshToken,
|
"refreshToken" to refreshToken,
|
||||||
"clientId" to this.apiClientId,
|
"clientId" to dotenv["NAVER_CLIENT_ID"],
|
||||||
"clientSecret" to this.apiSecret
|
"clientSecret" to dotenv["NAVER_CLIENT_SECRET"]
|
||||||
)).toRequestBody())
|
)).toRequestBody("application/json; charset=utf-8".toMediaType()))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
client.newCall(request).execute().use { response ->
|
||||||
try {
|
try {
|
||||||
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
|
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
|
||||||
val body = response.body?.string()
|
val body = response.body?.string()
|
||||||
val data = gson.fromJson(body, RefreshTokenResponse::class.java)
|
val data = gson.fromJson(body, object: TypeToken<IData<RefreshTokenResponse>>() {})
|
||||||
|
|
||||||
return Pair(data.accessToken, data.refreshToken)
|
return Pair(data.content.accessToken, data.content.refreshToken)
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
@@ -181,7 +181,7 @@ val server = embeddedServer(Netty, port = 8080, ) {
|
|||||||
clientSecret = dotenv["NAVER_CLIENT_SECRET"]
|
clientSecret = dotenv["NAVER_CLIENT_SECRET"]
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = applicationHttpClient.post("https://chzzk.naver.com/auth/v1/token") {
|
val response = applicationHttpClient.post("https://openapi.chzzk.naver.com/auth/v1/token") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(tokenRequest)
|
setBody(tokenRequest)
|
||||||
}
|
}
|
||||||
@@ -228,8 +228,7 @@ val server = embeddedServer(Netty, port = 8080, ) {
|
|||||||
// common: logout
|
// common: logout
|
||||||
get("/logout") {
|
get("/logout") {
|
||||||
call.sessions.clear<UserSession>()
|
call.sessions.clear<UserSession>()
|
||||||
call.response.status(HttpStatusCode.OK)
|
call.respondRedirect(getFrontendURL(""))
|
||||||
return@get
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user