Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fabric mod used when quilt version exists log processor #86

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class SimpleLogParserConfig(private val builder: Builder) : LogParserConf
FabricApisProcessor(),
FabricImplProcessor(),
IncompatibleModProcessor(),
FabricModUsedWhenQuiltVersionExistsProcessor(),
CrashReportProcessor(),
JavaClassFileVersionProcessor(),
MixinErrorProcessor(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import org.quiltmc.community.cozy.modules.logs.Version
public data class Mod(
val id: String,
val version: Version,

// Only present on Quilt Loader
val path: String?
val path: String?,
val hash: String?,
val type: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class FabricModsParser : LogParser() {
Mod(
split.first(),
Version(split.last()),
null,
null,
null
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class QuiltModsParser : LogParser() {

val lines = table
.split("\n")
.map { it.trim('|') } // Don't strip spaces here, but do remove border pipes
.map { it.trim().trim('|') } // Don't strip spaces here, but do remove border pipes
.toMutableList()

// The first line is the headers
Expand Down Expand Up @@ -73,7 +73,9 @@ public class QuiltModsParser : LogParser() {
Mod(
mod["id"]!!,
Version(mod["version"]!!),
mod["file(s)"]
mod["file(s)"],
mod["file hash (sha-1)"],
mod["type"]
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

package org.quiltmc.community.cozy.modules.logs.processors.quilt

import dev.kord.core.event.Event
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.decodeFromJsonElement
import org.quiltmc.community.cozy.modules.logs.Version
import org.quiltmc.community.cozy.modules.logs.data.LoaderType
import org.quiltmc.community.cozy.modules.logs.data.Log
import org.quiltmc.community.cozy.modules.logs.data.Order
import org.quiltmc.community.cozy.modules.logs.types.LogProcessor
import java.util.*

private const val MODRINTH_API_BASE = "https://api.modrinth.com/v2"

public class FabricModUsedWhenQuiltVersionExistsProcessor : LogProcessor() {

override val identifier: String = "fabric-mod-used-when-quilt-version-exists"
override val order: Order = Order.Default
private val json = Json {
ignoreUnknownKeys = true
}

override suspend fun predicate(log: Log, event: Event): Boolean = log.getLoaderVersion(LoaderType.Quilt) != null

override suspend fun process(log: Log) {
val mcVersion = log.minecraftVersion?.string ?: log.getMod("minecraft")?.version?.string ?: return

val hashToMod = log.getMods().filter {
it.value.type?.lowercase(Locale.getDefault()).equals("fabric") && it.value.hash != null
}.map { (_, mod) -> mod.hash!! to mod }.toMap()
anonymous123-code marked this conversation as resolved.
Show resolved Hide resolved
if (hashToMod.isEmpty()) return

val hashesRequest = client.post {
url("$MODRINTH_API_BASE/version_files")
setBody(json.encodeToString(HashLookupParam(hashToMod.keys.toList(), "sha1")))
contentType(ContentType.Application.Json)
}
anonymous123-code marked this conversation as resolved.
Show resolved Hide resolved
if (hashesRequest.status != HttpStatusCode.OK) return

val projectIdToNoneQuiltVersions = json.decodeFromJsonElement<Map<String, Version>>(hashesRequest.body())
.filter { !it.value.loaders.contains("quilt") }
.map { (hash, version) -> version.projectId to hashToMod[hash] }
.toMap()
if (projectIdToNoneQuiltVersions.isEmpty()) return

val projectsRequest = client.get("$MODRINTH_API_BASE/projects") {
url {
parameters.append("ids", json.encodeToString(projectIdToNoneQuiltVersions.keys.toList()))
}
}
if (projectsRequest.status != HttpStatusCode.OK) return

val body = projectsRequest.body<JsonArray>()
val potentialBetterVersionCandidates = json.decodeFromJsonElement<List<Project>>(body)
.filter { it.gameVersions.contains(mcVersion) && it.loaders.contains("quilt") }
.flatMap { it.versions }
if (potentialBetterVersionCandidates.isEmpty()) return

val versionsRequest = client.get("$MODRINTH_API_BASE/versions") {
url {
parameters.append("ids", json.encodeToString(potentialBetterVersionCandidates))
}
}
if (versionsRequest.status != HttpStatusCode.OK) return

val modIdToProjectVersion = mutableMapOf<String, MutableList<Version>>()
json.decodeFromJsonElement<List<Version>>(versionsRequest.body())
.filter { it.loaders.contains("quilt") && it.gameVersions.contains(mcVersion) }
.forEach {
val mod = projectIdToNoneQuiltVersions[it.projectId]!!
val version = Version(removeLoaderIdentifier(it.versionNumber))
val oldVersion = Version(removeLoaderIdentifier(mod.version.string))
if (version >= oldVersion) {
modIdToProjectVersion.getOrPut(mod.id) { mutableListOf<Version>() }.add(it)
}
}
if (modIdToProjectVersion.isEmpty()) return

val modsWithNewerVersions = mutableListOf<String>()
for ((modid, versions) in modIdToProjectVersion) {
val mod = log.getMod(modid)!!
if (versions.size == 1) {
val version = versions[0]
val oldMod = projectIdToNoneQuiltVersions[version.projectId]!!
modsWithNewerVersions.add(
"`${mod.id}`: Switch from ${oldMod.version.string} to " +
"[${version.versionNumber} (Modrinth)]" +
"(https://modrinth.com/mod/${version.projectId}/version/${versions[0].id})"
)
} else {
modsWithNewerVersions.add(
"`${mod.id}`: See [Modrinth](https://modrinth.com/mod/" +
"${versions[0].projectId}/versions?l=quilt&v=$mcVersion)"
)
}
}
log.addMessage(
buildString {
appendLine(
"The following fabric mods are marked as fabric only on Modrinth, " +
"but newer or alternative versions with explicit quilt support exist:"
)
for (mod in modsWithNewerVersions) {
appendLine(" - $mod")
}
}
)
}

private fun removeLoaderIdentifier(versionNumber: String): String = versionNumber.lowercase()
.replace(Regex("quilt[-+ ]"), "")
.replace(Regex("fabric[-+ ]"), "")
.replace(Regex("[-+ ]quilt"), "")
.replace(Regex("[-+ ]fabric"), "")
.replace("quilt", "")
.replace("fabric", "")

@Serializable
public data class Project(
public val versions: List<String>,
@SerialName("game_versions") public val gameVersions: List<String>,
public val loaders: List<String>
)

@Serializable
public data class HashLookupParam(public val hashes: List<String>, public val algorithm: String)

@Serializable
public data class Version(
public val loaders: List<String>,
@SerialName("project_id") public val projectId: String,
@SerialName("version_number") public val versionNumber: String,
@SerialName("game_versions") public val gameVersions: List<String>,
public val id: String
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
import dev.kord.core.event.Event
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
Expand All @@ -30,6 +31,9 @@ public abstract class LogProcessor : BaseLogHandler, KordExKoinComponent {
ContentType.Any
)
}
install(UserAgent) {
agent = "QuiltMC/cozy-discord (quiltmc.org)"
}
}

protected open suspend fun predicate(log: Log, event: Event): Boolean =
Expand Down
Loading