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

Are you kidding me... no you not (FIX FOR API REMOVAL) #72

Open
anonymusdennis opened this issue Nov 26, 2022 · 0 comments
Open

Are you kidding me... no you not (FIX FOR API REMOVAL) #72

anonymusdennis opened this issue Nov 26, 2022 · 0 comments

Comments

@anonymusdennis
Copy link

anonymusdennis commented Nov 26, 2022

@BloodWorkXGaming Kannst du das Vlt. fixen???
I went on for 3 Hours making an older version of this compatible with the new API
and now i am here to see it has already been done???
aww man i need to sleep now
i literally sat there for 4 hours implementing the new api because i thought i had to do it myself...
anyways i did this:

--- Edit -> I downloaded the serverstarter from here to find out my pain was not in vain
The following is a Problem:
the page you used in your project as an api got removed
I edited the code so that this Error:

[12:31:02] [ERROR] Error while trying to get URL from cursemeta for mod ModEntryRaw(projectID=405744, fileID=3296428)

java.io.IOException: Request to https://cursemeta.dries007.net/405744/3296428.json was not successful.
        at atm.bloodworkxgaming.serverstarter.packtype.curse.CursePackType.downloadMods$lambda-3(CursePackType.kt:203)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
        at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
        at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
        at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
        at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

Does not happen anymore
you may use this code in your project
you are welcome

