Вот так сейчас выглядит скрипт GoogleSheets.kt:

package ru.deflecta.auroomy

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.server.*
import io.ktor.server.application.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import java.util.zip.ZipInputStream
import io.ktor.http.*
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.serialization.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.serialization.kotlinx.json.*

fun startApiServer() {
    embeddedServer(Netty, port = 8080) {
        install(ContentNegotiation) {
            json()
        }
        routing {
            post("/api/getLanguages") {
                val request = call.receive<LanguageRequest>()
                val response = processLanguageRequest(request)
                call.respond(response)
            }
        }
    }.start(wait = false)
}

@Serializable
data class LanguageRequest(
    val modrinthId: String? = null,
    val curseforgeId: String? = null,
    val gameVer: String
)

@Serializable
data class LanguageResponse(
    val languages: List<String>,
    val success: Boolean,
    val errorMessage: String? = null
)

suspend fun processLanguageRequest(request: LanguageRequest): LanguageResponse {
    val languagesToCheck = listOf("ru_ru", "tt_ru", "be_by", "uk_ua")
    val detectedLanguages = mutableSetOf<String>()

    try {
        val modFileBytes: ByteArray? = when {
            !request.modrinthId.isNullOrBlank() -> downloadModFromModrinth(request.modrinthId, request.gameVer)
            !request.curseforgeId.isNullOrBlank() -> downloadModFromCurseForge(request.curseforgeId, request.gameVer)
            else -> null
        }

        if (modFileBytes == null) {
            return LanguageResponse(emptyList(), false, "Unable to download mod file.")
        }

        // Извлекаем языковые файлы
        val zipInputStream = ZipInputStream(modFileBytes.inputStream())
        var entry = zipInputStream.nextEntry
        while (entry != null) {
            val entryName = entry.name.lowercase()
            for (lang in languagesToCheck) {
                if (entryName.matches(Regex(""".*assets/.*/lang/$lang\.(json|lang|properties)$"""))) {
                    detectedLanguages.add(lang)
                }
            }
            zipInputStream.closeEntry()
            entry = zipInputStream.nextEntry
        }
        zipInputStream.close()

        return if (detectedLanguages.isNotEmpty()) {
            LanguageResponse(detectedLanguages.toList(), true)
        } else {
            LanguageResponse(emptyList(), true)
        }

    } catch (e: Exception) {
        e.printStackTrace()
        return LanguageResponse(emptyList(), false, e.message)
    }
}

suspend fun downloadModFromModrinth(modrinthId: String, gameVer: String): ByteArray? {
    val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json()
        }
    }

    val versions: List<ModrinthVersion> = client.get("https://api.modrinth.com/v2/project/$modrinthId/version").body()
    // Фильтруем по версии игры
    val filteredVersions = versions.filter { it.game_versions.contains(gameVer) }

    if (filteredVersions.isEmpty()) return null

    // Предпочитаем Fabric, если нет — берём любую
    val preferredVersion = filteredVersions.firstOrNull { it.loaders.contains("fabric") } ?: filteredVersions.first()

    val primaryFile = preferredVersion.files.firstOrNull { it.primary } ?: preferredVersion.files.firstOrNull()
    ?: return null

    val modBytes: ByteArray = client.get(primaryFile.url).body()

    client.close()

    return modBytes
}

suspend fun downloadModFromCurseForge(curseforgeId: String, gameVer: String, config: Config): ByteArray? {
    val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json()
        }
    }

    val apiKey = config.curseForgeApiKey

    val filesResponse: CurseForgeFileResponse =
        client.get("https://api.curseforge.com/v1/mods/$curseforgeId/files") {
            header("x-api-key", apiKey)
        }.body()

    val files = filesResponse.data

    // Фильтруем по версии игры
    val filteredFiles = files.filter { it.gameVersions.contains(gameVer) }

    if (filteredFiles.isEmpty()) return null

    // Предпочитаем Fabric
    val preferredFile = filteredFiles.firstOrNull { it.modLoader == 4 } ?: filteredFiles.first()

    val downloadUrl = preferredFile.downloadUrl ?: run {
        // Получаем downloadUrl через API
        val downloadUrlResponse: CurseForgeDownloadUrlResponse =
            client.get("https://api.curseforge.com/v1/mods/$curseforgeId/files/${preferredFile.id}/download-url") {
                header("x-api-key", apiKey)
            }.body()
        downloadUrlResponse.data
    }

    val modBytes: ByteArray = client.get(downloadUrl).body()

    client.close()

    return modBytes
}

