diff --git a/sisbasekt.md b/sisbasekt.md index dc2210d..ad7a615 100644 --- a/sisbasekt.md +++ b/sisbasekt.md @@ -2,13 +2,11 @@ `Type: Metaproject` Kotlin discord bot framework using the lessons learned by developing sisbase.net -## Uses -[`Kord`](https://github.com/kordlib/kord) - **Discord Library Wrapper** -[`pf4j`](https://github.com/pf4j/pf4j) - **Plugin Framework** - ## Subprojects [`sisbase-core`](sisbasekt/core.md) - Fabric-like extension manager. -[`sisbase-api`](sisbasekt/api.md) - Api for writing sisbase extensions. +[`sisbase-api`](sisbasekt/api.md) - A collection of interfaces representing the Discord API Spec. +[`sisbase-commands`](sisbasekt/commands.md) - Command Library. +[`sisbase-types`](sisbasekt/types.md) - Shared discord types. ## Rationale Learning [`kotlin-coroutines`](https://github.com/Kotlin/kotlinx.coroutines). @@ -17,3 +15,37 @@ Simplifying discord bot creation by centralizing all the boilerplate in a single Future proofing code by having an easy-to-update path for extensions since all low-level bookkeeping is delegated to the manager. +## Goals + +- Extensibility + +The library should be as extensible as possible as to allow advanced extension authors to have all tools necessary to implement their already existing code into an extension. + +- Easy upgrade path + +If discord changes their API, upgrading a sisbasekt bot would be as easy as updating the `sisbase-core.jar` loader. +If an extension author updates their extension, upgrading it would be as easy as replacing the `extensions/extension.jar` file. +If someone decides to port their "fat bot"1 into a sisbase extension the path should be as clear as possible. + +1- Bots which embed the discord library with them. + +- Modularity + +Extension writers should only import what they need. +E.g. If they already have their own solutions for command handling there would be no need to use `sisbase-commands`. + +- Backend Agnostic + +In order to explain why this is a goal, a bit of history is required. +Between 2019 and 2021 I had to swap between discord libraries constanly in order to keep my bots up-to-date with the latest API revision (at least 4 times [Discord4J -> discord.py -> DSharpPlus -> Discord.Net]), it was a complete pain and involved rewriting all of my bots' code (since in most cases the code is tied to the library). +Some libraries had a very slow update cycle and had a "bleeding edge" fork where "experiments" (like keeping the Discord API version up-to-date, for some wierd reason) were done and you were forced to add a separate source and keep track of that, leading to "Update Framework Version" commits being way too frequent. +Some libraries were propperly maintained but their design goals were incompatible with my use case and lacked the flexibility necessary to create personalized solutions to even prefix handling (which if you ever made anything more complex then a single-guild bot is a necessity). +Some other libraries just were left in the dust with no one to take care of them. +While some libraries never left the beta phase. + +**Developing bots shouldn't require you to pray that your flavour of library is updated.** +If the library you're using dies, just *swap to a different one*, **no code changes required**. +Make the choice of the backing a mere ~runtime detail~. Your code shall run the same on Discord4j and Kord. + +*Note: +Thanks to @wffirilat for bringing this to my attention, wouldn't have thougth about this on my own!* diff --git a/sisbasekt/api.md b/sisbasekt/api.md index 2112901..5b4c893 100644 --- a/sisbasekt/api.md +++ b/sisbasekt/api.md @@ -1,3 +1,38 @@ # sisbase-api `Type: Subproject` Subproject of: [`SisbaseKT`](../sisbasekt.md) + +A collection of interfaces representing the Discord API Spec. +Implemented by `Backends`. + +## Dependencies +[types](types.md) + +## Project Structure +`org.sisbase.sisbase-api` +Base package + +`org.sisbase.sisbase-api.rest` +High level wrapper for Discord's Rest API + +`org.sisbase.sisbase-api.gateway` +High level wrapper for Discord's Gateway + +## Usage + +### Implementing a Backend +Backends are the actual libraries that connect to discord. +An example of a valid backend would be Discord4J. + +To implement a gateway backend, extend `GatewayBackend` +To implement a rest backend, extend `RestBackend` +To implement a backend that supports both, extend `AbstractBackend` + +```kt +package org.siscode.sisbasekt-backends.discord4j +class Discord4JBackend() : AbstractBackend { + public restBackend = Discord4JRest(); + public gatewaybackend = Discord4JGateway() +} +``` + diff --git a/sisbasekt/commands.md b/sisbasekt/commands.md new file mode 100644 index 0000000..3fc4723 --- /dev/null +++ b/sisbasekt/commands.md @@ -0,0 +1,132 @@ +# sisbase-commands +`Type: Subproject` +Subproject of: [`SisbaseKT`](../sisbasekt.md) + +Command library. + +Implemented as an extension. Users can use `sisbase-commands` optionally for command handling, but can also use their own command handling solutions if they so desire. + +## Dependencies +[core](core.md) + +## Commands +Simple interactions with the user caused by a direct call to them. + +Has the following attributes: + +| Field | Type | Description | +|---------------|----------|---------------------------------------------------------------------------------| +| `name` | String | Name of the command | +| `description` | String? | A short description of the command | +| `user` | User | The user that called the command | +| `channel` | Channel | The channel on which the command was called | +| `guild` | Guild? | The guild on which the command was called | + +### Text-based +The third-party command system, based on detecting commands from messages sent on a given discord channel. +Has an extensive permission system since the code is fully controlled by the library. +Requires a system that checks every message for a valid command. + +Extends the base attributes with: + +| Field | Type | Description | +|----------|----------------|-------------------------------------------------------------------------------------------------------------| +| `checks` | [Precondition] | An array with all checks done to the text command by the permission engine, or an empty array if none exist | +| `group` | Group? | The group the command is a part of | +| `alias` | [String] | An array containing all aliases to the command, or an empty array if none exist | + +### Slash +Discord's native command system, supercedes text-based commands but has a limit of how many commands can be registered and require modifying the application. +Does not require a system since discord dispatches the command to the bot via the gateway. + +**Currently, support for slash commands isn't planned, this could change in the future** + + +## Structure of the `sisbase-commands` extension + +### Persistent Data +The extension must hold a `Registry` for commands during the bot lifetime. + +The `Registry` must safeguard against command conflicts, as **only one command** (including overloads) can be registered for a given `Identifier`. + +`Identifier`s are a way to uniquely identify a given command and they include the following fields: + +| Field | Type | Description | Observations | +|----------------------|--------|----------------------------------------------------|----------------------------------------------------------| +| `source` | String | The id of the extension that registers the command | Automatically set based on the extension's metadata file | +| `fullyQualifiedName` | String | The full command path for a given command | Automatically set once the command is registered | + +An example of a `Identifier` is given below: + +Command is called as `/help` +```yml +source: "your-bot" +command: "help" +identifier: "your-bot::help" +``` + +Command is called as `/group subcommand` +```yml +source: "your-bot" +command: "group/subcommand" +identifier: "your-bot::group/subcommand" +``` + +Identifiers don't care about command overloads, this is handled internally by the library. +Mismatches for commands with equal names registered by the same extension will cause the extension to be rejected. (DUPLICATE COMMAND) +Mismatches for commands with equal names registered by different extensions are to be resolved manually on the `overrides.yml` file. (COMMAND MISMATCH) + +Example of a mismatch: + +`ext-a` registers a `help` command. -> `ext-a::help` +`ext-b` also registers a `help` command. -> `ext-b::help` + +Console Output: +``` +[Sisbase-Commands] Command Mismatch Detected: + +The following commands will become unavailable until their mismatches are resolved. + + for `help`: + + `ext-a@version` registered `help` + `ext-b@version` registered `help` + +Please resolve all command mismatches on `overrides.yaml.` + +[Sisbase-Commands] Disabled `ext-a::help` due to: COMMAND MISMATCH +[Sisbase-Commands] Disabled `ext-b::help` due to: COMMAND MISMATCH +``` + +Example of a duplicate command: + +`ext-a` registers a `help` command. -> `ext-a::help` +`ext-a` later registers a `help` command. -> `ext-a::help` + +Console Output: +``` +[Sisbase-Commands] Duplicate Command Detected: + +The following extensions will become unavailable until their duplicate commands are removed. + + for `ext-a`: + + Duplicate `help` command registered. + +Please contact the extension authors to remove the duplicate commands. + +[Sisbase-Commands] Disabled `ext-a` due to: DUPLICATE COMMAND + +``` + +On the case of a critical failure that leads to the extension being shutdown (or on the case of it being disabled) +all commands and hooks **must be unregistered** immediately. + +### Required hooks + +For command parsing: + +| Hook | Usage | +|--------------------------|---------------------------------------------------------------------------------------------------------------| +| `onMessageReceived` | Used for parsing the message contents, building the CommandContext, and forwarding the data to the call site. | +| `onGuildMessageReceived` | Same as `onMessageReceived` | diff --git a/sisbasekt/core.md b/sisbasekt/core.md index 432f9a1..f92cae2 100644 --- a/sisbasekt/core.md +++ b/sisbasekt/core.md @@ -2,3 +2,100 @@ `Type: Subproject` Subproject of : [`SisbaseKT`](../sisbasekt.md) +Discord extension manager + +## Dependencies +[PF4J](https://github.com/pf4j/pf4j) - Plugin Framework +[koin](https://github.com/InsertKoinIO/koin) - Dependency Injection +[api](api.md) + +## Features + +- [Systems](core/systems.md) `META!` + User-provided lifelong processes. Uses range from repeating jobs bound to a timer or connectors between the bot and an external data source. + Systems can be manually unregistered during runtime via the `core.Manager::unregister` API. + +- System Handling `Important!` + Handles the lifetime of systems. + The system lifetime is as follows: + - `PreInit` + - `Init` - **Required** + - `PostInit` + - `Disable` - **Required** + +- Discord Connection `Important!` +*Note: Actual discord connection will be delegated to a `Backend`* +- Secret Storage +- Configuration API +- Prefix Handling `Important!` +*Note: Prefix handling must be open to the user as easily allow configuring an external prefix handler.* + +## Sisbase-Core Lifetime Description + +### Initialization + +1. Loads configuration +2. Start the Backend +3. Wait for the Backend to connect to Discord +4. Loads Extensions + +### Lost Connection to Backend + +5. Stops all Extensions +6. Try restarting the backend 3 times +7. Shuts down the backend +8. Shuts down + +### Connection to Backend is Resumed + +5. Loads Extensions + +## Configuration / Metadata + +See the [sisbasekt.mod.json Documentation](./mod_json.md) for information about the Metadata File Format. + +-! IDEA: Due to an change in [`types`](types.md), deriving the `@RequireApiVersion` automatically could be done. +-: [Not Required] [FUTURE] + +## Developing extensions + +### Metadata +In order for an extension to be loaded, the `sisbasekt.mod.json` file must be present at the root of the JAR file. +For more information on the Metadata File Format, [see above](core.md#configuration--metadata) + +### Discord API Version Compatibility + +In order to make sure your extension is propperly supported, extension writers can annotate their systems with `@RequireApiVersion`. + +On the case of a backend not supporting said version that system will be disabled and a warning will be printed to the console. + +``` +[Sisbase-Loader] Dependency Error: + Extension `id@version` requires + Discord API Version >= X + Backend `id@version` provides + Discord API Version == Z + +The following features will be disabled: +- Feature Name [Description] +- Feature Name [Description] + +Please update or change the backend to get these features back. +``` + +Extension writers can also add `require-api-version` to the `mod.json` metadata file. + +On the case of a backend not supporting said version the extension won't be loaded and an error will be printed to the console. + +``` +[Sisbase-Loader] Critical Dependency Failure: + Extension `id@version` requires + Discord API Version >= X + Backend `id@version` provides + Discord API Version == Z + +Extension `id@version` is now disabled. + +Please update or change the backend to get `id` back. +``` + diff --git a/sisbasekt/core/systems.md b/sisbasekt/core/systems.md new file mode 100644 index 0000000..e8f96a7 --- /dev/null +++ b/sisbasekt/core/systems.md @@ -0,0 +1,43 @@ +# Systems +`Type: Feature` +Part of [`core`](../core.md) + +Long-term, usually lifelong background procedures. + +## Dependency Management +Systems can depend on other systems using the `@DependsOn` annotation. +Systems can add "soft dependencies" using the `@Extends` annotation. + +## Data Description +Has the following attributes: + +| Field | Type | Description | +|----------------------|---------------------|-------------------------------------------------------------------------------------------------------------------| +| `name` | String | Name of the system | +| `description` | String? | A short description of the system | +| `expansions` | [Expansion] | An array of system expansions, or an empty array if none exist | +| `OnInit` | suspend fun | A function that runs on the start of the system's lifetime | +| `OnDisable` | suspend fun | A function that runs on the end of the system's lifetime | +| `CheckPreconditions` | suspend fun -> Bool | A function that runs before `OnInit`, if it returns `true` the system is loaded, otherwise the system is skipped. | + + +### Repeating +Background procedures that repeat at a set interval given by the user. + +Implemented by a `Expansion` that has the following attributes: +| Field | Type | Description | +|-------------|-------------|-----------------------------------------------------------------| +| `timeout` | Duration | The period of time that will be used for repeating the function | +| `OnElapsed` | suspend fun | The function that will be repeated once `timeout` elapses | + +### Discord-linked +Background procedures that reads/writes data to Discord. + +Implemented by a `Expansion` that has the following attributes: +| Field | Type | Description | +|-----------------|----------------------|----------------------------------------------------------------------| +| `applyToClient` | suspend fun (Client) | A function that will be applied to the discord client after `OnInit` | + +### Single-instance +Simple background procedures that are set once the bot loads and are used in other parts of the bot. + diff --git a/sisbasekt/mod_json.md b/sisbasekt/mod_json.md new file mode 100644 index 0000000..8ce5238 --- /dev/null +++ b/sisbasekt/mod_json.md @@ -0,0 +1,62 @@ +# [tbd].mod.json +The [tbd].mod.json is a extension metadata file used by the [`sisbase-loader`](./core.md) to load extensions. +In order to be loaded, an extension **must** have this file with the exact name placed in the root directory of the mod JAR. + +## Mandatory Fields +| Name | Type | Description | +|-----------------|---------|-----------------------------------------------------------------------------------------------------------| +| `id` | String | Defines the extension's identifier - A String of Latin Letters, Digits, Underscores with Lenght from 1-63 | +| `version` | String | Defines the extension's version. - A String value, Optionally matching [`SemVer`](). | +| `schemaVersion` | Integer | Used Internally. Must be `1`. | + +## Optional Fields + +### Metadata +| Name | Type | Description | +|----------------|----------|----------------------------------------------------------------------------------| +| `name` | String | Defines the user-friendly extension name. If not present, assume it matches `id` | +| `description` | String | Defines the extension's description. If not present, assume empty string. | +| `contact` | Contact | Defines the contact information for the project. | +| `authors` | [Author] | A list of authors of the extension. | +| `contributors` | [Author] | Same as `authors` | + + +### Dependency resolution + +| Name | Type | Description | +|------------------------|------------|-----------------------------------------------------------------------------------------| +| `required-api-version` | Integer | Defines the minimum required Discord API Version necessary for the extension to load. | +| `depends` | Dependency | For dependencies **required** to run. Without them the extension won't load. | +| `recommends` | Dependency | For dependencies **not required** to run. Without them the loader will print a warning. | + +## Data Types + +### Dependency +Object whose key is the `id` of the dependency, and whose value is either a string or an array of strings declaring supported version ranges. + +Example: +```json +{ + "my_dependency":">=1.0.0" +} +``` + + +### Author +Single Name (String) or an object containing these fields: +| Name | Type | Description | +|-----------|---------|----------------------------------------------------| +| `name` | String | The real name, username, of the person (Mandatory) | +| `contact` | Contact | Person's contact information (Optional) | + + +### Contact +An object containing any of those fields. +All of the following fields are **optional** +| Name | Type | Description | +|------------|--------|---------------------------------------------------------------| +| `email` | String | Must be a valid email | +| `irc` | String | Must be a valid URL format | +| `homepage` | String | Must be a valid HTTP/HTTPS address | +| `issues` | String | Extension's issue tracker. Must be a valid HTTP/HTTPS address | +| `sources` | String | Must be a valid URL format | diff --git a/sisbasekt/types.md b/sisbasekt/types.md new file mode 100644 index 0000000..9fe590f --- /dev/null +++ b/sisbasekt/types.md @@ -0,0 +1,39 @@ +# sisbase-types + +`Type: Shared Dependency` +[Go Back to Project Outline](../sisbasekt.md) + + +Discord data types. + +The types from this project will be used by all other sisbasekt projects. + +The objective of this project is to serve as a bridge between different backends, so all objects here will be high level and directly reflect the official API documentation. + +## Package Structure +`org.siscode.sisbase-types` +Base Package. + +`org.siscode.sisbase-types.rest` +Discord Rest API Types. + +`org.siscode.sisbase-type.gateway` +Discord Gateway Types. + +`org.siscode.sisbase-type.annotation` +Annotations used by the library + +## Annotations + +### `@Since` +Describes which version of the API added that Type. + +| Field | Type | Description | +|-----------|-------------------|------------------------------------------| +| `version` | DiscordApiVersion | Version of the API which added that type | + +## Other Types + +### `DiscordApiVersion` +An Enum containing all Discord API Versions so that no invalid versions can be represented. +