(maaan i really felt so dumb when i read the latest push: moved to new api, But it turns out i wasn't wrong...)

package atm.bloodworkxgaming.serverstarter.packtype.curse

import atm.bloodworkxgaming.serverstarter.InternetManager
import atm.bloodworkxgaming.serverstarter.ServerStarter.Companion.LOGGER
import atm.bloodworkxgaming.serverstarter.config.ConfigFile
import atm.bloodworkxgaming.serverstarter.packtype.AbstractZipbasedPackType
import atm.bloodworkxgaming.serverstarter.packtype.writeToFile
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import okhttp3.Request
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStreamReader
import java.net.URISyntaxException
import java.nio.file.PathMatcher
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicInteger
import java.util.regex.Pattern
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream


open class CursePackType(private val configFile: ConfigFile) : AbstractZipbasedPackType(configFile) {
    private var forgeVersion: String = configFile.install.loaderVersion
    private var mcVersion: String = configFile.install.mcVersion
    private val oldFiles = File(basePath + "OLD_TO_DELETE/")

    override fun cleanUrl(url: String): String {
        if (url.contains("curseforge.com") && !url.endsWith("/download"))
            return "$url/download"

        return url
    }

    /**
     * Gets the forge version, can be based on the version from the downloaded pack
     *
     * @return String representation of the version
     */
    override fun getForgeVersion(): String {
        return forgeVersion
    }

    /**
     * Gets the forge version, can be based on the version from the downloaded pack
     *
     * @return String representation of the version
     */
    override fun getMCVersion(): String {
        return mcVersion
    }

    @Throws(IOException::class)
    override fun handleZip(file: File, pathMatchers: List<PathMatcher>) {
        // delete old installer folder
        FileUtils.deleteDirectory(oldFiles)

        // start with deleting the mods folder as it is not guaranteed to have override mods
        val modsFolder = File(basePath + "mods/")

        if (modsFolder.exists())
            FileUtils.moveDirectory(modsFolder, File(oldFiles, "mods"))
        LOGGER.info("Moved the mods folder")

        LOGGER.info("Starting to unzip files.")
        // unzip start
        try {
            ZipInputStream(FileInputStream(file)).use { zis ->
                var entry: ZipEntry? = zis.nextEntry

                loop@ while (entry != null) {
                    LOGGER.info("Entry in zip: $entry", true)
                    val name = entry.name

                    // special manifest treatment
                    if (name == "manifest.json")
                        zis.writeToFile(File(basePath + "manifest.json"))


                    // overrides
                    if (name.startsWith("overrides/")) {
                        val path = entry.name.substring(10)

                        when {
                            pathMatchers.any { it.matches(Paths.get(path)) } ->
                                LOGGER.info("Skipping $path as it is on the ignore List.", true)


                            !name.endsWith("/") -> {
                                val outfile = File(basePath + path)
                                LOGGER.info("Copying zip entry to = $outfile", true)


                                outfile.parentFile?.mkdirs()

                                zis.writeToFile(outfile)
                            }

                            name != "overrides/" -> {
                                val newFolder = File(basePath + path)
                                if (newFolder.exists())
                                    FileUtils.moveDirectory(newFolder, File(oldFiles, path))

                                LOGGER.info("Folder moved: " + newFolder.absolutePath, true)
                            }
                        }
                    }

                    entry = zis.nextEntry
                }


                zis.closeEntry()
            }
        } catch (e: IOException) {
            LOGGER.error("Could not unzip files", e)
        }

        LOGGER.info("Done unzipping the files.")
    }

    @Throws(IOException::class)
    override fun postProcessing() {
        val mods = ArrayList<ModEntryRaw>()

        InputStreamReader(FileInputStream(File(basePath + "manifest.json")), "utf-8").use { reader ->
            val json = JsonParser().parse(reader).asJsonObject
            LOGGER.info("manifest JSON Object: $json", true)
            val mcObj = json.getAsJsonObject("minecraft")

            if (mcVersion.isEmpty()) {
                mcVersion = mcObj.getAsJsonPrimitive("version").asString
            }

            // gets the forge version
            if (forgeVersion.isEmpty()) {
                val loaders = mcObj.getAsJsonArray("modLoaders")
                if (loaders.size() > 0) {
                    forgeVersion = loaders[0].asJsonObject.getAsJsonPrimitive("id").asString.substring(6)
                }
            }

            // gets all the mods
            for (jsonElement in json.getAsJsonArray("files")) {
                val obj = jsonElement.asJsonObject
                mods.add(ModEntryRaw(
                        obj.getAsJsonPrimitive("projectID").asString,
                        obj.getAsJsonPrimitive("fileID").asString))
            }
        }

        downloadMods(mods)
    }

    /**
     * Downloads the mods specified in the manifest
     * Gets the data from cursemeta
     *
     * @param mods List of the mods from the manifest
     */
    private fun downloadMods(mods: List<ModEntryRaw>) {
        val ignoreSet = HashSet<String>()
        val ignoreListTemp = configFile.install.getFormatSpecificSettingOrDefault<List<Any>>("ignoreProject", null)

        if (ignoreListTemp != null)
            for (o in ignoreListTemp) {
                if (o is String)
                    ignoreSet.add(o)

                if (o is Int)
                    ignoreSet.add(o.toString())
            }


        val urls = ConcurrentLinkedQueue<String>()

        LOGGER.info("Requesting Download links from cursemeta.")

        mods.parallelStream().forEach { mod ->
            if (ignoreSet.isNotEmpty() && ignoreSet.contains(mod.projectID)) {
                LOGGER.info("Skipping mod with projectID: " + mod.projectID)
                return@forEach
            }

            val url = "https://api.cfwidget.com/${mod.projectID}?version=${mod.fileID}"
            LOGGER.info("Download url is: $url", true)

            try {
                val request = Request.Builder()
                        .url(url)
                        .header("User-Agent", "All the mods server installer.")
                        .header("Content-Type", "application/json")
                        .build()

                val res = InternetManager.httpClient.newCall(request).execute()

                if (!res.isSuccessful)
                    throw IOException("Request to $url was not successful.")
                val body = res.body ?: throw IOException("Request to $url returned a null body.")
                try {
                    val jsonRes = JsonParser().parse(body.string()).asJsonObject
                    LOGGER.info("Response from manifest query: $jsonRes", true)
                    var arr = jsonRes.asJsonObject.getAsJsonObject("download");
                    var part1 = mod.fileID.subSequence(0, 4).toString();
                    var part2 = mod.fileID.split(part1)[1];
                    //         https://mediafilez.forgecdn.net/files/2743/38/useful_backpacks-1.16.5-1.12.1.90.jar
                    //         https://mediafilez.forgecdn.net/files/3350/860/useful_backpacks-1.16.5-1.12.1.90.jar
                    //         https://mediafilez.forgecdn.net/files/3871/353/MouseTweaks-forge-mc1.19-2.23.jar
                    //         https://mediafilez.forgecdn.net/files/3398//CosmeticArmorReworked-1.16.5-v4.jar
                    //         https://mediafilez.forgecdn.net/files/3398/0/CosmeticArmorReworked-1.16.5-v4.jar
                    //         https://mediafilez.forgecdn.net/files/3407/020/archers_paradox-1.16.5-1.3.1.jar
                    //         https://mediafilez.forgecdn.net/files/3407/20/archers_paradox-1.16.5-1.3.1.jar
                    val regx = "^0+\$".toRegex();
                    if(!regx.containsMatchIn(part2))
                        part2 = part2.replace("^0*".toRegex(),"");
                    else
                        part2 = "0";
                    var url = "https://mediafilez.forgecdn.net/files/$part1/$part2/${arr["name"].asString.replace("+","%2B")}"
                    LOGGER.info(url)
                    urls.add(url)
                }
                catch(e:Exception)
                {

                }


            } catch (e: IOException) {
                LOGGER.error("Error while trying to get URL from cursemeta for mod $mod", e)
            }
        }

        LOGGER.info("Mods to download: $urls", true)

        processMods(urls)

    }

    /**
     * Downloads all mods, with a second fallback if failed
     * This is done in parallel for better performance
     *
     * @param mods List of urls
     */
    private fun processMods(mods: Collection<String>) {
        // constructs the ignore list
        val ignorePatterns = ArrayList<Pattern>()
        for (ignoreFile in configFile.install.ignoreFiles) {
            if (ignoreFile.startsWith("mods/")) {
                ignorePatterns.add(Pattern.compile(ignoreFile.substring(ignoreFile.lastIndexOf('/'))))
            }
        }

        // downloads the mods
        val count = AtomicInteger(0)
        val totalCount = mods.size
        val fallbackList = ArrayList<String>()

        mods.stream().parallel().forEach { s -> processSingleMod(s, count, totalCount, fallbackList, ignorePatterns) }

        val secondFail = ArrayList<String>()
        fallbackList.forEach { s -> processSingleMod(s, count, totalCount, secondFail, ignorePatterns) }

        if (secondFail.isNotEmpty()) {
            LOGGER.warn("Failed to download (a) mod(s):")
            for (s in secondFail) {
                LOGGER.warn("\t" + s)
            }
        }
    }

    /**
     * Downloads a single mod and saves to the /mods directory
     *
     * @param mod            URL of the mod
     * @param counter        current counter of how many mods have already been downloaded
     * @param totalCount     total count of mods that have to be downloaded
     * @param fallbackList   List to write to when it failed
     * @param ignorePatterns Patterns of mods which should be ignored
     */
    private fun processSingleMod(mod: String, counter: AtomicInteger, totalCount: Int, fallbackList: MutableList<String>, ignorePatterns: List<Pattern>) {
        try {
            val modName = FilenameUtils.getName(mod)
            for (ignorePattern in ignorePatterns) {
                if (ignorePattern.matcher(modName).matches()) {
                    LOGGER.info("[" + counter.incrementAndGet() + "/" + totalCount + "] Skipped ignored mod: " + modName)
                }
            }

            InternetManager.downloadToFile(mod, File(basePath + "mods/" + modName))
            LOGGER.info("[" + String.format("% 3d", counter.incrementAndGet()) + "/" + totalCount + "] Downloaded mod: " + modName)

        } catch (e: IOException) {
            LOGGER.error("Failed to download mod", e)
            fallbackList.add(mod)

        } catch (e: URISyntaxException) {
            LOGGER.error("Invalid url for $mod", e)
        }
    }
}

/**
 * Data class to keep projectID and fileID together
 */
data class ModEntryRaw(val projectID: String, val fileID: String)
@anonymusdennis anonymusdennis changed the title Are you kidding me Are you kidding me... no you not (FIX FOR API REMOVAL) Nov 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant