Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v0.4.2 - Snapshot
* Updated dependencies
* Moving to jitpack

## v0.4.1 - Snapshot
* Added custom blossom injector dependency
* Updated dependencies
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[![](https://jitci.com/gh/TheProgramSrc/SimpleCoreAPI/svg)](https://jitci.com/gh/TheProgramSrc/SimpleCoreAPI)
[![](https://jitpack.io/v/TheProgramSrc/SimpleCoreAPI.svg)](https://jitpack.io/#TheProgramSrc/SimpleCoreAPI)

# SimpleCoreAPI
_The best way to create a plugin_<br>
[![Latest Release](https://img.shields.io/nexus/r/xyz.theprogramsrc/simplecoreapi?color=%2300ff00&label=Latest%20Release&nexusVersion=3&server=https%3A%2F%2Frepo.theprogramsrc.xyz)](https://repo.theprogramsrc.xyz/#browse/browse:maven-releases)
[![Latest Snapshot](https://img.shields.io/badge/dynamic/xml?url=https://repo.theprogramsrc.xyz/repository/maven-snapshots/xyz/theprogramsrc/simplecoreapi/maven-metadata.xml&label=Latest%20Snapshot&color=orange&query=.//versioning/latest&prefix=v)](https://repo.theprogramsrc.xyz/#browse/browse:maven-snapshots)

<br>
[![Discord](https://i.imgur.com/J1XhmMd.png)](https://go.theprogramsrc.xyz/discord)
[![Terms of Service](https://i.imgur.com/4tFAGtE.png)](https://go.theprogramsrc.xyz/tos)
Expand Down
11 changes: 2 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
id 'org.jetbrains.dokka' version '1.7.10'
}

def projectVersion = (System.getenv("VERSION") ?: '0.4.1-SNAPSHOT').replaceFirst("v", "").replace('/', '')
def projectVersion = (System.getenv("VERSION") ?: '0.4.2-SNAPSHOT').replaceFirst("v", "").replace('/', '')

group 'xyz.theprogramsrc'
version projectVersion
Expand All @@ -15,7 +15,6 @@ description 'The best way to create a plugin'
repositories {
mavenCentral()

maven { url 'https://repo.theprogramsrc.xyz/repository/maven-public/' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://oss.sonatype.org/content/repositories/releases/' }
maven { url 'https://oss.sonatype.org/content/groups/public/' }
Expand All @@ -34,7 +33,7 @@ dependencies {
implementation 'org.jetbrains:annotations:23.0.0'
implementation 'commons-io:commons-io:2.11.0'
implementation 'com.google.code.gson:gson:2.9.1'
implementation 'net.lingala.zip4j:zip4j:2.11.1'
implementation 'net.lingala.zip4j:zip4j:2.11.2'

annotationProcessor 'com.velocitypowered:velocity-api:3.1.1'

Expand Down Expand Up @@ -101,12 +100,6 @@ tasks.named("dokkaHtml") {
publishing {
repositories {
if(System.getenv('env') == 'prod') {
maven {
name = 'TheProgramSrc'
credentials.username = System.getenv('NEXUS_USERNAME')
credentials.password = System.getenv('NEXUS_PASSWORD')
url = uri(version.contains('-SNAPSHOT') ? 'https://repo.theprogramsrc.xyz/repository/maven-snapshots/' : 'https://repo.theprogramsrc.xyz/repository/maven-releases/')
}
maven {
name = 'GitHubPackages'
url = 'https://maven.pkg.github.com/TheProgramSrc/SimpleCoreAPI'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,43 @@
package xyz.theprogramsrc.simplecoreapi.global.module

import com.google.gson.JsonObject
import com.google.gson.JsonParser
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.net.URL
import java.security.MessageDigest
import java.util.*
import java.util.jar.JarFile

/**
* Module Helper to Download or Sort modules
*/
object ModuleHelper {

private var lastRepoUpdate = 0L // Used to avoid timeouts

/**
* Downloads a Module from the database
* @param repositoryId Identifier of the artifact inside the repository
* @param repository Repository of a module to download. (Must be in GitHub format 'User/Repository'. Example: 'TheProgramSrc/SimpleCore-UIsModule')
* @param fileName The name of the file. This can be fetched using the repository metadata.
* @param downloadLocation Location to download the module. (Defaults to plugins/SimpleCoreAPI/modules/)
* @return true if the module was downloaded, false otherwise
*/
fun downloadModule(repositoryId: String, downloadLocation: File = File("plugins/SimpleCoreAPI/modules/")): Boolean{
fun downloadModule(repository: String, fileName: String, downloadLocation: File = File("plugins/SimpleCoreAPI/modules/")): Boolean{
if(!downloadLocation.exists()) downloadLocation.mkdirs()
validateRepositories()
val repo = (JsonParser.parseString(File("plugins/SimpleCoreAPI/repositories.json").readText()).asJsonArray.firstOrNull { element ->
JsonParser.parseString(URL("https://${parseHost(element.asJsonObject.get("url").asString)}/service/rest/v1/search?repository=${element.asJsonObject.get("repo").asString}&format=maven2&maven.artifactId=$repositoryId&maven.extension=jar&sort=version").readText()).asJsonObject.get("items").asJsonArray.size() > 0
} ?: return false).asJsonObject
val artifact = ((JsonParser.parseString(URL("https://${parseHost(repo.get("url").asString)}/service/rest/v1/search/?repository=${repo.get("repo").asString}&format=maven2&maven.artifactId=$repositoryId&maven.extension=jar&sort=version").readText()).asJsonObject.get("items").asJsonArray.firstOrNull { it.asJsonObject.get("repository").asString.equals(repo.get("repo").asString) } ?: return false).asJsonObject.get("assets").asJsonArray.firstOrNull {
if(!it.asJsonObject.get("maven2").asJsonObject.get("extension").asString.equals("jar")){
return@firstOrNull false // Check that this is a jar file (not checksums or pom)
}

if(it.asJsonObject.get("maven2").asJsonObject.has("classifier")){
if(it.asJsonObject.get("maven2").asJsonObject.get("classifier").asString.equals("sources")){
return@firstOrNull false // Check that this is not a sources jar file
}

if(it.asJsonObject.get("maven2").asJsonObject.get("classifier").asString.equals("javadoc")){
return@firstOrNull false // Check that this is not a javadoc jar file
}
}

if(!it.asJsonObject.get("maven2").asJsonObject.get("artifactId").asString.equals(repositoryId)) {
return@firstOrNull false // Check that this is the correct artifact
}

if(!it.asJsonObject.get("contentType").asString.equals("application/java-archive")){
return@firstOrNull false // Check that this is a jar file
}

return@firstOrNull true
} ?: return false).asJsonObject
return artifact.get("downloadUrl").asString.let {
val bytes = URL(it).readBytes()
val sha512 = MessageDigest.getInstance("SHA-512").digest(bytes)
val file = File(downloadLocation, "$repositoryId.jar")
file.writeBytes(bytes)
val fileSha512 = MessageDigest.getInstance("SHA-512").digest(file.readBytes())
Arrays.equals(sha512, fileSha512)
val releases = JsonParser.parseString(URL("https://api.github.com/repos/$repository/releases").readText()).asJsonArray // Get the repo releases list
if(releases.isEmpty) // If empty stop
return false
val latestRelease = releases[0].asJsonObject
val assets = JsonParser.parseString(URL(latestRelease.get("assets_url").asString).readText()).asJsonArray // List all the available assets
if(assets.isEmpty)
return false
assets.find { it.asJsonObject.get("name").asString.endsWith(".jar") }?.asJsonObject?.get("browser_download_url")?.asString.let { // Find the first asset that's a .jar (Should be only one, but let's check just in case)
val bytes = URL(it).readBytes() // Read bytes
val file = File(downloadLocation, "$fileName.jar") // Create the file
if(!file.exists()) file.createNewFile()
file.writeBytes(bytes) // Overwrite the bytes with the new data
}
return true // At this point everything went well!
}

/**
Expand Down Expand Up @@ -87,26 +66,9 @@ object ModuleHelper {
}

/**
* Ensures that the repositories are up-to-date
*/
private fun validateRepositories(){
val file = File("plugins/SimpleCoreAPI/repositories.json")
val onlineBytes = URL("https://raw.githubusercontent.com/TheProgramSrc/PluginsResources/master/SimpleCoreAPI/repositories.json").readBytes()
if(!file.exists()) file.createNewFile()
file.writeBytes(onlineBytes) // Always overwrite the file
}

/**
* Parses the host from a URL
* @param url URL to parse
* @return Host of the URL
*/
private fun parseHost(url: String): String = if(url.startsWith("http")) url.split("://")[1].split("/")[0] else url.split("/")[0]

/**
* Scans the given folder for jar files and then scan
* every jar file to download the required modules
*/
* Scans the given folder for jar files and then scan
* every jar file to download the required modules
*/
fun scanRequiredModules(folder: File = File(".")): Unit = (folder.listFiles() ?: emptyArray()).forEach {
if(it.isDirectory) {
scanRequiredModules(it)
Expand All @@ -115,24 +77,58 @@ object ModuleHelper {
}
}

/**
* Updates the modules repository cache
*/
fun updateRepository(){
// To allow the development of modules and testing we'll let devs provide the environment variable 'SCAPI_NO_REPO_UPDATE'
if(System.getenv("SCAPI_NO_REPO_UPDATE") != null)
return

val now = System.currentTimeMillis()
if(lastRepoUpdate == 0L || (lastRepoUpdate - now) > 30000L){
val file = File("plugins/SimpleCoreAPI/modules-repository.json")
val onlineBytes = URL("https://github.com/TheProgramSrc/GlobalDatabase/raw/master/SimpleCoreAPI/modules-repository.json").readBytes() // Get the online version
if(!file.exists()) file.createNewFile() // Create the file
file.writeBytes(onlineBytes) // Overwrite file
lastRepoUpdate = now // Update the update time
}
}

/**
* Gets the module metadata from the repository
* @param moduleId The id of the module to fetch the metadata
* @return the given module metadata if it's under the modules reposutory, otherwise null.
*/
fun getModuleMeta(moduleId: String): JsonObject? {
updateRepository() // First we update the repo
val json = JsonParser.parseString(File("plugins/SimpleCoreAPI/modules-repository.json").readText()).asJsonObject
return if(json.has(moduleId)) json.getAsJsonObject(moduleId) else null
}

/**
* Scans the given [File] for the simplecoreapi.modules
* file and loads the required modules if any
* @param file File to scan.
* @param downloadLocation Location to download the modules. (Defaults to plugins/SimpleCoreAPI/modules/)
*/
fun downloadRequiredModules(file: File, downloadLocation: File = File("plugins/SimpleCoreAPI/modules/")){
updateRepository() // First we update the repository
if(file.extension != "jar") return
try {
JarFile(file).use { jarFile ->
val jarEntry = jarFile.getJarEntry("simplecoreapi.modules")
JarFile(file).use { jarFile -> // Now we check for every file
val jarEntry = jarFile.getJarEntry("simplecoreapi.modules") // If we find simplecoreapi.modules
if (jarEntry != null) {
val inputStream = jarFile.getInputStream(jarEntry)
val reader = BufferedReader(InputStreamReader(inputStream))
reader.readLines().forEach {
if(it.isNotBlank() && it.isNotEmpty() && !it.startsWith("#")) {
if(!File(downloadLocation, "$it.jar").exists()){
downloadModule(it)
val inputStream = jarFile.getInputStream(jarEntry) // Read the file
val reader = BufferedReader(InputStreamReader(inputStream)) // Create the reader
reader.readLines().forEach { // Read every line
if(it.isNotBlank() && it.isNotEmpty() && !it.startsWith("#")) { // Check that is not a blank line nor a comment
val meta = getModuleMeta(it) // Fetch the metadata
if(meta != null){
if(!File(downloadLocation, "${meta.get("file_name").asString}.jar").exists()){
val repo = if(meta.has("repository")) meta.get("repository").asString else "TheProgramSrc/SimpleCore-$it" // Generate default repo if not found
downloadModule(repo, meta.get("file_name").asString) // Download the module
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,17 @@ class ModuleManager(private val logger: ILogger) {
val autoUpdate = config["auto-update"] == "true" // Check if we have enabled the auto updater
if(isAvailable && autoUpdate){ // Download an update if there is one available and the auto updater is enabled
logger.info("An update for the module ${description.name} is available. Downloading and updating...")
if(ModuleHelper.downloadModule(description.repositoryId, File("plugins/SimpleCoreAPI/update/").apply { if(!exists()) mkdirs() })){
logger.info("Successfully updated the module ${description.name}")
updatedModules.add(description.name)
val meta = ModuleHelper.getModuleMeta(description.repositoryId)
if(meta == null) {
logger.error("Failed to update the module ${description.name}. Please download manually from ${if(description.githubRepository.isBlank()) "https://github.com/${description.githubRepository}/releases/latest" else " the module page."}")
} else {
logger.error("Failed to update the module ${description.name}. Please download manually from https://github.com/${description.githubRepository}/releases/latest")
val repo = if(meta.has("repository")) meta.get("repository").asString else "TheProgramSrc/SimpleCore-${description.repositoryId}" // Generate default repo if not found
if(ModuleHelper.downloadModule(repo, meta.get("file_name").asString, File("plugins/SimpleCoreAPI/update/").apply { if(!exists()) mkdirs() })){
logger.info("Successfully updated the module ${description.name}")
updatedModules.add(description.name)
} else {
logger.error("Failed to update the module ${description.name}. Please download manually from https://github.com/${description.githubRepository}/releases/latest")
}
}
} else if(isAvailable){ // Notify the user that an update is available
checker.checkWithPrint()
Expand Down Expand Up @@ -171,7 +177,9 @@ class ModuleManager(private val logger: ILogger) {
// Loop through the dependencies and download the missing ones
val downloadedModules: MutableList<String> = ArrayList()
for (dependencyId in dependencies.filter { it.isNotBlank() && !modules.any { entry -> entry.value.repositoryId == it } }) {
if (ModuleHelper.downloadModule(dependencyId)) {
val meta = ModuleHelper.getModuleMeta(dependencyId) ?: throw ModuleDownloadException("Failed to download module with id '$dependencyId'")
val repo = if(meta.has("repository")) meta.get("repository").asString else "TheProgramSrc/SimpleCore-${dependencyId}" // Generate default repo if not found
if (ModuleHelper.downloadModule(repo, meta.get("file_name").asString)) {
downloadedModules.add(dependencyId)
} else {
throw ModuleDownloadException("Failed to download module with id '$dependencyId'")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ internal class ModuleHelperTest {

@Test
fun downloadModule() {
assertTrue(ModuleHelper.downloadModule("loggingmodule")) // Test the download
assertTrue(ModuleHelper.downloadModule("TheProgramSrc/SimpleCore-TasksModule", "TasksModule")) // Test the download
assertTrue(File("plugins/SimpleCoreAPI/modules/TasksModule.jar").exists())
File("plugins/").deleteRecursively() // Recursively delete the plugins folder
}

Expand Down