Get player names from UUID and include in /players

main
Kevin Belisle 2021-07-02 01:04:26 -04:00
parent 6cf621f156
commit 5945276b1d
5 changed files with 122 additions and 15 deletions

View File

@ -5,10 +5,7 @@
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/7.1/userguide/building_java_projects.html * User Manual available at https://docs.gradle.org/7.1/userguide/building_java_projects.html
*/ */
// compileOptions { import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
// sourceCompatibility(JavaVersion.VERSION_1_8)
// targetCompatibility(JavaVersion.VERSION_1_8)
// }
plugins { plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
@ -60,6 +57,13 @@ dependencies {
implementation("com.beust:klaxon:5.5") implementation("com.beust:klaxon:5.5")
implementation("com.natpryce:konfig:1.6.10.0") implementation("com.natpryce:konfig:1.6.10.0")
implementation("io.ktor:ktor-client-core:1.6.1")
implementation("io.ktor:ktor-client-java:1.6.1")
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
} }
application { application {

View File

@ -9,6 +9,7 @@ import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.* import org.jetbrains.exposed.sql.transactions.*
import xyz.etztech.stonks.dsl.LiveStatistics import xyz.etztech.stonks.dsl.LiveStatistics
import xyz.etztech.stonks.dsl.Players
import xyz.etztech.stonks.dsl.Statistics import xyz.etztech.stonks.dsl.Statistics
private var statisticsCache = "" private var statisticsCache = ""
@ -86,10 +87,11 @@ fun initApiServer(apiServerPort: Int, database: Database) {
addLogger(StdOutSqlLogger) addLogger(StdOutSqlLogger)
val players = val players =
LiveStatistics.slice(LiveStatistics.playerId) LiveStatistics.leftJoin(Players, { playerId }, { id })
.slice(LiveStatistics.playerId, Players.name)
.selectAll() .selectAll()
.groupBy(LiveStatistics.playerId) .groupBy(LiveStatistics.playerId)
.map { it[LiveStatistics.playerId] } .map { Player(it[LiveStatistics.playerId], it[Players.name]) }
playersCache = Json.encodeToString(players) playersCache = Json.encodeToString(players)
} }
} }
@ -159,6 +161,8 @@ fun initApiServer(apiServerPort: Int, database: Database) {
@Serializable data class Statistic(val type: String, val name: String) @Serializable data class Statistic(val type: String, val name: String)
@Serializable data class Player(val id: String, val name: String?)
@Serializable @Serializable
data class StatisticValue( data class StatisticValue(
val playerId: String, val playerId: String,

View File

@ -11,6 +11,7 @@ import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import xyz.etztech.stonks.api.initApiServer import xyz.etztech.stonks.api.initApiServer
import xyz.etztech.stonks.dsl.LiveStatistics import xyz.etztech.stonks.dsl.LiveStatistics
import xyz.etztech.stonks.dsl.Players
import xyz.etztech.stonks.dsl.Statistics import xyz.etztech.stonks.dsl.Statistics
import xyz.etztech.stonks.statisticsimporter.StatisticsImporter import xyz.etztech.stonks.statisticsimporter.StatisticsImporter
@ -78,6 +79,7 @@ fun initH2Server(
addLogger(StdOutSqlLogger) addLogger(StdOutSqlLogger)
SchemaUtils.create(Statistics) SchemaUtils.create(Statistics)
SchemaUtils.create(LiveStatistics) SchemaUtils.create(LiveStatistics)
SchemaUtils.create(Players)
// Create indexes with explicit SQL because I can't figure out how to do it with exposed // Create indexes with explicit SQL because I can't figure out how to do it with exposed
// Wrap it in a try block because it throws an exception if index already exists // Wrap it in a try block because it throws an exception if index already exists

View File

@ -24,3 +24,11 @@ object Statistics : Table() {
override val primaryKey = override val primaryKey =
PrimaryKey(playerId, type, name, timestamp, name = "PK_playerId_type_name_timestamp") PrimaryKey(playerId, type, name, timestamp, name = "PK_playerId_type_name_timestamp")
} }
object Players : Table() {
val id: Column<String> = varchar("Id", 150)
val name: Column<String> = varchar("Name", 150)
val timestamp: Column<Instant> = timestamp("Timestamp")
override val primaryKey = PrimaryKey(id, name = "PK_id")
}

View File

@ -2,17 +2,27 @@ package xyz.etztech.stonks.statisticsimporter
import com.beust.klaxon.* import com.beust.klaxon.*
import com.beust.klaxon.Klaxon import com.beust.klaxon.Klaxon
import io.ktor.client.*
import io.ktor.client.engine.java.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import java.io.File import java.io.File
import java.time.Duration
import java.time.Instant import java.time.Instant
import kotlinx.coroutines.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.`java-time`.timestamp import org.jetbrains.exposed.sql.`java-time`.timestamp
import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import xyz.etztech.stonks.dsl.LiveStatistics
import xyz.etztech.stonks.dsl.Players
import xyz.etztech.stonks.dsl.Statistics import xyz.etztech.stonks.dsl.Statistics
object StatisticsImporter { object StatisticsImporter {
init {} init {}
private val httpClient = HttpClient(Java)
private val klaxon = Klaxon() private val klaxon = Klaxon()
fun importStatistics(folder: String, database: Database) { fun importStatistics(folder: String, database: Database) {
@ -20,6 +30,8 @@ object StatisticsImporter {
File(folder).listFiles().forEach { readFile(it, database) } File(folder).listFiles().forEach { readFile(it, database) }
println("Updating live statistics table...") println("Updating live statistics table...")
updateLiveStatistics(database) updateLiveStatistics(database)
println("Refreshing player names...")
refreshPlayerNames(database)
} }
fun readFile(file: File, database: Database) { fun readFile(file: File, database: Database) {
@ -27,21 +39,22 @@ object StatisticsImporter {
val playerId = file.nameWithoutExtension val playerId = file.nameWithoutExtension
transaction(database) { transaction(database) {
val maxExpr = Statistics.value.max() addLogger(StdOutSqlLogger)
val playerStats = emptyMap<String, MutableMap<String, Long>>().toMutableMap() val playerStats = emptyMap<String, MutableMap<String, Long>>().toMutableMap()
Statistics.slice(Statistics.type, Statistics.name, maxExpr) LiveStatistics.slice(LiveStatistics.type, LiveStatistics.name, LiveStatistics.value)
.select { Statistics.playerId.eq(playerId) } .select { LiveStatistics.playerId eq playerId }
.groupBy(Statistics.type, Statistics.name)
.forEach { .forEach {
if (playerStats.containsKey(it[Statistics.type])) { if (playerStats.containsKey(it[LiveStatistics.type])) {
playerStats[it[Statistics.type]]?.put( playerStats[it[LiveStatistics.type]]?.put(
it[Statistics.name], it[maxExpr]!!) it[LiveStatistics.name], it[LiveStatistics.value])
} else { } else {
playerStats.put( playerStats.put(
it[Statistics.type], it[LiveStatistics.type],
mapOf<String, Long>(it[Statistics.name] to it[maxExpr]!!) mapOf<String, Long>(
it[LiveStatistics.name] to
it[LiveStatistics.value])
.toMutableMap()) .toMutableMap())
} }
} }
@ -104,9 +117,85 @@ object StatisticsImporter {
} }
} }
fun refreshPlayerNames(database: Database) {
transaction(database) {
addLogger(StdOutSqlLogger)
val savedPlayers =
Players.selectAll().map {
object {
val id = it[Players.id]
val timestamp = it[Players.timestamp]
}
}
val players =
LiveStatistics.slice(LiveStatistics.playerId)
.selectAll()
.groupBy(LiveStatistics.playerId)
.map { it[LiveStatistics.playerId] }
savedPlayers
.filter { Duration.between(Instant.now(), it.timestamp) > Duration.ofDays(1) }
.forEach { player ->
runBlocking {
val name = getName(player.id)
if (name != null) {
println("Updating ${player.id} -> $name")
Players.update({ Players.id eq player.id }) {
it[Players.name] = name
it[Players.timestamp] = Instant.now()
}
} else {
println("Error updating ${player.id}")
}
}
}
players
.filterNot { player ->
savedPlayers.any { savedPlayer -> savedPlayer.id == player }
}
.forEach { playerId ->
runBlocking {
val name = getName(playerId)
if (name != null) {
println("Updating $playerId -> $name")
Players.insert {
it[Players.id] = playerId
it[Players.name] = name
it[Players.timestamp] = Instant.now()
}
} else {
println("Error updating $playerId")
}
}
}
}
}
suspend fun getName(id: String): String? {
val response: HttpResponse =
httpClient.request(
"https://sessionserver.mojang.com/session/minecraft/profile/${id.replace("-", "")}") {
method = HttpMethod.Get
}
if (response.status.isSuccess()) {
try {
return MojangProfileResponse.fromJson(response.readText())?.name
} catch (e: Exception) {}
}
return null
}
data class StatsFile(val stats: Map<String, Map<String, Long>>) { data class StatsFile(val stats: Map<String, Map<String, Long>>) {
companion object { companion object {
public fun fromJson(json: String) = klaxon.parse<StatsFile>(json) public fun fromJson(json: String) = klaxon.parse<StatsFile>(json)
} }
} }
data class MojangProfileResponse(val name: String) {
companion object {
public fun fromJson(json: String) = klaxon.parse<MojangProfileResponse>(json)
}
}
} }