@Serializable
data class ModrinthVersion(
    val game_versions: List<String>,
    val loaders: List<String>,
    val files: List<ModrinthFile>
)

@Serializable
data class ModrinthFile(
    val url: String,
    val primary: Boolean
)

@Serializable
data class CurseForgeFileResponse(
    val data: List<CurseForgeFile>
)

@Serializable
data class CurseForgeFile(
    val id: Int,
    val gameVersions: List<String>,
    val modLoader: Int,
    val downloadUrl: String? = null
)

@Serializable
data class CurseForgeDownloadUrlResponse(
    val data: String
)

А вот так Models.kt:

package ru.deflecta.auroomy

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class Config(
    val discordToken: String,
    val googlePlayChannelId: String,
    val modsChannelId: String,
    val curseForgeApiKey: String,
    val imBoUserAgent: String,
    val russianFeedChannelId: String,
    val englishFeedChannelId: String,
    val mods: List<ModConfig>
)

@Serializable
data class ModConfig(
    val modrinthId: String? = null,
    val curseForgeId: String? = null,
    val name: String
)

@Serializable
data class ModrinthProject(
    @SerialName("project_id") val id: String,
    val slug: String,
    val title: String
    // Добавьте другие поля по необходимости
)

@Serializable
data class ModrinthVersion(
    val id: String,
    val version_number: String,
    val date_published: String, // ISO 8601 format
    val files: List<ModrinthFile>
)

@Serializable
data class ModrinthFile(
    val url: String,
    val primary: Boolean
)

@Serializable
data class CurseForgeFileResponse(
    val data: List<CurseForgeFileData>
)

@Serializable
data class CurseForgeFileData(
    val downloadUrl: String?,
    val displayName: String,
    val fileDate: String // ISO 8601 format
)

@Serializable
data class CurseForgeSearchResponse(
    val data: List<CurseForgeProject>
)

@Serializable
data class CurseForgeProject(
    val id: Int,
    val name: String
)

@Serializable
data class ModrinthSearchResponse(
    val hits: List<ModrinthProject>,
    val offset: Int,
    val limit: Int,
    val total_hits: Int
)

@Serializable
data class ModLanguageData(
    val ruRu: ByteArray?,
    val enUs: ByteArray?,
    val modVersion: String? = null,
    val releaseDate: String? = null
)

@Serializable
data class ImBoPost(
    val id: Long,
    val file: ImBoFile,
    val tags: ImBoTags,
    val rating: String,
    val created_at: String // можно добавить при необходимости
)

@Serializable
data class ImBoFile(
    val url: String?
)

@Serializable
data class ImBoTags(
    val artist: List<String>
)

@Serializable
data class ImBoResponse(
    val posts: List<ImBoPost>
)

Также у меня есть config.json:

{
  "discordToken": "text",
  "googlePlayChannelId": "1287932435451088896",
  "modsChannelId": "1289624298188836924",
  "curseForgeApiKey": "text",
  "imBoUserAgent": "Auroomy/1.0 (by Kolbasart on imBo)",
  "russianFeedChannelId": "878021157977223228",
  "englishFeedChannelId": "927956678278807633",
  "mods": [
    {
      "modrinthId": "AANobbMI",
      "curseForgeId": "394468",
      "name": "Sodium"
    },
    {
      "modrinthId": "YL57xq9U",
      "curseForgeId": "455508",
      "name": "Iris Shaders"
    }
  ]
}

Исправь все конфликты в этом коде.