Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ plugins {

repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}

group = "de.bigboot.ggtools"
Expand Down Expand Up @@ -81,6 +82,9 @@ dependencies {
// CopyDown
implementation("io.github.furstenheim:copy_down:1.1")

// Glicko2
implementation("io.github.gorgtopalski:glicko2-team:3666e16")

// JUnit
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.1")
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/de/bigboot/ggtools/fang/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ data class EmojisConfig(
val deny: String = "\uD83D\uDC4E",
val match_finished: String = "\uD83C\uDFC1",
val match_drop: String = "\uD83D\uDC4E",
val match_unranked: String = "\uD83C\uDFC5",
val queue_empty: String = "\uD83D\uDE22",
val join_queue: String = "\uD83D\uDC4D",
val leave_queue: String = "\uD83D\uDC4E",
Expand Down Expand Up @@ -43,13 +44,14 @@ data class BotConfig(
val token: String,
val prefix: String = "!",
val accept_timeout: Int = 120,
val mapvote_time: Int = 30,
val vote_time: Int = 30,
val statusupdate_poll_rate: Long = 2000L,
val required_players: Int = 10,
val log_level: String = "info",
val queues: List<QueueConfig> = listOf(),
val highscore_channel: String = "",
val time_to_join: Int = 600,
val rating: Boolean = true,
)

@JsonClass(generateAdapter = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ interface ServerApi {

@POST("events")
suspend fun getEvents(@Body eventsRequest: EventsRequest): EventsResponse

@POST("result")
suspend fun getResult(@Body resultRequest: ResultRequest): ResultResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.bigboot.ggtools.fang.api.agent.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class ResultRequest(
@field:Json(name = "id")
val id: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.bigboot.ggtools.fang.api.agent.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class ResultResponse(
@field:Json(name = "winner")
val winner: String?
)
2 changes: 2 additions & 0 deletions src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import de.bigboot.ggtools.fang.CommandGroupSpec
import de.bigboot.ggtools.fang.commands.admin.Admin
import de.bigboot.ggtools.fang.commands.queue.Queue
import de.bigboot.ggtools.fang.commands.server.Server
import de.bigboot.ggtools.fang.commands.rating.Rating
import de.bigboot.ggtools.fang.utils.createEmbedCompat
import de.bigboot.ggtools.fang.utils.formatCommandHelp
import de.bigboot.ggtools.fang.utils.formatCommandTree
Expand All @@ -15,6 +16,7 @@ class Root : CommandGroupSpec("", "") {
group(Admin())
group(Queue())
group(Server())
group(Rating())

command("help", "show this help") {
onCall {
Expand Down
65 changes: 65 additions & 0 deletions src/main/kotlin/de/bigboot/ggtools/fang/commands/rating/Rating.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package de.bigboot.ggtools.fang.commands.rating

import de.bigboot.ggtools.fang.CommandGroupBuilder
import de.bigboot.ggtools.fang.CommandGroupSpec
import de.bigboot.ggtools.fang.Config
import de.bigboot.ggtools.fang.service.RatingService
import de.bigboot.ggtools.fang.utils.*
import discord4j.common.util.Snowflake
import kotlinx.coroutines.reactive.awaitSingle
import okhttp3.Request
import okhttp3.OkHttpClient
import org.koin.core.component.inject

class Rating : CommandGroupSpec("rating", "Commands for ratings") {
private val ratingService by inject<RatingService>()

override val build: CommandGroupBuilder.() -> Unit = {
command("import", "import many games into the rating system") {
onCall {
val attachments = message.attachments;
if (!attachments.isEmpty()) {
var client = OkHttpClient();
val message = channel().createMessageCompat {
addEmbedCompat {
description("Adding scores")
}
}.awaitSingle()
attachments.forEach {
val request = Request.Builder().url(it.url).build();
val response = client.newCall(request).execute().body();
if (response != null) {
response.string().lines().forEach {
if (it.trim() != "") {
val split = it.split(" ").map{it.toLong()};
val half = split.size/2;
ratingService.addResult(split.slice(0..half-1), split.slice(half..split.size-1));
}
}
}
else {
channel().createMessageCompat {
addEmbedCompat {
description("Failed to read the contents of your attachment, please try in a few minuets")
}
}.awaitSingle()
return@onCall
}
}
message.editCompat {
addEmbedCompat {
description("Added all of the results!")
}
}.awaitSingle()
}
else {
channel().createMessageCompat {
addEmbedCompat {
description("No attachments were provided")
}
}.awaitSingle()
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package de.bigboot.ggtools.fang.components.queue

import discord4j.common.util.Snowflake
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.Button
import java.util.*

data class ButtonAccept(val matchId: UUID): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}"
data class ButtonAccept(val matchId: UUID, val dropper: Snowflake?): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}:${if (dropper != null) dropper.asLong() else ""}"
override fun component(): ActionComponent = Button.success(id(), "Accept")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:ACCEPT"
private val ID_REGEX = Regex("$PREFIX:([^:]+)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId) -> ButtonAccept(UUID.fromString(matchId)) }
private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)?")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, dropper) -> ButtonAccept(UUID.fromString(matchId), if (dropper != "") Snowflake.of(dropper) else null) }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.bigboot.ggtools.fang.components.queue

import discord4j.common.util.Snowflake
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.Button
import java.util.*
import de.bigboot.ggtools.fang.utils.asReaction

data class ButtonDownvote(val matchId: UUID, val suggester: Snowflake): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}:${suggester.asLong()}"
override fun component(): ActionComponent = Button.danger(id(), "👎".asReaction(), "")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:DOWNVOTE"
private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, suggester) -> ButtonDownvote(UUID.fromString(matchId), Snowflake.of(suggester) ) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.bigboot.ggtools.fang.components.queue

import de.bigboot.ggtools.fang.Config
import de.bigboot.ggtools.fang.utils.asReaction
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.Button
import java.util.UUID

data class ButtonMatchUnranked(val matchId: UUID, val final: Boolean): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}:${final}"
override fun component(): ActionComponent = Button.primary(id(), Config.emojis.match_unranked.asReaction(), "Set unranked")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:MATCH_UNRANKED"
private val ID_REGEX = Regex("$PREFIX:([^:]+):(true|false)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, final) -> ButtonMatchUnranked(UUID.fromString(matchId), final.toBoolean()) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.bigboot.ggtools.fang.components.queue

import de.bigboot.ggtools.fang.Config
import de.bigboot.ggtools.fang.utils.asReaction
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.Button
import java.util.UUID

data class ButtonRequestFill(val matchId: UUID): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}"
override fun component(): ActionComponent = Button.primary(id(), "Request Fill")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:REQUEST_FILL"
private val ID_REGEX = Regex("$PREFIX:([^:]+)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId) -> ButtonRequestFill(UUID.fromString(matchId)) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.bigboot.ggtools.fang.components.queue

import de.bigboot.ggtools.fang.Config
import de.bigboot.ggtools.fang.utils.asReaction
import discord4j.common.util.Snowflake
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.Button
import java.util.UUID

data class ButtonRequestFillCancel(val matchId: UUID, val dropper: Snowflake): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}:${dropper.asLong()}"
override fun component(): ActionComponent = Button.primary(id(), "Cancel")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:REQUEST_FILL_CANCEL"
private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, dropper) -> ButtonRequestFillCancel(UUID.fromString(matchId), Snowflake.of(dropper)) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.bigboot.ggtools.fang.components.queue

import de.bigboot.ggtools.fang.Config
import de.bigboot.ggtools.fang.utils.asReaction
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.Button
import java.util.UUID

data class ButtonSuggestSwap(val matchId: UUID, val final: Boolean): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}:${final}"
override fun component(): ActionComponent = Button.primary(id(), "Suggest Swap")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:SUGGEST_SWAP"
private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, final) -> ButtonSuggestSwap(UUID.fromString(matchId), final.toBoolean()) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.bigboot.ggtools.fang.components.queue

import discord4j.common.util.Snowflake
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.Button
import java.util.*
import de.bigboot.ggtools.fang.utils.asReaction

data class ButtonUpvote(val matchId: UUID, val suggester: Snowflake): QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}:${suggester.asLong()}"
override fun component(): ActionComponent = Button.success(id(), "👍".asReaction(), "")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:BUTTON:UPVOTE"
private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, suggester) -> ButtonUpvote(UUID.fromString(matchId), Snowflake.of(suggester) ) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package de.bigboot.ggtools.fang.components.queue

import discord4j.common.util.Snowflake
import discord4j.core.GatewayDiscordClient
import discord4j.core.`object`.entity.Member
import discord4j.core.`object`.component.ActionComponent
import discord4j.core.`object`.component.SelectMenu
import java.util.*
import de.bigboot.ggtools.fang.utils.awaitSingle

class SelectPickSwap(val matchId: UUID, private val players: List<Pair<String, Long>>, val team: Boolean):
QueueComponentSpec {
override fun id() = "$PREFIX:${matchId}:${team.toString()}"
override fun component(): ActionComponent = SelectMenu.of(
id(),
players.map { SelectMenu.Option.of(it.first, it.second.toString()) }
).withMinValues(1).withMaxValues(1).withPlaceholder("Pick a person")

companion object {
private val PREFIX = "${QueueComponentSpec.ID_PREFIX}:SELECT:PICK_SWAP"
private val ID_REGEX = Regex("$PREFIX:([^:]+):([^:]+)")
fun parse(id: String) = ID_REGEX.find(id)?.destructured?.let { (matchId, team) -> SelectPickSwap(UUID.fromString(matchId), listOf(), team.toBoolean()) }
}
}
2 changes: 0 additions & 2 deletions src/main/kotlin/de/bigboot/ggtools/fang/db/Users.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ class User(id: EntityID<UUID>) : UUIDEntity(id) {
companion object : UUIDEntityClass<User>(Users)

var snowflake by Users.snowflake
var skill by Users.skill
var groups by Group via UsersGroups
}

object Users : UUIDTable() {
val snowflake = long("snowflake")
val skill = integer("skill").default(1)
}
23 changes: 23 additions & 0 deletions src/main/kotlin/de/bigboot/ggtools/fang/db/UsersRating.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.bigboot.ggtools.fang.db

import org.jetbrains.exposed.dao.UUIDEntity
import org.jetbrains.exposed.dao.UUIDEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.UUIDTable
import java.util.*

class UserRating(id: EntityID<UUID>) : UUIDEntity(id) {
companion object : UUIDEntityClass<UserRating>(UsersRating)

var snowflake by UsersRating.snowflake
var rating by UsersRating.rating
var ratingDeviation by UsersRating.ratingDeviation
var volatility by UsersRating.volatility
}

object UsersRating : UUIDTable() {
val snowflake = long("snowflake")
val rating = double("rating")
val ratingDeviation = double("ratingDeviation")
val volatility = double("volatility")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@file:Suppress("ClassName", "ClassNaming", "unused", "LongMethod")

package de.bigboot.ggtools.fang.db.migrations

import org.flywaydb.core.api.migration.BaseJavaMigration
import org.flywaydb.core.api.migration.Context

class V11__add_match_making : BaseJavaMigration() {
override fun migrate(context: Context) {
context.connection.prepareStatement("""
|alter table Users
| drop column skill;
""".trimMargin()).execute()

context.connection.prepareStatement("""
|create table if not exists UsersRating
|(
| id binary(16) not null
| primary key,
| snowflake long not null,
| rating double not null,
| ratingDeviation double not null,
| volatility double not null
|);
""".trimMargin()).execute()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ val serviceModule = module {
single { ServerServiceImpl() } bind ServerService::class
single { ChangelogServiceImpl() } bind ChangelogService::class
single { PreferencesServiceImpl() } bind PreferencesService::class
single { RatingServiceImpl() } bind RatingService::class

single { SetupGuildServiceImpl() } binds arrayOf(AutostartService::class, SetupGuildService::class)
single { CommandsService() } bind AutostartService::class
Expand Down
Loading