Scripts
Automate your client with JavaScript — custom /commands, message filters, auto-responders and web API integrations, in the spirit of mIRC and irssi scripting
How Scripts Work
A script is a small piece of JavaScript that reacts to what happens in the client: it can subscribe to events (messages, joins, parts, nick changes…), register its own slash commands, send messages, print local notes and fetch data from the web. Scripts run inside the client — there is nothing to install and no external process.
To create one:
- Click your avatar in the toolbar and choose Profile Settings
- Scroll to the Scripts section and click New script
- Write your code in the editor (it opens pre-filled with a commented example) and Save
- Flip the script's toggle to enable it — it starts immediately, no reconnect needed
Scripts are stored locally in your browser or app — nothing is uploaded anywhere. The same scripts work in the web app and the desktop builds.
Sandboxed by Design
Every script runs in its own isolated JavaScript sandbox (a QuickJS engine compiled to WebAssembly). A script only sees the sic API described below — it has no access to the page, your settings, your stored passwords or other scripts. On top of that:
- An accidental infinite loop is interrupted automatically instead of freezing the client
- Each script has its own memory limit
- A script error never breaks message handling — it is reported on the script's row in settings and as a
[script:Name]line in the Status window - Disabling a script tears it down completely, including its commands and pending requests
The sic API
Scripts talk to the client through a single global object:
sic.on(event, handler)
Subscribes to an event (see the table below). Returns a function that unsubscribes the handler. Handlers for message and notice can call e.block() to hide the message before it renders, or assign to e.text to rewrite it.
sic.command(name, handler, options?)
Registers a slash command; the handler receives (args, channel) — the rest of the typed line and the window it was typed in. Optional { aliases: […] }. Script commands show up in Tab completion; names that collide with built-in commands are rejected.
sic.say(target, text) / sic.sendRaw(line)
say sends a normal message to a channel or nick. sendRaw sends a raw IRC line for anything the API does not cover (MODE, TOPIC, CTCP…).
sic.print(text, target?)
Prints a local informational line that is not sent to the server — to the current window, or to a named one (for example 'Status').
sic.fetch(url, options?)
HTTP request returning a Promise of { ok, status, headers, body, json() }. Options: method, headers, body. Limits: http(s) only, 10 second timeout, 1 MB response, 4 concurrent requests — and the target API must allow cross-origin requests (CORS), like any browser fetch.
sic.nick() / sic.currentChannel()
Your current nickname and the name of the active window.
Events
| Event | Handler receives | Can block / rewrite |
|---|---|---|
message |
nick, target, text, self, tags |
Yes — e.block() and e.text |
notice |
nick, target, text, tags |
Yes — e.block() and e.text |
join |
nick, channel |
Observe only |
part |
nick, channel, reason |
Observe only |
quit |
nick, reason |
Observe only |
nick |
oldNick, newNick |
Observe only |
connect / disconnect |
— | Observe only |
raw |
line (the unparsed IRC line) |
e.block() — advanced, use with care |
A blocked message is gone for good: it does not render, does not bump unread counters and does not trigger mention notifications. When several scripts are enabled they run in the order they were created — each sees the text as rewritten by the previous one, and the first block() wins.
Example Scripts
Copy any example, open Profile Settings → Scripts → New script, replace the pre-filled code, save and enable. Each one is self-contained — use them as they are or as starting points.
The Classic /slap
No IRC client is complete without it. Registers /slap (alias /trout) and sends the traditional action message.
// /slap <nick> - the IRC classic
sic.command('slap', (args, channel) => {
const victim = args.trim() || 'someone';
sic.sendRaw('PRIVMSG ' + channel +
' :\u0001ACTION slaps ' + victim +
' around a bit with a large trout\u0001');
}, { aliases: ['trout'] });
Ignore List and Word Filter
Hides messages from nicks you never want to hear from and anything containing blocked phrases — filtered messages never render and never trigger notifications.
// Personal ignore list + spam filter
const ignoredNicks = ['spammer42', 'flooder'];
const blockedPhrases = ['free crypto', 'casino bonus'];
sic.on('message', (e) => {
if (ignoredNicks.includes(e.nick.toLowerCase())) {
e.block();
return;
}
const text = e.text.toLowerCase();
if (blockedPhrases.some((phrase) => text.includes(phrase))) {
e.block();
}
});
Tame the Shouting
Rewrites incoming messages instead of hiding them: anything written in ALL CAPS arrives in calm lowercase.
// Lowercase ALL-CAPS messages
sic.on('message', (e) => {
const letters = e.text.replace(/[^a-z]/gi, '');
if (letters.length >= 8 && letters === letters.toUpperCase()) {
e.text = e.text.toLowerCase();
}
});
Mention Log
Collects every message that mentions your nick into the Status window — one place to catch up after being away, no matter how many channels you are in.
// Copy mentions to the Status window
sic.on('message', (e) => {
if (e.self) { return; }
if (e.text.toLowerCase().includes(sic.nick().toLowerCase())) {
sic.print('[mention] ' + e.nick + ' in ' + e.target + ': ' + e.text, 'Status');
}
});
Channel Greeter
Welcomes people joining your channel. The nick check keeps it from greeting you on reconnect.
// Greet joins in one channel
const myChannel = '#mychannel';
sic.on('join', (e) => {
if (e.channel === myChannel && e.nick !== sic.nick()) {
sic.say(myChannel, 'Welcome to ' + myChannel + ', ' + e.nick + '!');
}
});
/weather — Web APIs from a Script
Fetches a one-line forecast from wttr.in and shares it with the channel. The same pattern works with any CORS-enabled JSON or text API.
// /weather <city> - share the forecast
sic.command('weather', (args, channel) => {
const city = args.trim();
if (!city) {
sic.print('Usage: /weather <city>');
return;
}
sic.fetch('https://wttr.in/' + encodeURIComponent(city) + '?format=3')
.then((res) => sic.say(channel, res.body.trim()))
.catch((err) => sic.print('weather: ' + err.message));
});
/github — JSON APIs
Looks up a repository on the GitHub API and posts a summary — a compact example of json(), error handling and status checks.
// /github owner/repo - repository summary
sic.command('github', (args, channel) => {
const repo = args.trim();
if (!repo.includes('/')) {
sic.print('Usage: /github owner/repo');
return;
}
sic.fetch('https://api.github.com/repos/' + repo)
.then((res) => {
if (!res.ok) {
sic.print('github: HTTP ' + res.status);
return;
}
const data = res.json();
sic.say(channel, data.full_name + ' — ' +
(data.description || 'no description') +
' | ⭐ ' + data.stargazers_count);
})
.catch((err) => sic.print('github: ' + err.message));
});
Auto-Join on Connect
Runs whenever registration with the server completes — handy for anything you would otherwise type by hand after connecting.
// Extra channels on every (re)connect
sic.on('connect', () => {
sic.sendRaw('JOIN #linux,#security');
});
Try It Yourself
Open Simple IRC Client, head to Profile Settings and write your first script — it is just JavaScript.