Overview
Written in Rust, Merlin is a utility bot for Discord that responds to message commands. Easily configurable with a customisable module system.
Installation
This page assumes you have Rust and Cargo installed. Here's a guide on how to get them.
Install from Git
You can install the latest commit from Git with Cargo.
cargo install --git https://github.com/siriusmart/merlin
The installed binary can be found in ~/.cargo/bin/
.
You may use the CONFIG=/path/to/config/folder
environment variable to specify a config folder, ~
is not supported. By default config files are generated at
~/.config/merlin
(Linux)/Users/[user]/Library/Application Support/merlin
(MacOS)C:\Users\[user]\AppData\Roaming
(Windows).
Merlin does not auto update, for updates you will have to watch the repository.
Features
By default Merlin compiles with all features. If there are features you don't need or wish to be replaced, you can compile without them using the --no-default-features
flag.
Features can be added to a featureless install with the -F [feature]
feature flag, the installation command can include as many feature flags as needed.
Here's a list of available features:
Feature | Description |
---|---|
modcore | Core module, provide basic functionalities. |
modcoords | Coords DB module for anarchy servers, WIP. |
Basic usage
A list of commands can be accessed through the help
command.
Command structure
A command can be called by specifying its full name. For example
.core ping
Calls the ping
command in the core
module.
The help command
The help command is an internal command (does not belong to any module, cannot be disabled) to display information about a module or command.
Module
A module is a collection of command of similar function, for example the core
module provides commands for basic housekeeping. Whereas the coords
module contains everything to do with the coords DB feature.
$ .help core
[Module] core
Core service modules.
Commands
- ping
- uptime
- version
Aliases
- ping → core ping
- uptime → core uptime
- version → core version
Alias
An alias is a shortcut for another command, in the example above, the command .ping
will be aliased to .core ping
, so a command can be ran without mentioning its module.
Command
A command does something. Commands can be disabled globally, and per-user permission can be set up. When running a command that is disabled or without sufficient permission, it will be ignored by the bot.
$ .help core.ping
[Command] core.ping
Check shard network latency.
Usage:
.core ping
You can also use alias on help:
.help ping
is equivalent to.help core.ping
.
Usage
Command usage can be displayed by using .help
on the command. Each line in usage example represents a different way of using the command.
Usage:
.module command [required argument] (optional argument) (remaning arguments...)
.module command clear (either this|or this)
Modules
Modules are independent units with a specific function - enabling/disabling one module will have no effect on one another. Modules that are disabled will not be loaded on startup.
Structure
Each module is a collection of commands and aliases, it also provides a brief description of itself to be used in the help commands.
Each module contains the following information:
name: core
description: Core service modules.
aliases:
- ping → core ping
- version → core version
commands:
- ping
- version
And each command contains the following information:
name: ping
description: Check shard network latency.
usage:
- .core ping
Module options
Module/command can be enabled and disabled without restarting the bot, and permission rules can be applied, which will be discussed in greater detail in following chapters.
Bot options can only be modified by
admins
, you can give yourself permission to edit options by adding+@[user_id]
to~/.config/merlin/clearance.jsonc
.
Switches
Each module and command can be enabled/disabled through switching.
The command core.switch
provides functionality for enabling and disabling a module.
$ .help switch
[Command] core.switch
Enable/disable commands and modules.
Usage:
.core switch [module] (enable|disable)
Module
The module core
can be disabled with
$ .switch core disable
core has been disabled. (not saved)
Command
We will be considering commands to be a module, for example command
ping
in modulecore
will have identifiercore.ping
.
The command core.ping
can be disabled with
$ .switch core.ping disable
core.ping has been disabled. (not saved)
Disable behaviour
A command can only be used if itself and its parent module are both enabled.
Disabling a module will take effect immediately, and the bot will not respond to any disabled commands. This change can be made persistent across reloads by writing any changed options to config using .save
.
A disabled module may still appear in help pages, as there is no mechanics to unload modules at runtime. To fully unexist the module, a reload should be done using the .reload
command. (make sure to run .save
first!)
Permissions
On every action, the permission system checks you against a "rule list" for that operation. Only if you passes the rule list the operation will continue, otherwise your message will be ignored or a rejection message will be given as response.
Rule list
A rule list consist of multiple rules, for example
1. +everyone
2. -dm
3. +@siriusmart
Rules closer to the bottom has higher priority than rules on top, you can think of it as rules later in the list will "override" previous rules. Each condition is prefixed with a +
or -
to show the rule is an "allow" or "disallow" type of rule.
In our example rule list, the checks runs in the sequence:
- Check rule 3: Is your username "siriusmart"? If so, the check ends here and you are allowed to continue.
- Check rule 2: Are you running this command in a dm? If so, the check ends here and you are not allowed to continue.
- Check rule 1: Everyone is allowed, if you are not subjected to rule 2 or 3, then you are allowed to continue.
If the user is subjected to none of the rules, then it is assumed that the user is allowed to continue.
Syntax
Each rule starts with a modifier (+
or -
), followed by a condition.
[modifier][condition]
Clearance presets is the only type of rule that doesn't begin with a modifier, this will be mentioned later.
Conditions
Here's a list of all conditions
Condition | Satisfied when |
---|---|
everyone | Always satisfied. |
everywhere | Always satisfied. |
dm | The command is ran in a DM to the bot. |
server | The command is ran in a server. |
@user_name | The user has the specified username. |
@user_id | The user has the specified user ID. |
&server_id:role_name | The user has the specified role. |
&server_id:role_id | The user has the specified role of the ID. |
#channel_name | The command is ran in a channel with the specified name. |
#channel_id | The command is ran in a channel with the specified channel ID. |
%server_name | The command is ran in a server with the specified name. |
%server_id | The command is ran in a server with the specified ID. |
Adding a role permissions in a server will automatically prepend it the rule with the server ID.
Changing permissions
The command core.perms
provides functionality for modifying module and command permissions.
$ .help perms
[Command] core.perms
Manage module permissions.
Usage:
.core perms [module] (rules...)
.core perms [module] clear
The argument (rules...)
represent a list that is optional, for example
$ .perms core.ping -everyone +@siriusmart
Module permissions for core.ping updated. (not saved)
Disables core.ping
for everyone except siriusmart. The rule +@siriusmart
is later in the list, and therefore has a higher priority than -everyone
.
Viewing permissions
Permissions for existing commands can be viewed by not including a rule list, for example.
$ .perms core.ping
[Permission] core.ping
1. -everyone
2. +@623823202073706496
A command can be used only if it is allowed by both the rule lists for the command and for its parent module.
Clearance presets
Clearance presets are macros that can be used in rule lists.
Using presets allow for changes in permission to propagate to multiple rule lists using the preset. Presets can be used in rule lists with the ?preset_name
syntax.
Example
Suppose we have the preset "admin" as follows
1. +&bot_masters
2. +@siriusmart
3. +#admin_chat
This preset can be used in other rule lists
1. -everyone
2. ?admin
3. -dm
since preset work the same as macros, the rule list above is equivalent to
1. -everyone
2. +&bot_masters
3. +@siriusmart
4. +#admin_chat
5. -dm
where the ?admin
rule is replaced by contents in the preset.
Using a preset that doesn't exist is the same as using an empty preset, this is unsounding so make sure you are not making a type when specifying presets.
Managing presets
The command core.clearance
provides functionality for modifying presets.
$ .help clearance
[Command] core.clearance
Manage clearance presets.
Usage:
.core clearance (preset)
.core clearance [preset] (rules...)
.core clearance [preset] clear
Using the command without arguments responds with a list of all nonempty presets. Preset content can be changed similar to how permission is changed.
$ .clearance admin +&bot_masters +@siriusmart +#admin_chat
Clearance preset admin updated.
To prevent circular definitions, presets should not contain other presets. Although not recommended, you may still define presets in terms of other presets by modifying
clearance.jsonc
.
Features
Features are essentially a module system at compile time - a feature can be disabled at compile such that it is physically not in the bot. (Ideally) features are independent of each other, and can be developed separately.
The bot currently includes 2 features, both are enabled by default.
- modcore
- modcoords
To only compile certain features, use the cargo compile flags
$ cargo build --release --no-default-features -F feature1 -F feature2 # ...
where feature1
and feature2
are features you wish to compile.
Modules
Modules are loaded in when the bot starts. Each module consists of
- A help page
- Multiple commands
- Multiple aliases
- A default command
Aliases
Aliases are decoy commands that redirects you to another command. For example, given the alias ping → core ping
running
$ .ping
will be recognised as running
$ .core ping
where ping
is replaced by core ping
.
Default command
A default command is essentially an alias, when calling a module without specifying a command, the "default command" will get ran instead. For example, core → help core
. Which is why when running .core
you will see the help page.
Module documentation
The following pages will contain information about various modules and their commands.
[Module] Coords DB
Searchable coordinates for anarchy servers.
Categorisation
Each entry is associated with a parent category and a child category, similar to the binary nomenclature used in naming species.
For example, farm.enderman
would be a valid name for a category.
Reserved categories
Most categories can be treated as a folder where privileged users can access. However, there are some reserved categories with special behaviours.
Category | Description |
---|---|
generic.unspecified | When an entry is added without specifying a category, it will be assumed to be in this category. When deleting a main category, its entries will be automatically moved here. |
generic.private | Entries added to this category will only be visible to the person who added the entry with no exception. |
[cogname].unspecified | Entries added to [cogname] without specifying a subcategory will be added here, it has the same permission level as its parent category. |
Operations
Each parent and subcategory can include a rule list. Users with permission to a category has full access to that category, a user have permission to a subcategory if it has permission to both the parent and child category.
Access
A user with permission to a category can
- Edit category details.
- Edit entries within that category.
An edit action includes updating details, adding items and removing items.
Clearance
- Users with the
?coorduser
clearance preset can use commands in thecoord
module. - Users with the
?coordmod
clearance preset can edit permissions of categories.
By default all new categories are created with the
?coordmod
preset, you will need to modify that to allow access to all users.
[Command] Coords.CogAdd
Create a new category.
Categories
Entries are organised into categories and subcategories, all entries must be under a category and subcategory.
Creating a category
.cogadd new-category # where `new-category` is the name of the category
This command will only succeed if there isn't another category with the name new-category
.
Creating a subcategory
.cogadd new-category.new-subcategory # where `new_subcategory` is the name of the subcategory
This command will only succeed if the parent category new-category
exists, and there isn't another subcategory with the name new-subcategory
under the same parent category.
Category description
A category (or subcategory) can be created with a description by passing it as an additional argument.
.cogadd new-category 'This is a description.'
.cogadd new-category.new-subcategory 'This is another description.'
The single quotes shows that it is a single argument.
Permissions
- Anyone can create top level categories.
- Users can only create subcategories under a parent category if they have access to the parent category.
- New subcategories cannot be created under the
generic
parent category.
[Command] Coords.CogEdit
Edit an existing category.
Category details
Each category contains the following fields.
Field | Description |
---|---|
name | A unique identifier. |
desc | An optional description text. |
path | Absolute path in the host system where attached files for entries in this category. |
Renaming category
.cogedit category name=new-name
This command renames the category with name category
to new-name
, provided a category with name category
exists.
Renaming subcategory
.cogedit category.subcategory name=new-name
This command renames category.subcategory
to category.new-name
, you cannot change a subcategory's parent by renaming it.
Changing description
The text description of a category or subcategory can be edited.
.cogedit category desc='This is a description.'
.cogedit category.subcategory desc='This is a description.'
Removing description
An empty description text is treated not exist.
.cogedit category desc=''
Where ''
shows that there is an empty argument following desc=
.
Permissions
- Users can only edit a parent category if they have access to it.
- Users can only edit a child category if they have access to both the parent and child category.
generic.*
and*.unspecified
are system categories that cannot be edited.
[Command] Coords.CogRm
Removes an existing category.
Caveat
This command can only be used to remove empty categories.
Removing a category
.cogrm category
.cogrm category.subcategory
Permissions
- Users can only remove a parent category if they have access to it.
- Users can only remove a child category if they have access to both the parent and child category.
generic.*
and*.unspecified
are system categories that cannot be removed.
[Command] Coords.CogPerms
Edit permissions to a category.
Usage
This command uses the same rules for permission as core.perms
.
Setting permissions
.cogperms category [rule list]
.cogperms category.subcategory [rule list]
Read more about rule lists here.
Removing permissions
.cogperms category clear
.cogperms category.subcategory clear
Permissions
- Anyone allowed in the
?coordmod
clearance preset (customisable).
[Command] Coords.CoordAdd
Add an entry.
Entries
Each entry corresponds to a location in the Minecraft world, when creating an entry, the following details must be provided.
Field | Description |
---|---|
name | Unique name for the entry. |
dim | Dimension of the location, allowes ow (overworld), nether and end . |
x | Whole number X coordinate. |
z | Whole number Z coordinate. |
Adding an entry (minimum)
Providing only the required details, details of the entry can be edited separately.
.coordadd [name] [dim] [x] [z]
.coordadd big-base ow 1234 -5678
Since a category is not specified, the entry will be added to
generic.unspecified
.
Adding an entry with additional info
Full details can be provided on creation, detailed description of each field can be found in coords.coordedit
.
.coordadd [name] [dim] [x] [z] (category)
.coordadd [name] [dim] [x] [z] (category) (description)
.coordadd [name] [dim] [x] [z] (category) (description) (tags)
Where tags is a list of comma separated values with no whitespace in between each item. For example tag1,tag2,tag3
.
The user will be notified to edit an existing entry instead if there exist a visible entry within close proximity of the new entry (customisable).
Permissions
- Anyone can add entries to
generic.unspecified
andgeneric.private
. - Users can add entries to a category if they have access to it.
- Entries added to
generic.private
is only visible to the author.
[Command] Coords.Find
Search for entries.
Simple search
Search for a single entry
Search for an entry using its name.
.find big-base
This command only succeed if there is a visible entry with name big-base
.
Search for a category
List entries in a category.
.find category
.find category.subcategory
Regex search
If you only remember parts of the name or description, you can always use regex search on the name
and description
field.
.find he # will match `he who must not be named`
.find hello desc='metro' # will match whatever description string containing `metro`
Search by ID
Each entry has a permanent unique identifier. If the ID is 123
, then the entry can be found with.
.find 123
This command only succeed if the specified category exists.
Filters
The following filters can be applied when searching.
Field | Description | Format |
---|---|---|
dim | Dimension of the location, allowes ow (overworld), nether and end . | [Dimension] |
cog | Category of the entry. | [Category] or [Category].[Subcategory] |
near | Search for entries at a radius from a specific point. | [x],[z],[radius] |
tags | Search for entries containing all of the specified tags. | [Tag],[Tag],.. |
page | If the filter allows for a large number of entries, you may specify a page number. (default=1) | [Integer] |
Usage
.find field1=value1 field2=value2
# search for entries in the overworld and within 5000 blocks of spawn
.find dim=ow near=0,0,5000
# search page for of entries under the `farm` category, with tags `afkable` and `exp`
.find cog=farm tags=afkable,exp page=3
You may also list out all visible entries with
.find *
Permissions
- Anyone can view entries from
generic.unspecified
and their own entries ingeneric.private
. - Users can view entries from a category if they have access to it.
[Command] Coords.CoordEdit
Bulk edit existing entries.
Entry details
All fields of an entry can be edited.
Field | Description | Format |
---|---|---|
newname | Unique name for the entry. | [String] |
newdesc | Description text for the entry. | [String] |
newdim | Dimension of the location, allows ow (overworld), nether and end . | [String] |
newpos | Whole number coordinates in format of x,z . | [int],[int] |
newcog | Category to move to. | [String] or [String].[String] |
newtags | List of tags. | [String],[String],... |
Edit a single entry
A single entry can be specified by its unique name.
.coordedit [entry name] field1=value1 field2=value2
# moves entry to the `base.member` category
.coordedit big-base newcog=base.member
# updates description to `A very cool base` and set tags to `meeting` and `stash`
.coordedit big-base newdesc='A very cool base.' newtags=meeting,stash
Bulk editing
You may apply search filters as specified in find
to edit entries in bulk.
# moves all visible entries within a 100 block radius to 10000 10000 to the `mainbase.private` category
.coordedit near=10000,10000,100 newcog=mainbase.private
Note that you cannot move an entry not created by you to
generic.private
.
Permissions
- Anyone can edit entries from
generic.unspecified
and their own entries ingeneric.private
. - Users can edit entries from a category if they have access to it.
[Command] Coords.CoordRm
Remove an entry from coord DB.
Remove a single entry
Remove an entry using its unique name.
.coordrm big-base
This command only succeed if there is a visible entry with name big-base
.
Bulk remove
You may use search filters as specified in find
.
.coordrm field1=value1 field2=value2
Permissions
- Anyone can remove entries from
generic.unspecified
and their own entries ingeneric.private
. - Users can remove entries from a category if they have access to it.
[Command] Coords.Attach
Attach files to an entry.
Upload by name
You may upload any number of files to Merlin.
.attach big-base [attachments]
This command only succeed if there is a visible entry with name big-base
.
If the message does not include any attachments, the command will not succeed.
Filters
You may use search filters as specified in find
.
.attach field1=value1 field2=value2 [attachments]
If more than one entry match the filters, the command will not succeed.
Permissions
- Anyone can attach entries in
generic.unspecified
and their own entries ingeneric.private
. - Users can attach entries in a category if they have access to it.
Known issues
Currently none.
When in doubt, chuck a water bucket at the server.
- Disable the problematic module with
.switch [module] disable
. - Write to config file so changes will persist over restarts with
.save
. - Reload config files so that the problematic is completely taken offline with
.reload
. - DM @siriusmart on Discord and get the issue fixed.
Changelog
v0.1.3 (2024-10-31)
Added
- Attachment management through
coord.attach
. - Search by ID for entries using
coord.find
.
v0.1.2 (2024-10-28)
Added
- Regex search for
coords.find
. - Shorthand category search for
coords.find
. - Custom config directory through the
CONFIG
env.