Hearth Script

Write a few lines of JavaScript and have Hearth run them when someone says the right thing.

What Hearth Script is

Hearth Script is JavaScript. If you have written JavaScript before, you already know it — variables, if, loops, functions, strings, arrays, Math and JSON all behave as you expect.

A script runs when a custom command’s trigger matches a message. It cannot run on its own, it cannot run forever, and it can only affect your server through the hearth object described below.

Scripting is a Pro feature.

What a script cannot do

Scripts run inside a sandbox. This is not a list of things that are discouraged — none of them are possible, and each is covered by a test that tries it:

  • No filesystem, no network, no access to the machine Hearth runs on.
  • No access to any other server’s data. A script sees only the server it ran in.
  • No eval and no building code from strings at runtime.
  • No mentioning @everyone or @here. A script runs on messages, so one that could do this would turn a busy day into an outage.
  • No granting a role above Hearth’s own. A script can never give someone more power than Hearth itself has.

If a script breaks one of these rules, none of its actions happen — not the ones before the mistake either. A half-applied script is worse than one that plainly failed.

Working with IDs

Discord IDs are always strings in Hearth Script, and must stay that way. They are larger than JavaScript can hold as an exact number, so treating one as a number rounds it — and a rounded ID is a different, real member.

To find an ID, turn on Developer Mode in Discord, then right-click a user, role or channel and choose “Copy ID”.

Reference

What a script can read

userName
The display name of whoever triggered the command.
userId
Their Discord ID, as a string.
guildName
Your server’s name.
guildId
Your server’s ID, as a string.
message
The full text of the message that triggered the command.
member
Who used the command: .name, .nickname, .id, .roleIds, .roleNames, .joinedAt, .createdAt. Roles are empty if Discord has not cached the member, so an absent role is not proof they lack it.
server
This server: .name, .id, .memberCount.
channel
Where the command was used: .name, .id.
msg
The triggering message: .content, .id.

What a script can do

hearth.sendMessage(content, channelId?)
Post a message. Without a channel it goes where the command was used.
hearth.sendTemplate(name, channelId?)
Post one of your saved message templates by name.
hearth.addRole(userId, roleId)
Give someone a role.
hearth.removeRole(userId, roleId)
Take a role away.
hearth.adjustCurrency(userId, amount)
Change a balance. Negative removes; balances never go below zero.
hearth.log(content)
Write a line to your log channel.
hearth.set(key, value)
Store a value against the server.
hearth.setForUser(userId, key, value)
Store a value against one member.

Limits

Every script shares Hearth with every other server, so each run has a budget.

Time per run
500ms
Steps per run
100,000
Actions per run
10
Runs per minute, per server
30
Message length
2,000 characters

Examples

The basics

Start here. A script is JavaScript, and these are the shapes almost everything uses.

Reply with a message

The smallest useful script.

hearth.sendMessage('Hello!');

Use the person’s name

Variables are ordinary JavaScript values, so string concatenation works.

hearth.sendMessage('Hello ' + userName + '!');

Mention them properly

A real Discord mention is <@ then the ID then >. Hearth will not let it ping the whole server, but it does highlight for that member.

hearth.sendMessage('Welcome <@' + userId + '>!');

Say which server they are in

Useful when the same script is shared between servers.

hearth.sendMessage('You are in ' + guildName + '.');

Answering differently

The full triggering message is available, so one command can branch on what was said.

Branch on what the message contains

One command, several answers.

if (message.includes('rules')) {
  hearth.sendMessage('Please read #rules.');
} else if (message.includes('mod')) {
  hearth.sendMessage('A moderator will be with you shortly.');
} else {
  hearth.sendMessage('Ask away and someone will help.');
}

Read an argument after the trigger

With a StartsWith trigger of "!echo", everything after it is the argument.

const argument = message.slice('!echo'.length).trim();

if (argument.length === 0) {
  hearth.sendMessage('Give me something to echo.');
} else {
  hearth.sendMessage(argument);
}

Only answer people whose name matches

Any JavaScript comparison works.

if (userName.toLowerCase().startsWith('a')) {
  hearth.sendMessage('An A name! Nice.');
}

Do nothing sometimes

A script that sends nothing is fine. Nothing is posted and nobody is told, which is what you want for a rare-response command.

if (Math.random() < 0.1) {
  hearth.sendMessage('Rare response!');
}

Knowing who is asking

Read-only details about the member, the channel and the server. None of it can be changed from a script — it is there so a script can decide.

Only answer people with a role

Match on the role name, or on its ID if names change.

if (member.roleNames.includes('Moderator')) {
  hearth.sendMessage('Yes, boss.');
} else {
  hearth.sendMessage('That command is for moderators.');
}

Refuse very new accounts

A common anti-spam check. Dates are ISO strings, so JavaScript’s Date does the work.

const ageDays = (Date.now() - new Date(member.createdAt)) / 86400000;

if (ageDays < 7) {
  hearth.sendMessage('This command needs an account older than a week.');
} else {
  hearth.sendMessage('Welcome aboard!');
}

