Skip to content

Commit

Permalink
Improve !fabric and add !forge
Browse files Browse the repository at this point in the history
Signed-off-by: shedaniel <daniel@shedaniel.me>
  • Loading branch information
shedaniel committed Feb 15, 2021
1 parent 68189da commit 4273fa9
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 54 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ repositories {
}

dependencies {
compile("me.shedaniel:linkie-core:1.0.59")
compile("me.shedaniel:linkie-core:1.0.60")
compile("com.discord4j:discord4j-core:3.1.3") {
force = true
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/me/shedaniel/linkie/discord/CommandBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ fun embedCreator(creator: EmbedCreator) = creator
fun MessageCreator.sendPages(
initialPage: Int,
maxPages: Int,
creator: suspend EmbedCreateSpec.(Int) -> Unit,
creator: suspend EmbedCreateSpec.(page: Int) -> Unit,
) = sendPages(initialPage, maxPages, previous.author.get().id, creator)

fun MessageCreator.sendPages(
initialPage: Int,
maxPages: Int,
user: User,
creator: suspend EmbedCreateSpec.(Int) -> Unit,
creator: suspend EmbedCreateSpec.(page: Int) -> Unit,
) = sendPages(initialPage, maxPages, user.id, creator)

fun MessageCreator.sendPages(
initialPage: Int,
maxPages: Int,
userId: Snowflake,
creator: suspend EmbedCreateSpec.(Int) -> Unit,
creator: suspend EmbedCreateSpec.(page: Int) -> Unit,
) {
var page = initialPage
val builder = embedCreator { creator(this, page) }
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/me/shedaniel/linkie/discord/LinkieBot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,6 @@ fun registerCommands(commands: CommandHandler) {
commands.registerCommand(TricksCommand, "trick")
commands.registerCommand(ValueCommand, "value")
commands.registerCommand(FabricCommand, "fabric")
commands.registerCommand(ForgeCommand, "forge")
commands.registerCommand(GoogleCommand, "google")
}
33 changes: 27 additions & 6 deletions src/main/kotlin/me/shedaniel/linkie/discord/ValueKeeper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import java.time.Duration
import java.util.*
import kotlin.concurrent.timerTask
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

@Suppress("MemberVisibilityCanBePrivate", "unused")
class ValueKeeper<T> constructor(val timeToKeep: Duration, var value: Optional<T>, val getter: suspend () -> T) {
class ValueKeeper<T> constructor(val timeToKeep: Duration, var valueBackend: Optional<T>, val getter: suspend () -> T) : Lazy<T> {
companion object {
private val timer = Timer()
}
Expand All @@ -40,10 +41,10 @@ class ValueKeeper<T> constructor(val timeToKeep: Duration, var value: Optional<T
}
}

suspend fun get(): T = value.getOrNull() ?: getter().also { value = Optional.of(it); schedule() }
suspend fun get(): T = valueBackend.getOrNull() ?: getter().also { valueBackend = Optional.of(it); schedule() }

suspend fun clear() {
value = Optional.empty()
valueBackend = Optional.empty()
System.gc()
}

Expand All @@ -52,9 +53,29 @@ class ValueKeeper<T> constructor(val timeToKeep: Duration, var value: Optional<T
task = timerTask { runBlockingNoJs { clear() } }
timer.schedule(task, timeToKeep.toMillis())
}

override val value: T
get() = runBlockingNoJs { get() }

override fun isInitialized(): Boolean = valueBackend.isPresent
}

fun <T> valueKeeper(timeToKeep: Duration = Duration.ofMinutes(2), getter: suspend () -> T): ReadOnlyProperty<Any?, T> {
val keeper = ValueKeeper(timeToKeep, getter)
return ReadOnlyProperty { _, _ -> runBlockingNoJs { keeper.get() } }
fun <T> valueKeeper(timeToKeep: Duration = Duration.ofMinutes(2), getter: suspend () -> T): ValueKeeperProperty<T> =
ValueKeeperProperty(timeToKeep, getter)

class ValueKeeperProperty<T>(
timeToKeep: Duration,
getter: suspend () -> T,
) : ReadOnlyProperty<Any?, T>, Lazy<T> {
val keeperLazy = lazy { ValueKeeper(timeToKeep, getter) }
val keeper by keeperLazy
val property = ReadOnlyProperty<Any?, T> { _, _ -> runBlockingNoJs { keeper.get() } }

override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return this.property.getValue(thisRef, property)
}

override fun isInitialized(): Boolean = keeperLazy.isInitialized() && keeper.isInitialized()
override val value: T
get() = runBlockingNoJs { keeper.get() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2019, 2020 shedaniel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package me.shedaniel.linkie.discord.commands

import discord4j.core.`object`.entity.User
import discord4j.core.`object`.entity.channel.MessageChannel
import discord4j.core.event.domain.message.MessageCreateEvent
import discord4j.core.spec.EmbedCreateSpec
import me.shedaniel.linkie.discord.CommandBase
import me.shedaniel.linkie.discord.MessageCreator
import me.shedaniel.linkie.discord.sendPages
import me.shedaniel.linkie.discord.utils.addInlineField
import me.shedaniel.linkie.discord.utils.buildSafeDescription
import me.shedaniel.linkie.discord.utils.discriminatedName
import me.shedaniel.linkie.discord.utils.setSafeDescription
import me.shedaniel.linkie.discord.utils.setTimestampToNow
import me.shedaniel.linkie.discord.validateUsage
import me.shedaniel.linkie.discord.valueKeeper
import java.time.Duration
import kotlin.math.ceil

abstract class AbstractPlatformVersionCommand<R : PlatformVersion, T : PlatformData<R>> : CommandBase {
private val dataKeeper = valueKeeper(Duration.ofMinutes(10)) { updateData() }
protected val data by dataKeeper

override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList<String>, channel: MessageChannel) {
args.validateUsage(prefix, 0..1, "$cmd [version|list|first]")
if (!dataKeeper.isInitialized()) {
message.sendEmbed {
setFooter("Requested by " + user.discriminatedName, user.avatarUrl)
setTimestampToNow()
buildSafeDescription {
append("Searching up version data.\n\nIf you are stuck with this message, please do the command again and report the issue on the issue tracker.")
}
}.subscribe()
}
if (args.isNotEmpty() && args[0] == "list") {
val maxPage = ceil(data.versions.size / 20.0).toInt()
message.sendPages(0, maxPage, user) { page ->
setTitle("Available Versions (Page ${page + 1}/$maxPage)")
buildSafeDescription {
data.versions.asSequence().drop(page * 20).take(20).forEach { versionString ->
val version = data[versionString]
appendLine("$versionString" + when {
version.unstable -> " (Unstable)"
version.version == latestVersion -> " **(Latest)**"
else -> ""
})
}
}
}
return
}
val latestVersion = this.latestVersion
val gameVersion = when {
args.isEmpty() -> latestVersion
args[0] == "first" -> data.versions.first()
else -> args[0]
}
require(data.versions.contains(gameVersion)) { "Invalid Version Specified: $gameVersion\nYou may list the versions available by using `$prefix$cmd list`" }
val version = data[gameVersion]
message.sendEmbed {
setTitle(getTitle(version.version))
buildString {
if (data.versions.first() != latestVersion) {
appendLine("Tip: You can use `$prefix$cmd list` to view the available versions, or use `$prefix$cmd first` to view the first version, even if it is unstable.")
} else {
appendLine("Tip: You can use `$prefix$cmd list` to view the available versions.")
}
when {
version.unstable -> addInlineField("Type", "Unstable")
version.version == latestVersion -> addInlineField("Type", "Release (Latest)")
else -> addInlineField("Type", "Release")
}
version.appendData()(this@sendEmbed, this)
}.takeIf { it.isNotBlank() }?.also {
setSafeDescription(it)
}
}.subscribe()
}

abstract val latestVersion: String
abstract fun getTitle(version: String): String
abstract fun updateData(): T
}

interface PlatformData<R : PlatformVersion> {
val versions: Set<String>

operator fun get(version: String): R
}

interface PlatformVersion {
val version: String
val unstable: Boolean

fun appendData(): EmbedCreateSpec.(descriptionBuilder: StringBuilder) -> Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,67 +16,37 @@

package me.shedaniel.linkie.discord.commands

import discord4j.core.`object`.entity.User
import discord4j.core.`object`.entity.channel.MessageChannel
import discord4j.core.event.domain.message.MessageCreateEvent
import discord4j.core.spec.EmbedCreateSpec
import kotlinx.serialization.json.*
import me.shedaniel.cursemetaapi.CurseMetaAPI
import me.shedaniel.linkie.discord.CommandBase
import me.shedaniel.linkie.discord.MessageCreator
import me.shedaniel.linkie.discord.utils.addInlineField
import me.shedaniel.linkie.discord.validateUsage
import me.shedaniel.linkie.discord.valueKeeper
import me.shedaniel.linkie.utils.tryToVersion
import java.net.URL
import java.time.Duration

object FabricCommand : CommandBase {
private val json = Json {

}
private val fabricData by valueKeeper(Duration.ofMinutes(10)) { updateData() }
private val latestVersion: String
get() = fabricData.versions.keys.asSequence().filter {
object FabricCommand : AbstractPlatformVersionCommand<FabricCommand.FabricVersion, FabricCommand.FabricData>() {
private val json = Json {}
override val latestVersion: String
get() = data.versions.asSequence().filter {
val version = it.tryToVersion()
version != null && version.snapshot == null
}.maxWithOrNull(compareBy { it.tryToVersion() })!!

override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList<String>, channel: MessageChannel) {
args.validateUsage(prefix, 0..1, "$cmd [version]")
val latestVersion = this.latestVersion
val gameVersion = if (args.isEmpty()) latestVersion else args[0]
require(fabricData.versions.containsKey(gameVersion)) { "Invalid Version Specified: $gameVersion" }
val version = fabricData.versions[gameVersion]!!
message.sendEmbed {
setTitle("Fabric Version for Minecraft $gameVersion")
addInlineField("Type", when (version.release) {
true -> when (version.version) {
latestVersion -> "Release (Latest)"
else -> "Release"
}
false -> "Unstable"
})
addInlineField("Loader Version", version.loaderVersion)
addInlineField("Yarn Version", version.yarnVersion)
if (version.apiVersion != null) {
addInlineField("Api Version", version.apiVersion!!.version)
}
}.subscribe()
}
override fun getTitle(version: String): String = "Fabric Version for Minecraft $version"

private fun updateData(): FabricData {
override fun updateData(): FabricData {
val data = FabricData()
val meta = json.parseToJsonElement(URL("https://meta.fabricmc.net/v2/versions").readText()).jsonObject
val loaderVersion = meta["loader"]!!.jsonArray[0].jsonObject["version"]!!.jsonPrimitive.content
val installerVersion = meta["installer"]!!.jsonArray[0].jsonObject["version"]!!.jsonPrimitive.content
val mappings = meta["mappings"]!!.jsonArray
meta["game"]!!.jsonArray.asSequence().map(JsonElement::jsonObject).forEach { obj ->
val version = obj["version"]!!.jsonPrimitive.content
val release = version.tryToVersion().let { it != null && it.snapshot == null }
val mappingsObj = mappings.firstOrNull { it.jsonObject["gameVersion"]!!.jsonPrimitive.content == version }?.jsonObject ?: return@forEach
val yarnVersion = mappingsObj["version"]!!.jsonPrimitive.content
data.versions[version] = FabricVersion(version, release, loaderVersion, yarnVersion)
data.versionsMap[version] = FabricVersion(version, release, loaderVersion, installerVersion, yarnVersion)
}
fillFabricApi(data.versions)
fillFabricApi(data.versionsMap)
return data
}

Expand Down Expand Up @@ -112,16 +82,33 @@ object FabricCommand : CommandBase {
}

data class FabricData(
val versions: MutableMap<String, FabricVersion> = mutableMapOf(),
)
val versionsMap: MutableMap<String, FabricVersion> = mutableMapOf(),
) : PlatformData<FabricVersion> {
override fun get(version: String): FabricVersion = versionsMap[version]!!
override val versions: Set<String>
get() = versionsMap.keys
}

data class FabricVersion(
val version: String,
override val version: String,
val release: Boolean,
val loaderVersion: String,
val installerVersion: String,
val yarnVersion: String,
var apiVersion: FabricApiVersion? = null,
)
) : PlatformVersion {
override val unstable: Boolean
get() = !release

override fun appendData(): EmbedCreateSpec.(StringBuilder) -> Unit = {
addInlineField("Loader Version", loaderVersion)
addInlineField("Installer Version", installerVersion)
addInlineField("Yarn Version", yarnVersion)
if (apiVersion != null) {
addInlineField("Api Version", apiVersion!!.version)
}
}
}

data class FabricApiVersion(
val version: String,
Expand Down
Loading

0 comments on commit 4273fa9

Please sign in to comment.