{"id":19149100,"url":"https://github.com/flytegg/twilight","last_synced_at":"2025-04-06T11:07:53.528Z","repository":{"id":185406658,"uuid":"667613985","full_name":"flytegg/twilight","owner":"flytegg","description":"A Kotlin API for Spigot/Paper servers providing essential utilities to streamline development.","archived":false,"fork":false,"pushed_at":"2025-03-09T20:40:29.000Z","size":592,"stargazers_count":78,"open_issues_count":22,"forks_count":13,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-30T10:06:42.598Z","etag":null,"topics":["api","java","kotlin","minecraft","paper","spigot","twilight"],"latest_commit_sha":null,"homepage":"https://discord.gg/flyte","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flytegg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-17T23:22:21.000Z","updated_at":"2025-03-19T00:50:21.000Z","dependencies_parsed_at":"2023-11-25T01:24:35.850Z","dependency_job_id":"45028b57-e829-46ca-8b41-ba1c0abe4651","html_url":"https://github.com/flytegg/twilight","commit_stats":{"total_commits":266,"total_committers":13,"mean_commits":20.46153846153846,"dds":0.4548872180451128,"last_synced_commit":"e5c712f615604a2a499d4b64cc2876fe3cd9367e"},"previous_names":["flytegg/twilight"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flytegg%2Ftwilight","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flytegg%2Ftwilight/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flytegg%2Ftwilight/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flytegg%2Ftwilight/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flytegg","download_url":"https://codeload.github.com/flytegg/twilight/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247471518,"owners_count":20944158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","java","kotlin","minecraft","paper","spigot","twilight"],"created_at":"2024-11-09T08:06:30.668Z","updated_at":"2025-04-06T11:07:53.484Z","avatar_url":"https://github.com/flytegg.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Twilight ✨\n\nTwilight is an API for developers creating plugins for Spigot (or forks like Paper, Purpur, Pufferfish etc.) based Minecraft servers. It contains a\nwide range of utilities and QOL improvements, from inventories, to schedulers and databases.\n\nTwilight is built using **Kotlin**, and is recommended for usage with. Many features of Twilight should work with plain Java, though compatibility is\nnot guaranteed.\n\nFor support, questions or to chat with the team, come join the Discord:\n\n[![Discord Banner](https://discordapp.com/api/guilds/835561528299880518/widget.png?style=banner2)](https://discord.gg/flyte)\n\n# Table of Contents\n\n- [Extension Functions](#extension-functions)\n- [Events](#events)\n  - [Custom Events](#custom-events)\n  - [Additional Events](#additional-events)\n  - [Custom ChatClickEvent](#custom-chatclickevent)\n- [Scheduler](#scheduler)\n- [Scoreboard](#scoreboard)\n  - [Sidebars](#sidebars)\n  - [Below Name Display](#below-name-display)\n  - [Tab List (Player List Header/Footer)](#tab-list-player-list-headerfooter)\n  - [Player Prefixes and Suffixes](#player-prefixes-and-suffixes)\n- [GUI Builder](#gui-builder)\n- [Databases](#databases)\n  - [MongoDB](#mongodb)\n  - [SQL (MySQL, Postgres)](#sql-mysql-postgres)\n- [Ternary Operator](#ternary-operator)\n- [UUID ⟷ Name](#uuid--name)\n- [Redis](#redis)\n  - [String Key-Value Pairs](#string-key-value-pairs)\n  - [Publishing Messages](#publishing-messages)\n  - [Redis Listeners (PubSub)](#redis-listeners-pubsub)\n- [Files Extensions](#files-extensions)\n- [Symbols](#symbols)\n- [Libraries](#libraries)\n  - [GSON](#gson)\n  \n# Setup\n\nTwilight should be bundled within your plugin. Add the following repository and dependency to your build tool:\n\nMaven\n\n```xml\n\n\u003crepository\u003e\n    \u003cid\u003eflyte-repository-releases\u003c/id\u003e\n    \u003cname\u003eFlyte Repository\u003c/name\u003e\n    \u003curl\u003ehttps://repo.flyte.gg/releases\u003c/url\u003e\n\u003c/repository\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003egg.flyte\u003c/groupId\u003e\n    \u003cartifactId\u003etwilight\u003c/artifactId\u003e\n    \u003cversion\u003e1.1.19\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nGradle (Groovy DSL)\n\n```groovy\nmaven {\n    url \"https://repo.flyte.gg/releases\"\n}\n\nimplementation \"gg.flyte:twilight:1.1.19\"\n```\n\nGradle (Kotlin DSL)\n\n```kotlin\nmaven(\"https://repo.flyte.gg/releases\")\n\nimplementation(\"gg.flyte:twilight:1.1.19\")\n```\n\nCertain features of Twilight require configuration, which can be done via the Twilight class. To set up a Twilight class instance, you can use the\n`twilight` function as shown below:\n\n```kotlin\nval twilight = twilight(this)\n```\n\nIf you want to make use of environment variables (.env files), you can configure your usage of these here, like so:\n\n```kotlin\nval twilight = twilight(this) {\n    env {\n        useDifferentEnvironments = true\n        devEnvFileName = \".env.dev\"\n        prodEnvFileName = \".env.prod\"\n    }\n}\n```\n\nThe above are the default env configuration settings.\n\nIf you `useDifferentEnvironments`, you'll need a `.env` file which contains the following:\n\n```env\nENVIRONMENT=DEV # or PROD\n```\n\nThis file determines whether to use your dev .env or your prod .env.\n\nIf you do not use this different environments feature, then it will just use the .env (or whatever you specify the name as with `prodEnvFileName`).\n\nThroughout your project you can use `Environment.get(\"VARIABLE\")` to retrieve a value from your environment variables.\n\nOther features that can be configured in the Twilight class builder will have their own sections later in the README.\n\n# Features\n\n## Extension Functions\n\nTwilight takes advantage of Kotlin's extension functions to add additional functions to various classes used within the API. Many of these are\nconvenience functions, and some add complete new functionality. To see all the functions added, view\nthem [inside the code](https://github.com/flytegg/twilight/tree/master/src/main/kotlin/gg/flyte/twilight/extension).\n\n## Events\n\nWe have a neat way to handle code for events, which register by themselves, so you don't have to!\n\nYou can make use of it like so:\n\n```kt\nevent\u003cPlayerJoinEvent\u003e {\n    player.sendMessage(\"Welcome to the server!\")\n}\n```\n\nIf you need to change the `eventPriority` or the `ignoreCancelled`, you can pass it to the function call like:\n\n```kt\nevent\u003cPlayerJoinEvent\u003e(EventPriority.HIGHEST, true) {\n    player.sendMessage(\"Welcome to the server!\")\n}\n```\n\nIf you ever need an instance of the Listener class that the event gets put in to, it's returned by the function. Specifically, it returns\n`TwilightListener`. This class has a convenience function in for unregistering the listener, it can be used like so:\n\n```kt\nval listener = event\u003cPlayerJoinEvent\u003e {\n    player.sendMessage(\"Welcome to the server!\")\n}\n\nlistener.unregister()\n```\n\n### Custom Events\n\nInstead of having to extend the org.bukkit.event.Event and adding all the extra boilerplate yourself, you can simply extend `TwilightEvent`!\n\nHere's an example:\n\n```kt\nclass MyCustomEvent : TwilightEvent() {\n\n}\n```\n\nThis is much easier than the standard Bukkit Event, as you don't have to worry about defining handles, etc.\n\nThe TwilightEvent also includes a timestamp which provides an Instant for when the event was run:\n\n```kt\n// with the above event example\nevent\u003cMyCustomEvent\u003e {\n    println(\"Time event was executed: $timestamp\")\n}\n```\n\nYou can declare that it should be asynchronous by passing a `true` value to the TwilightEvent constructor:\n\n```kt\nclass MyCustomEvent : TwilightEvent(true) {\n\n}\n\nclass MyCustomEvent(async: Boolean) : TwilightEvent(async) {\n\n}\n```\n\n### Additional Events\n\nTwilight provides additional events which are not found in Spigot or Paper. These are:\n\n- PlayerMainHandInteractEvent\n- PlayerOffHandInteractEvent\n- PlayerOpChangeEvent\n- PlayerOpEvent\n- PlayerDeopEvent\n- ChatClickEvent [(see below)](#custom-chatclickevent)\n\nYou can opt out of Twilight calling these events. For example:\n\n```kotlin\ndisableCustomEventListeners(OpEventListener, InteractEventListener)\n```\n\n### Custom ChatClickEvent\n\nDue to limitations imposed by the Minecraft server software, when interacting with a clickable message in chat or in a book the only response options\nare `RUN_COMMAND`, `SUGGEST_COMMAND`, `CHANGE_PAGE`, `COPY_TO_CLIPBOARD`, `OPEN_FILE` and `OPEN_URL`. None of these match the most common use case:\nrunning custom code. Twilight utilizes the `RUN_COMMAND` response to call a custom `ChatClickEvent` which can be listened to like a regular event.\n\nTo use this feature, where you would normally build your clickable message, use the Twilight extension functions to add a custom click event. Twilight\nwill then redirect any data which you put in the parameters to be accessible as a variable from within the `ChatClickEvent` when the player clicks the\nmessage.\n\nFor Paper/Adventure (recommended):\n\n```kotlin\nimport net.kyori.adventure.text.Component\n\nComponent.text(\"Click here\")\n    .customClickEvent(\"openGUI\", \"warps\")\n```\n\nOr for Spigot/BungeeCord:\n\n```kotlin\nimport net.md_5.bungee.api.chat.TextComponent\n\nTextComponent(\"Click here\")\n    .customClickEvent(\"openGUI\", \"warps\")\n```\n\nFrom there, simply listen to the event as normal, and access the data attached to the message the player has clicked. In this basic example,\ninformation to open a \"warps\" GUI has been passed through as the custom data, and so the correct action can be taken:\n\n```kotlin\nevent\u003cChatClickEvent\u003e {\n    if (data.size != 2) return@event\n    if (data[0] != \"openGUI\") return@event\n    when (data[1]) {\n        \"warps\" -\u003e GUIManager.openWarps(player)\n        // {...}\n    }\n}\n\n```\n\n## Scheduler\n\nBukkit's built-in scheduler is tedious at best, so Twilight takes advantage of beautiful Kotlin syntax to make it easier to write, as well as adding a\ncustom TimeUnit to save you calculating ticks.\n\nHow to schedule a single task to run on Bukkit's main thread either sync or async:\n\n```kotlin\nsync {\n    println(\"I am a sync BukkitRunnable\")\n}\n\nasync {\n    println(\"I am an async BukkitRunnable\")\n}\n```\n\nHow to schedule a delayed task, with optional custom time unit and async parameters:\n\n```kotlin\n// SYNC\ndelay {\n    println(\"I am a sync BukkitRunnable delayed by 1 tick\")\n}\n\ndelay(10) {\n    println(\"I am a sync BukkitRunnable delayed by 10 ticks\")\n}\n\ndelay(1, TimeUnit.SECONDS) {\n    println(\"I am a sync BukkitRunnable delayed by 1 second\")\n}\n\n// ASYNC\ndelay(10, true) {\n    println(\"I am an async BukkitRunnable delayed by 10 ticks\")\n}\n\ndelay(1, TimeUnit.SECONDS, true) {\n    println(\"I am an async BukkitRunnable delayed by 1 second\")\n}\n```\n\nHow to schedule a repeating task, with optional custom time unit and async parameters:\n\n```kotlin\n// SYNC\nrepeat(10) {\n    println(\"I am a sync BukkitRunnable running every 10 ticks\")\n}\n\nrepeat(10, TimeUnit.SECONDS) {\n    println(\"I am a sync BukkitRunnable running every 10 seconds\")\n}\n\nrepeat(5, 10) {\n    println(\"I am a sync BukkitRunnable running every 10 ticks, waiting 5 before starting\")\n}\n\nrepeat(5, 10, TimeUnit.SECONDS) {\n    println(\"I am a sync BukkitRunnable running every 10 seconds, waiting 5 before starting\")\n}\n\n// ASYNC\nrepeat(10, true) {\n    println(\"I am an async BukkitRunnable running every 10 ticks\")\n}\n\nrepeat(10, TimeUnit.SECONDS, true) {\n    println(\"I am an async BukkitRunnable running every 10 seconds\")\n}\n\nrepeat(5, 10, true) {\n    println(\"I am an async BukkitRunnable running every 10 ticks, waiting 5 before starting\")\n}\n\nrepeat(5, 10, TimeUnit.SECONDS, true) {\n    println(\"I am an async BukkitRunnable running every 10 seconds, waiting 5 before starting\")\n}\n```\n\n\u003e Is Twilight's `repeat` conflicting with Kotlin's `repeat`? As an alternative, you can use `repeatingTask`.\n\nYou can chain tasks together using `onComplete` to nicely nest sync/async executions. Here's an example:\n\n```kotlin\nasync {\n    println(\"I am an async BukkitRunnable called Atom\")\n}.onComplete() {\n    println(\"I am an async BukkitRunnable called Brandon running immediately after Atom finishes executing\")\n}.onCompleteSync(10) {\n    println(\"I am a sync BukkitRunnable called Charlie running 10 ticks after Brandon finishes executing\")\n}.onComplete(5, TimeUnit.SECONDS) {\n    println(\"I am a sync BukkitRunnable called Dawson running 5 seconds after Charlie finishes executing\")\n}.onCompleteAsync {\n    println(\"I am an async BukkitRunnable called Enid running immediately after Dawson finishes executing\")\n}\n```\n\nAs you can see, you can specify whether sync/async (if unspecified, it will not change) and you can pass in an optional delay. This also works with a\ndelay from the get-go:\n\n```kotlin\ndelay(20, TimeUnit.SECONDS) {\n    println(\"I am a sync BukkitRunnable delayed by 20 seconds\")\n}.onCompleteAsync(10, TimeUnit.SECONDS) {\n    println(\"I am an async BukkitRunnable delayed by a further 10 seconds\")\n}\n```\n\n\u003e Currently, onComplete is incompatible with repeating tasks.\n\u003e\n\n## Scoreboard\n\nTwilight provides an easy-to-use Scoreboard system that allows you to create and manage scoreboards with ease, including\nSidebars, Prefixes/Suffixes, and Below Name displays.\n\nThe implementation also includes methods to manage the PlayerList (also known as TabList).\n\n### Sidebars\n\nYou can display static content/components with just a few lines of code.\n\n```kotlin\n// Keep track of all scoreboards with a Map\nprivate val scoreboards = mutableMapOf\u003cPlayer, TwilightScoreboard\u003e()\n\nval join = event\u003cPlayerJoinEvent\u003e {\n    // Create a new instance of TwilightScoreboard when a player joins the server\n    val scoreboard = TwilightScoreboard(player)\n\n    scoreboard.apply {\n        // Set the title of the Sidebar\n        updateSidebarTitle(Component.text(\"TWILIGHT\", NamedTextColor.BLUE, TextDecoration.BOLD))\n\n        // Update all the sidebar lines\n        updateSidebarLines(\n            Component.text(\"\"),\n            Component.text()\n                .append(Component.text(\"Name: \", NamedTextColor.WHITE))\n                .append(Component.text(player.name, NamedTextColor.YELLOW))\n                .build(),\n            Component.text()\n                .append(Component.text(\"Level: \", NamedTextColor.WHITE))\n                .append(Component.text(player.level.toString(), NamedTextColor.YELLOW))\n                .build(),\n            Component.text(\"\"),\n            Component.text()\n                .append(Component.text(\"Deaths: \", NamedTextColor.WHITE))\n                .append(Component.text(player.getStatistic(Statistic.DEATHS).toString(), NamedTextColor.YELLOW))\n                .build(),\n            Component.text()\n                .append(Component.text(\"Kills: \", NamedTextColor.WHITE))\n                .append(Component.text(player.getStatistic(Statistic.PLAYER_KILLS).toString(), NamedTextColor.YELLOW))\n                .build(),\n            Component.text(\"\")\n        )\n    }\n    scoreboards[player] = scoreboard\n}\n```\n\nTo display dynamic values (data that changes throughout runtime), the method `updateSidebarLines` will need to be called every time the data changes.\nTo make this easier, you can use a `BukkitRunnable` to update the scoreboard at your preferred interval.\n\n### Below Name Display\n\nYou can utilise the Below Name scoreboard functionality to show anything you'd like (health, stats, or other player-specific information) under their\nname.\nLet's create a Dynamic Below Name that shows the player health.\n\n```kotlin\n// Keep track of all scoreboards with a Map\nprivate val scoreboards = mutableMapOf\u003cPlayer, TwilightScoreboard\u003e()\n\nval join = event\u003cPlayerJoinEvent\u003e {\n    // Same as the sidebar, create an instance when a player joins\n    val scoreboard = TwilightScoreboard(player)\n\n    scoreboard.apply {\n        // Set up below name display\n        belowName(Component.text(\"Health\", NamedTextColor.WHITE))\n        updateBelowNameScore(player, player.health.toInt())\n    }\n\n    // Update the scoreboard for all players so everyone sees the same health\n    plugin.server.onlinePlayers.forEach { onlinePlayer -\u003e\n        scoreboard.updateBelowNameScore(onlinePlayer, onlinePlayer.health.toInt())\n        scoreboards[onlinePlayer]?.updateBelowNameScore(player, player.health.toInt())\n    }\n    scoreboards[player] = scoreboard\n}\n\n// Update health on damage\nval damage = event\u003cEntityDamageEvent\u003e {\n    if (entity !is Player) return@event\n    val player = entity as Player\n    // Update for all players\n    plugin.server.onlinePlayers.forEach { onlinePlayer -\u003e\n        scoreboards[onlinePlayer]?.updateBelowNameScore(player, player.health.toInt())\n    }\n}\n\n// Update health when it increases\nval regen = event\u003cEntityRegainHealthEvent\u003e {\n    if (entity !is Player) return@event\n    val player = entity as Player\n    // Update for all players\n    plugin.server.onlinePlayers.forEach { onlinePlayer -\u003e\n        scoreboards[onlinePlayer]?.updateBelowNameScore(player, player.health.toInt())\n    }\n}\n```\n\n### Tab List (Player List Header/Footer)\n\nTwilight also provides a way to customize the TabList of your server. Like the sidebar, this can be made dynamic by simply having a BukkitRunnable\nthat updates the TabList lines every 20 ticks (1 second).\n\n```kotlin\nscoreboard.updateTabList(\n    header = {\n        Component.text()\n            .append(Component.text(\"Welcome to the Server!\", NamedTextColor.BLUE, TextDecoration.BOLD))\n            .appendNewline()\n            .append(Component.text(player.name, NamedTextColor.AQUA))\n            .appendNewline()\n            .append(Component.text(\"Enjoy your stay!\", NamedTextColor.WHITE))\n            .build()\n    },\n    footer = {\n        Component.text()\n            .append(Component.text(\"Players online: ${plugin.server.onlinePlayers.size}\", NamedTextColor.GRAY))\n            .appendNewline()\n            .append(Component.text(\"play.server.com\", NamedTextColor.YELLOW))\n            .build()\n    }\n)\n```\n\n### Player Prefixes and Suffixes\n\nAdd custom prefixes or suffixes to players!\n\n*Note: You would need like the Below Name to update the prefix/suffix for everyone when you change it.*\n\n```kotlin\n// Apply a prefix to a player\nscoreboard.prefix(player, Component.text(\"[ADMIN]\", NamedTextColor.RED, TextDecoration.BOLD))\n\n// Apply a suffix to a player\nscoreboard.suffix(player, Component.text(\"[AFK]\", NamedTextColor.GRAY))\n```\n\nIt's suggested to remove the player from the Map when they quit, to prevent memory leaks.\n\n```kotlin\nval quit = event\u003cPlayerQuitEvent\u003e {\n    scoreboards[player]?.delete()\n    scoreboards.remove(player)\n}\n```\n\n## GUI Builder\n\nCreating GUI's can be an incredibly long and tedious process, however, Twilight offers a clean and efficient way.\n\nHere's an example of a simple, standard GUI:\n\n```kotlin\nval basicGui = gui(Component.text(\"Click the apple!\"), 9) {\n    set(4, ItemStack(Material.APPLE)) {\n        isCancelled = true\n        viewer.sendMessage(Component.text(\"This is an apple!\"))\n    }\n}\nplayer.openInventory(basicGui)\n```\n\nAs you can see, Setting the click event logic has never been easier. You can reference the player using `viewer`.\n\nHere's an example of a more complex GUI implementing the pattern feature, making it much easier to visualise:\n\n```kotlin\nval complexGui = gui {\n    pattern(\n        \"#########\",\n        \"#   S   #\",\n        \"#########\"\n    )\n\n    set('S', ItemStack(Material.PLAYER_HEAD).apply {\n        val meta = itemMeta as SkullMeta\n        meta.displayName(Component.text(\"${viewer.name}'s Head!\"))\n        meta.owningPlayer = viewer\n        itemMeta = meta\n    }) {\n        isCancelled = true\n        viewer.sendMessage(Component.text(\"Ouch!\", NamedTextColor.RED))\n    }\n\n    set('#', ItemStack(Material.LIGHT_GRAY_STAINED_GLASS_PANE).apply {\n        val meta = itemMeta\n        meta.displayName(Component.empty())\n        itemMeta = meta\n    }) { isCancelled = true }\n}\nplayer.openInventory(complexGui)\n```\n\nYou can also implement GUIs for other inventory types:\n\n```kotlin\nval dropperGui = gui(Component.text(\"Title\"), 9, InventoryType.DROPPER) {\n    // You can set multiple indexes at once\n    set(listOf(1, 3, 4, 5, 7), ItemStack(Material.GRAY_STAINED_GLASS_PANE))\n\n    // You can set default actions that run on every click (before your item-specific code)\n    onClick { isCancelled = true }\n}\nplayer.openInventory(dropperGui)\n```\n\nTo open the gui, simply run `Player#openInventory(GUI)` like shown in the examples.\n\n## Databases\n\n### MongoDB\n\nTwilight has support for MongoDB. To configure it, you can take one of two routes:\n\n#### Environment variables\n\nYou can use the following Environment variables for your MongoDB:\n\n```env\nMONGO_URI=\"your URI string\"\nMONGO_DATABASE=\"your database name\"\n```\n\n#### Builder\n\nWhen building your Twilight instance, you can specify your URI and database like so:\n\n```kotlin\nval twilight = twilight(plugin) {\n    mongo {\n        uri = \"your URI string\"\n        database = \"your database name\"\n    }\n}\n```\n\nFrom here you can use the following function to get a collection from your database:\n\n```kotlin\nMongoDB.collection(\"my-collection\")\n```\n\nAnd use the standard features of the Mongo Sync Driver with your `MongoCollection`.\n\n**OR** you can use some of our custom features, making communicating with a Mongo database infinitely easier. Here's how you do it:\n\n```kt\nclass Profile(\n    @field:Id val id: UUID,\n    val name: String\n) : MongoSerializable\n```\n\nIn the above example, we're declaring what should be used as the key identifier for our class in the database. We do so by annotating a field with\n`@field:Id`.\n\nWe also implement an interface `MongoSerializable`. This gives us access to a bunch of methods which make our lives really easy when it comes to\nmoving between our class instance and our database.\n\nFor example, I could do the following:\n\n```kt\nval profile = Profile(UUID.randomUUID(), \"Name\")\n\nprofile.save()\n// this returns a CompletableFuture of the UpdateResult\n// or we could do profile.saveSync() if we need it to be sync, does not return wrapped by a CompletableFuture\n\nprofile.delete()\n// this returns a CompletableFuture of the DeleteResult\n// same as save, can do profile.deleteSync() for sync, does not return wrapped by a CompletableFuture\n```\n\nIf we ever want to find and load and instance of our class from the database, we can use some functions from the TwilightMongoCollection:\n\n```kt\nval collection =\n    MongoDB.collection\u003cProfile\u003e() // by default this assumes the name of the collection is the plural camel case of the type, f.x. Profile -\u003e profiles, SomeExampleThing -\u003e someExampleThings\n// you can specify the name of the collection if you wish it to be different like so\nval collection = MongoDB.collection\u003cProfile\u003e(\"myCollection\")\ncollection.find() // returns a CompletableFuture\u003cMongoIterable\u003cProfile\u003e\u003e\ncollection.find(BsonFilter) // returns a CompletableFuture\u003cMongoIterable\u003cProfile\u003e\u003e\ncollection.findById(id) // id must be the same type as the field marked as the id on the class, returns a CompletableFuture\u003cMongoIterable\u003cProfile\u003e\u003e\ncollection.delete(BsonFilter) // returns a CompletableFuture\u003cDeleteResult\u003e\ncollection.deleteById(id) // id must be the same type as the field marked as the id on the class, returns a CompletableFuture\u003cDeleteResult\u003e\n// all of these have sync versions which follow the same pattern, f.x. collection.findSync(), where the return value is the same as the async version, just not wrapped by a CompletableFuture\n```\n\nIf we need something that isn't already wrapped by the TwilightMongoCollection, it exposes us the MongoCollection of Documents, which we can get with\n`collection.documents`.\n\n### SQL (MySQL, Postgres)\n\n#### Getting Started\n\nTo get started you need to create an instance of the SQL Wrapper like so\n\n```kotlin\nval db = SQLWrapper(url = \"your db url\", user = \"user\", password = \"password\")\ndb.connect()\n```\n\n#### QueryBuilder\n\nThe QueryBuilder class will help you in creating everything from simple queries like SELECTs to even complex JOINs.\n\nAll you need to start is an instance of `QueryBuilder`. Here's an example usage:\n\n```kotlin\nval queryBuilder = QueryBuilder()\n\n// Example SELECT query\nval selectQuery = queryBuilder.select(\"id\", \"name\").from(\"person\").where(\"age\" gt 18).build()\n\n// Example INSERT query\nval insertQuery = queryBuilder.insertInto(\"person\", \"id\", \"name\").values(1, \"John\").build()\n\n// Example UPDATE query\nval updateQuery = queryBuilder.update().table(\"person\").set(\"name\", \"John Doe\").where(\"id\" eq 1).build()\n\n// Example DELETE query\nval deleteQuery = queryBuilder.delete().table(\"person\").where(\"id = 1\").build()\n```\n\n#### Using objects in your database\n\nIf you would like to retrieve and store data as objects within your database there a some methods provided for this\n\n1 - Your object must implement SQLSerializable\n\n2 - You must have a table that fits the structure of your object, you can create by calling `convertToSQLTable()` on your object, then execute the\nstatement like so:\n\n```kotlin\n// NOTE: convertToSQLTable() takes an optional dialect parameter, at this time the only additional dialect is postgres\nval createTable = yourObjectInstace.convertToSQLTable()\n\nif (db.execute(createTable)) {\n    // successfully executed\n}\n```\n\n3 - To insert your object call `toInsertQuery()` like so:\n\n```kotlin\nval insertToTable = yourObjectInstace.toInsertQuery()\n\nif (db.execute(insertToTable)) {\n    // successfully executed\n}\n```\n\n4 - To retrieve objects from your database you call a select statement like normal but call `toListOfObjects\u003cType\u003e()` on the returned `Results` class.\n\n#### Running queries\n\nOnce you have your query using either the QueryBuilder or your own you can run it like so:\n\n```kotlin\nval result = result.executeQuery(selectQuery)\n```\n\nOnce you have run the query it will return a `Results` class, it can be used like so:\n\n```kotlin\nresult?.let { res -\u003e\n    println(\"MyColumn Value: \" + res[\"my_column\"])\n}\n```\n\nThe results class contains a list of all the rows and columns returned by the database.\nYou can access any of the columns values the same way you would with a map.\n\n## Ternary Operator\n\nThere is a basic ternary operator implementation added which can be used like so:\n\n```kotlin\nval test = false\nprintln(test then \"yes\" or \"no\")\n```\n\n\u003e This doesn't yet work for evaluating functions either side of the ternary.\n\n## UUID ⟷ Name\n\nTwilight can do the heavy lifting and query the Mojang API to find the UUID from name or name from UUID of a player, particularly useful for networks.\nTwilight will cache responses in an attempt to not break the rate limit imposed by Mojang.\n\nIf you have a UUID and you want to get a name, you can call `nameFromUUID`:\n\n```kotlin\nNameCacheService.nameFromUUID(UUID.fromString(\"a008c892-e7e1-48e1-8235-8aa389318b7a\"))\n```\n\nThis will look up your cache to see if we already know the name, otherwise we will check the MongoDB cache of key, value pairs, and finally, we'll\nquery Mojang if we still don't know it.\n\nAfter each step the key, value pair will be stored so the next call is just on the cache.\n\nSimilarly, if you have a name and want to get the UUID, you can call `uuidFromName`:\n\n```kotlin\nNameCacheService.uuidFromName(\"stxphen\")\n```\n\nCurrently, the only way to configure your MongoDB \"cache\" for UUIDs and names, is to have an Environment variable called `NAME_CACHE_COLLECTION` with\nthe value being what you want to call the collection.\n\nDon't want to use the Mongo cache? Disable `useMongoCache` in the settings.\n\n## Redis\n\nTwilight has a Redis system that lets you set/get/delete string key value pairs, additionally, you can publish messages and listen to incoming\nmessages on any channel you'd like.\n\n### Environment variables\n\nYou can use the following Environment variables for your Redis Server:\n\n```env\nREDIS_HOST=\"your redis server host\"\nREDIS_PORT=\"your redis server port\"\nREDIS_TIMEOUT=\"your redis connection timeout\"\nREDIS_AUTHENTICATION=\"NONE\"\n```\n\nAlternatively, if your Redis server requires a Username + Password in order to access, you can use the following:\n\n```env\nREDIS_HOST=\"your redis server host\"\nREDIS_PORT=\"your redis server port\"\nREDIS_TIMEOUT=\"your redis connection timeout\"\nREDIS_AUTHENTICATION=\"USERNAME_PASSWORD\"\nREDIS_USERNAME:\"coolUsername\"\nREDIS_PASSWORD:\"coolPassword\"\n```\n\nAlternatively, if your Redis server requires a URL in order to access, you can use the following:\n\n```env\nREDIS_HOST=\"your redis server host\"\nREDIS_PORT=\"your redis server port\"\nREDIS_TIMEOUT=\"your redis connection timeout\"\nREDIS_AUTHENTICATION=\"URL\"\nREDIS_URL=\"coolURL\"\n```\n\n#### Builder\n\nWhen building your Twilight instance, you can specify your host and port like so:\n\n```kotlin\nval twilight = twilight(plugin) {\n    redis {\n        authentication = Authentication.NONE\n        host = \"your redis server host\"\n        port = 6379 // Default Redis Port\n        timeout = 500 // 500 Milliseconds Timeout\n    }\n}\n```\n\nAlternatively, if your Redis server requires a Username + Password in order to access, you can use the following:\n\n```kotlin\nval twilight = twilight(plugin) {\n    redis {\n        authentication = Authentication.USERNAME_PASSWORD\n        host = \"your redis server host\"\n        port = 6379 // Default Redis Port\n        timeout = 500 // 500 Milliseconds Timeout\n        username = \"coolUsername\"\n        password = \"coolPassword\"\n    }\n}\n```\n\nAlternatively, if your Redis server requires a URL in order to access, you can use the following:\n\n```kotlin\nval twilight = twilight(plugin) {\n    redis {\n        authentication = Authentication.URL\n        host = \"your redis server host\"\n        port = 6379 // Default Redis Port\n        timeout = 500 // 500 Milliseconds Timeout\n        url = \"coolURL\"\n    }\n}\n```\n\n#### String Key-Value Pairs\n\nYou can Set/Get/Delete String Key-Value pairs on your Redis server like so (all of these functions are Async and return a CompletableFuture):\n\n```kotlin\nRedis.set(\"cool-key\", \"super-secret-value\")\n\nval future = Redis.get(\"cool-key\") // Returns a Completable Future\n\nfuture.thenApplyAsync { value -\u003e\n    println(\"The value is: $value\") // Prints: \"The value is: super-secret-value\"\n}.exceptionally { e -\u003e\n    println(\"An exception occurred: ${e.message}\") // Handle the Exception\n}\n\nThread.sleep(1000)\n\nRedis.delete(\"cool-key\")\n```\n\n#### Publishing Messages\n\nYou can publish messages like so:\n\n```kotlin\nRedis.publish(\"channel\", \"message\") // Async Publishing\n```\n\n#### Redis Listeners (PubSub)\n\n##### Listen to incoming messages\n\nYou are able to listen to incoming message on specific channels, using the 'TwilightRedisListener' Class:\n\n```kotlin\n// Extend the 'TwilightRedisListener' class and override the 'onMessage' function.\nclass PlayerConnectionRedisListener() : TwilightRedisListener(\"player-connection\") {\n    override fun onMessage(message: String) {\n        // do stuff\n    }\n}\n```\n\nYou can add/register the listener like this (which also returns the listener which lets you unregister it if you'd like):\n\n```kotlin\nval listener = Redis.addListener(PlayerConnectionRedisListener())\nlistener.unregister() // unregistering the listener.\n```\n\nAlternatively, instead of extending the listener class, you can add a listener using a block of code, which returns the 'RedisMessage' data class,\nwhich contains the channel, the message, and the listener:\n\n```kotlin\nval listener = Redis.addListener(\"cool-channel\") {\n    println(\"The following message was received: '$message' on channel '$channel'\")\n    this.listener.unregister() // unregistering the listener after we received the message.\n}\n```\n\n## Files Extensions\n\n### File.hash()\n\nYou can easily get the hash of a file using this method. The parameter `algorithm` is used to define which should be used for the hash, the default is\nSHA-256.\n\nIf you need to hold a reference to a file at a remote location you can use the `RemoteFile` class provided here. This allows you to easily get the\nhash of a remote file with `RemoteFile#hash`.\n\nThis is particularly useful when used in parallel with our other open-source\nlibrary, [resource-pack-deploy](https://github.com/flytegg/resource-pack-deploy), so you can get the hash of the latest resource pack file, and use it\nto reset a client's cached version of your resource pack if there are any differences.\n\n## Symbols\n\nTwilight offers a collection of widely used symbols within the `Symbols` object.\n\nThese include, but are not limited to:\n\n- • (BULLET)\n- ❤ (HEART)\n- ★ (STAR)\n- » (SMALL_RIGHT)\n- █ (SQUARE)\n- ⬤ (CIRCLE)\n- ⛏ (PICKAXE)\n- ⛀ (COIN)\n- ⛁ (COINS)\n\n## Libraries\n\nTwilight is bundled with some useful libraries, some include:\n\n- Bson\n- Dotenv\n- Mongo Sync Driver\n- GSON\n\n### GSON\n\nWe provide some standard GSON Type Adapters for ease of use. Currently, we have adapters for the following:\n\n- Location\n\nWe have a useful exclusion strategy, allowing you to exclude marked fields of a class from being in serialized JSON. All you need to do is annotate\nthe class field with `@Exclude`!\n\nMake sure to use our `GSON` import rather than making your own/own builder, as the Gson instance has to declare the ExclusionStrategy.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflytegg%2Ftwilight","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflytegg%2Ftwilight","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflytegg%2Ftwilight/lists"}