import java.io.OutputStreamWriter import java.net.URL import java.net.HttpURLConnection import java.time.LocalDate // We can't import libraries here for some reason, so we must use what is provided // by gradle, which isn't much. The groovy JSON library is meant for use by groovy code, // so we need some creativity to use it in Kotlin. import org.apache.groovy.json.internal.LazyMap import groovy.json.JsonOutput import groovy.json.JsonSlurper // Run this as `./gradlew app:updateInstances` tasks.register("updateInstances") { description = "Fetches a list of popular Lemmy instances and writes it to the DefaultInstances.kt file" // All lemmy instances with at least this amount of monthly active users will be included. minimumMAU.set(50) endpointUrl.set("https://api.fediverse.observer/") instancesFile.set(file("src/main/java/com/jerboa/DefaultInstances.kt")) manifestFile.set(file("src/main/AndroidManifest.xml")) nsfwList.set(listOf("lemmynsfw.com")) } @UntrackedTask(because = "Output depends on api response") abstract class UpdateInstancesTask: DefaultTask() { private companion object { const val START_TAG = "" const val END_TAG = "" const val INDENT = 14 } @get:Input abstract val minimumMAU: Property @get:Input abstract val endpointUrl: Property @get:Input abstract val nsfwList: ListProperty @get:OutputFile abstract val instancesFile: RegularFileProperty @get:OutputFile abstract val manifestFile: RegularFileProperty // Some extension methods to make the JsonSlurper output easier to process fun LazyMap.getMap(key: String): LazyMap { return this[key] as LazyMap } fun LazyMap.getArray(key: String): ArrayList<*> { return this[key] as ArrayList<*> } @Suppress("UNCHECKED_CAST") fun LazyMap.getAs(key: String): T { return this[key] as T } @TaskAction fun action() { // Get sorted list of nodes val nodes = getData() .getMap("data") .getArray("nodes") .map { val name = (it as LazyMap)["domain"] as String val users = it["active_users_monthly"] as Int? Pair(name, users ?: 0) } .filter { it.second >= minimumMAU.get() && !nsfwList.get().contains(it.first) } .sortedBy { it.second } .reversed() updateInstanceList(nodes) updateManifest(nodes.map { it.first }) } private fun getData(): LazyMap { val url = URL(endpointUrl.get()) val query = """ { nodes(softwarename: "lemmy") { domain active_users_monthly } }""".trimIndent() // Format JSON request body val body = JsonOutput.toJson(mapOf("query" to query)) // Create POST request val req = url.openConnection() as HttpURLConnection req.requestMethod = "POST" req.doOutput = true req.setRequestProperty("Content-Type", "application/json") // Write body to request OutputStreamWriter(req.outputStream, "UTF-8").use { it.write(body) } // Get response and JSON parse it return JsonSlurper().parse(req.inputStream.reader()) as LazyMap } fun updateInstanceList(nodes: List>) { // Create output file and write header val outFile = instancesFile.get().asFile outFile.writeText(""" /*********************************************** * WARNING: AUTO-GENERATED FILE * ***********************************************/ // The following file is automatically generated by the script "update_instance_gradle.kts". // Caution: Manual modifications to this file may be overwritten without notice. // It is recommended to make changes to the code section responsible for the generation. // Date of Auto-generation: ${LocalDate.now()} package com.jerboa val DEFAULT_LEMMY_INSTANCES = setOf( """.trimIndent() ) // Write each node's name, one per line for (n in nodes) { outFile.appendText(" \"${n.first}\", // ${n.second} monthly users\n") } outFile.appendText(")\n") } fun updateManifest(list: List) { val manifest = manifestFile.get().asFile val lines = manifest.readLines() manifest.writeText("") var skip = false for (line in lines) { if (line.trim() == START_TAG) { skip = true manifest.appendText(" ".repeat(INDENT) + START_TAG) manifest.appendText(genManifestHosts(list)) manifest.appendText(" ".repeat(INDENT) + END_TAG + System.lineSeparator()) } else if (line.trim() == END_TAG) { skip = false } else if (!skip) { manifest.appendText(line + System.lineSeparator()) } } } fun genManifestHosts(list: List): String { return list.joinToString( separator = System.lineSeparator(), prefix = System.lineSeparator(), postfix = System.lineSeparator(), ) { " ".repeat(INDENT) + "" } } }