Greet by nickname when they have one

Nickname is null when unset, so fall back to the username.

hearth.sendMessage('Hi ' + (member.nickname || member.name) + '!');

Reward long-standing members

How long they have been in this server, rather than on Discord.

const days = (Date.now() - new Date(member.joinedAt)) / 86400000;

if (days > 365) {
  hearth.adjustCurrency(userId, 100);
  hearth.sendMessage('A year here! Have 100 coins.');
}

Say something about the server

Member count and channel name are both available.

hearth.sendMessage(server.name + ' has ' + server.memberCount + ' members. You are in #' + channel.name + '.');

Randomness and numbers

Math and the rest of JavaScript’s standard library are available in full.

Roll a die

Math.random and Math.floor, exactly as anywhere else.

const roll = Math.floor(Math.random() * 6) + 1;
hearth.sendMessage(userName + ' rolled a ' + roll + '.');

Pick from a list

An array plus a random index is the whole trick.

const answers = ['Yes', 'No', 'Ask again later', 'Definitely'];
const pick = answers[Math.floor(Math.random() * answers.length)];

hearth.sendMessage(pick);

Flip a coin

A conditional expression keeps it to one line.

hearth.sendMessage(Math.random() < 0.5 ? 'Heads' : 'Tails');

Roll any dice notation

Parse the count and sides out of the message, then sum the rolls.

const match = /(\d+)d(\d+)/.exec(message);
const count = match ? Math.min(Number(match[1]), 20) : 1;
const sides = match ? Math.min(Number(match[2]), 100) : 6;

let total = 0;
for (let i = 0; i < count; i++) {
  total += Math.floor(Math.random() * sides) + 1;
}

hearth.sendMessage(userName + ' rolled ' + total + '.');

Roles

Hearth can only grant roles below its own. Move Hearth’s role up in Server Settings if a grant is refused.

Give someone a role

IDs are always strings. A Discord ID is too large for JavaScript to hold exactly, and an unquoted one silently becomes a different, real member.

hearth.addRole(userId, '112233445566778899');
hearth.sendMessage(userName + ' has been given the role.');

Take a role away

The mirror of addRole.

hearth.removeRole(userId, '112233445566778899');

Let people pick a colour role

Remove the others first so only one is ever held.

const colours = {
  red: '111111111111111111',
  blue: '222222222222222222',
  green: '333333333333333333',
};

const wanted = message.split(' ')[1];

if (colours[wanted]) {
  for (const id of Object.values(colours)) {
    hearth.removeRole(userId, id);
  }
  hearth.addRole(userId, colours[wanted]);
  hearth.sendMessage(userName + ' is now ' + wanted + '.');
} else {
  hearth.sendMessage('Pick one of: red, blue, green.');
}

Currency

Balances never go below zero, so a negative adjustment cannot overdraw an account.

Reward someone

A positive amount adds.

hearth.adjustCurrency(userId, 10);
hearth.sendMessage(userName + ' earned 10 coins.');

Charge for something

A negative amount removes, clamped at zero.

hearth.adjustCurrency(userId, -5);
hearth.sendMessage('That cost you 5 coins.');

A random payout

Combine randomness with currency for a daily-style command.

const payout = Math.floor(Math.random() * 50) + 10;

hearth.adjustCurrency(userId, payout);
hearth.sendMessage(userName + ' found ' + payout + ' coins.');

Remembering things

Storage is small and simple. A script writes; the dashboard shows what is stored. There is no read from within a script yet.

Remember who last used a command

One value against the whole server.

hearth.set('lastAsked', userName);

Remember something per member

The same, keyed to one person.

hearth.setForUser(userId, 'favouriteColour', 'blue');

Record what someone said

Trim it, because stored values are bounded.

const note = message.slice('!note'.length).trim().slice(0, 200);

hearth.setForUser(userId, 'note', note);
hearth.sendMessage('Noted.');

Logging and moderation

Writes to the log channel configured for your server.

Log that something happened

Goes to your log channel, not to the channel the command was used in.

hearth.log(userName + ' used the report command.');

Quietly flag a message

Reply to the member while telling moderators separately.

hearth.sendMessage('Thanks, a moderator will look into it.');
hearth.log('Report from ' + userName + ': ' + message.slice(0, 200));

Formatting and channels

Discord markdown works, and a message can be sent somewhere other than here.

Post into a specific channel

Pass a channel ID as the last argument.

hearth.sendMessage('A report was filed.', '998877665544332211');

Use a saved template

Keeps formatting out of the script.

hearth.sendTemplate('welcome-pack');

Build a multi-line message

Discord markdown is just text — bold, lists and code all work.

const lines = [
  '**Server rules**',
  '1. Be kind',
  '2. Stay on topic',
  '3. No spam',
];

hearth.sendMessage(lines.join('\n'));

Community scripts

Once enough servers are writing scripts, the best of them will be collected here. Nothing is listed yet — and rather than fill this with invented entries, it stays empty until there is something real to show.