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:

FeatureDescription
modcoreCore module, provide basic functionalities.
modcoordsCoords 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 module core will have identifier core.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:

  1. Check rule 3: Is your username "siriusmart"? If so, the check ends here and you are allowed to continue.
  2. Check rule 2: Are you running this command in a dm? If so, the check ends here and you are not allowed to continue.
  3. 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

ConditionSatisfied when
everyoneAlways satisfied.
everywhereAlways satisfied.
dmThe command is ran in a DM to the bot.
serverThe command is ran in a server.
@user_nameThe user has the specified username.
@user_idThe user has the specified user ID.
&server_id:role_nameThe user has the specified role.
&server_id:role_idThe user has the specified role of the ID.
#channel_nameThe command is ran in a channel with the specified name.
#channel_idThe command is ran in a channel with the specified channel ID.
%server_nameThe command is ran in a server with the specified name.
%server_idThe 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.

CategoryDescription
generic.unspecifiedWhen 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.privateEntries added to this category will only be visible to the person who added the entry with no exception.
[cogname].unspecifiedEntries 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 the coord 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.

FieldDescription
nameA unique identifier.
descAn optional description text.
pathAbsolute 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.

FieldDescription
nameUnique name for the entry.
dimDimension of the location, allowes ow (overworld), nether and end.
xWhole number X coordinate.
zWhole 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 and generic.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.

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

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.

FieldDescriptionFormat
dimDimension of the location, allowes ow (overworld), nether and end.[Dimension]
cogCategory of the entry.[Category] or [Category].[Subcategory]
nearSearch for entries at a radius from a specific point.[x],[z],[radius]
tagsSearch for entries containing all of the specified tags.[Tag],[Tag],..
pageIf 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 in generic.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.

FieldDescriptionFormat
newnameUnique name for the entry.[String]
newdescDescription text for the entry.[String]
newdimDimension of the location, allows ow (overworld), nether and end.[String]
newposWhole number coordinates in format of x,z.[int],[int]
newcogCategory to move to.[String] or [String].[String]
newtagsList 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 in generic.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 in generic.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 in generic.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.

  1. Disable the problematic module with .switch [module] disable.
  2. Write to config file so changes will persist over restarts with .save.
  3. Reload config files so that the problematic is completely taken offline with .reload.
  4. 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.