How to build anything Sirius can do: flows that react to commands, messages, reactions and buttons,
remember things, and send messages that fill themselves in. Read part 1 and 2 once; part 3 is the language
you write in, and part 4 lists everything you can use.
1 · Getting started
Apps, flows and nodes
An app is one feature of your server - a ticket system, a leaderboard, a welcome message. It holds
flows, and the variables and tables they share. Turning an app off stops all its flows; exporting it copies it
to another server.
A flow is drawn on a canvas: nodes joined by wires, read left to right. It starts at a
trigger (someone runs a command, sends a message, reacts, presses a button…), then runs the nodes the wires lead
to: steps that do things (send a message, start a thread, give a role, add a table row), checks that let
the run through or stop it, and logic (If, Switch, For each…).
Triggers
What starts it - and, wired in the middle, what it waits for.
Steps
What it does. Each gives results the next nodes can use.
Checks & logic
Which way it goes: passes / fails, true / false, one wire per case.
Placeholders
{user.mention}, {steps.find.rows}… values filled in when it runs.
Your first flow
Server → Apps → New app. The studio opens on an empty flow. (Or pick a ready-made app, like Thread tickets.)
Triggers tab → Command. Drop it on the canvas and name it, e.g. hello.
Nodes tab → Send a message. Drag it next to the trigger and wire the trigger's Then dot to it
(or click the dot and pick it).
Write the message. Type { to pick a placeholder: Hello {user.mention}!
Check the live preview (top left): it's made by the bot with the same code it uses in Discord.
Publish. Until then it's a draft (autosaved). Then run /c name:hello in Discord.
The studio
Left
Triggers, Nodes and Variables tabs. Drag onto the canvas, or click to add after the selected node.
Canvas
Drag to pan, Ctrl + scroll to zoom, Shift + drag to select several. Wires go from a node's dots on the right to the next node.
Faint dashed wires underneath show which node uses a value another one gives - full for the node you select or point at.
Drag a node's bottom dot onto another node to use one of its values there.
Right
The selected node's Settings, a Test run, the app's Data, and History (the last published versions; restore any).
Top
The app's flows as tabs, Canvas / List view, whether it's Valid, undo / redo, Save draft and Publish.
Work is autosaved as a draft. Discord keeps running the last published version until you publish.
Discard changes goes back to it.
Publish checks everything; if something's wrong, the problems are listed and the draft is kept.
The live preview is a dry run: every message it would send, in order, and what the other nodes would do - nothing
is sent or changed. The nodes it went through light up.
2 · Building flows
Triggers
A flow can have several triggers leading into the same nodes (a command and a button doing the same thing).
Each command trigger is a command of its own. A trigger nothing is wired into starts the flow; one with a wire
coming in is a Wait for.
Another flow of this app runs it (Call a flow, or Fire an event)
{call.<name>}, {call.from}
Every run also has {user} (who it's about), {channel} (where) and {server}.
Name a trigger (its Name setting, e.g. reactor) and {reactor.user.mention} always means
that trigger's event - plain {user} means the latest one. Everything each trigger gives is in
What each trigger gives.
Commands: /c name:hello always works. Turn on Real slash command to also get /hello with real options (Discord can take a moment to show it).
Nodes
Every step has a name (shown on the node, like send); later nodes read its results as
{steps.name.result}. Any step can also lead somewhere else if it fails - for any reason,
Discord refusing included - with {steps.name.error} (what went wrong) and {steps.name.error_kind}
(permission, not_found, discord or step). Every step has an Only run this step if….
Who and what a step acts on. Wherever a step picks a person (Give a role, Time out, Kick / ban, DM, permissions), a
message (Delete, Pin, Reaction, Edit, Start a thread on it) or a thread / channel an earlier step made, the choice has
A value… too: {steps.find.first.member}, {interaction.message.author}, {reaction.message},
a mention, an ID or a message link. So a schedule can give a role to the week's winner, and a button can DM the post's author.
Actions
Node
What it does
Gives
Send a message
Text and embeds, or a laid-out message (Components V2), with buttons, menus and pickers
{steps.name} the message itself {steps.name.message_id} the message's ID {steps.name.link} a link to the message {steps.name.situation} which situation was sent (0 = the default message)
Send a built-in embed
Sirius's own /arbitration, /sortie, /baro… embed, with your changes
{steps.name} the message itself {steps.name.message_id} the message's ID {steps.name.link} a link to the message {steps.name.title} the embed's title
Grade a riven
Read a riven screenshot and post the grade picture (like /grade)
{steps.name} the message itself {steps.name.found} “yes” if a riven was read {steps.name.weapon} the weapon {steps.name.grades} each stat's grade {steps.name.best} the first stat's grade {steps.name.rank} the riven's rank {steps.name.count} how many cards were graded {steps.name.message_id} the grade message's ID {steps.name.link} a link to it
Delete a message
Delete the triggering message or one an earlier step sent, now or after a delay
{steps.name.deleted} “yes”, or “later” with a delay
Reaction
Add or take off a reaction on a message (the triggering one, one a step sent, or one from a value)
—
Give a role
Give roles to the person who started it
{steps.name.changed} “yes” if a role was given {steps.name.skipped} roles it couldn't give, and why
Take a role
Take roles from the person who started it
{steps.name.changed} “yes” if a role was taken {steps.name.skipped} roles it couldn't take, and why
Pin / unpin a message
Pin or unpin a message: theirs, one a step sent, or one from a value
—
Start a thread
On a message, or a (private) thread in a channel; add people, lock it, post in it later
{steps.name} the thread / channel itself {steps.name.name} the thread's name {steps.name.link} a link to it {steps.name.mention} a clickable #thread {steps.name.id} its ID
Kick / ban / nickname
Kick, ban or unban someone, or change their nickname - who started it or a person from a value
{steps.name.done} what it did: kick / ban / unban / nickname
Edit a message
Change a message Sirius sent: from an earlier step, or by its link
{steps.name.message_id} the message's ID {steps.name.link} a link to it
Clean up messages
Delete the last 1-1000 messages in a channel (all, theirs, bots', or with some text)
{steps.name.count} how many were deleted
Create a channel
A text channel, optionally private to them and some roles (tickets, applications…)
{steps.name} the thread / channel itself {steps.name.id} its ID {steps.name.name} its name {steps.name.mention} a clickable #channel {steps.name.link} a link to it
Delete a channel
Delete this channel or one an earlier step made - only ones Sirius made (close a ticket)
—
Change channel permissions
Allow / deny seeing, typing, reacting… for them, some roles or everyone
—
Time out
Time out someone - who started it or a person from a value (or remove their timeout)
—
Show a build
Post a build from /build (compare two, or check for a mod)
{steps.name} the message itself {steps.name.found} “yes” if the build exists {steps.name.name} the build's name {steps.name.item} what it's for {steps.name.mods} its mods {steps.name.has} “yes” if it uses the mod you asked about {steps.name.added} compare: what the second build adds {steps.name.removed} compare: what it leaves out
Change a thread
Rename, close, open again, lock, add or remove someone, slowmode
{steps.name.id} the thread's ID {steps.name.mention} a clickable #thread {steps.name.done} what it did
Update the clicked message
After a button press: turn its buttons off, change its text, answer only them
—
Flow
Node
What it does
Gives
Stop
End the run here (give it an “only if” to stop only sometimes)
—
Show a form
Ask up to 5 questions in a pop-up - typed answers, dropdowns, ticks, members, roles, channels, files
{steps.name.fields} what they gave: fields.<name> (text, picks, mentions, yes / no, file links) {steps.name.fields_list} what they picked, as lists: fields_list.<name>
Wait
Pause before the next step - seconds or days (long waits carry on after a restart)
{steps.name.until} when it carries on
Wait for an answer
Wait for their next message in the channel - or give up after a while
{steps.name.answer} what they wrote {steps.name.link} a link to their message {steps.name.answered} “yes” when they answered
Call a flow
Run another flow of this app with some values, and use what it worked out
{steps.name.local} its run variables: local.<name> {steps.name.ok} “yes” when it worked
Fire an event
Start every flow of this app that listens to this event (they run on their own)
{steps.name.started} how many flows it started
Data
Node
What it does
Gives
Set a variable
Remember a value or count up: for this run, the app, the server, or on a person / channel / message
{steps.name.value} the new value
Read a variable
Read a value kept on someone or something other than this run's person / channel
{steps.name.value} its value {steps.name.found} “yes” if it has one
Add a row
Add a row to one of the app's tables
{steps.name.id} the new row's id (#1, #2…) {steps.name.row} the row: row.<column>
Find rows
Look rows up in a table: by id, or any column; sort them; count them
{steps.name.rows} the rows found (a list) {steps.name.count} how many match {steps.name.first} the first one: first.<column> {steps.name.found} “yes” if any
Change rows
Change columns of the rows that match: set, add, add to a list… (or add a row when none match)
{steps.name.count} how many rows changed {steps.name.added} the new row's id when none matched and it added one
Delete rows
Delete the rows that match
{steps.name.count} how many rows were deleted
Math
Work out a sum: + - * / % and round, min, max, abs
{steps.name.result} the answer
Text
Replace, split, find a pattern, change case, count characters
{steps.name.result} the text {steps.name.list} the pieces (split, pattern groups) {steps.name.count} how many {steps.name.found} Find a pattern: “yes” if it matched
List
Count, sort, filter, join or pick from a list
{steps.name.result} the answer {steps.name.list} the new list {steps.name.count} how many
Time
Now, add time, the time between two times, show a time
{steps.name.unix} the time (seconds) {steps.name.text} it as a Discord time {steps.name.result} the answer {steps.name.seconds} Time between: in seconds
Looks at one value; each case tests it its own way (is, is not, contains, starts / ends with, is one of,
more / less than, matches, has the role) and the first that's true takes its wire, or Otherwise.
For each · Repeat
Runs everything on its Each wire once per item of a list (or N times), then goes on down Then, after all.
Inside: {loop.item}, {loop.index}, {loop.first}, {loop.last}, {loop.count}.
Name its rounds (say squad) to read {squad.item} from a loop inside it. After it: {steps.loop.count}.
One at a time
Lets only one run per person / thread / … through; the others take Busy.
Checks
A check has a Passes and a Fails wire. With nothing on Fails, the run stops - quietly, or with a short
reply only they see.
Has roles
Only people with (or without) some roles
In channels
Only in (or not in) some channels or categories
Cooldown
Wait between uses, per person, channel or server
Discord permission
Only people with a Discord permission
Condition
Any condition over the data, like arbitration.tier in "S,A"
Message has
Only messages with an image, a file, a link or some text
Max runs
At most N runs per person, channel or server in some hours
Only at some times
Only on some days and/or between two times (the server's timezone)
Account / member age
Only accounts (or members) at least N days old - stops fresh alts
In a thread
Only inside a thread: any, one under some channels, or one this app made
Is the thread's creator
Only the person who made the thread (or who Sirius made it for)
Sirius needs the right permissions where it acts (Manage Messages to delete, Manage Roles to give roles, Manage Threads…). If it can't, the run log says so.
Waiting & listening
Wire a node into a trigger and it becomes a Wait for: the run pauses there until that happens (a reaction on the
message it just sent, a reply in this thread, someone joining…), then carries on with everything it knew.
Where
On a message an earlier step sent · In the thread / channel an earlier step made ·
Here (the channel the run started in and every thread under it) · Anywhere.
By
Anyone, or only the person the run is about.
Once
The first match carries the run on (When it happens); nothing in time goes down No match in time.
Every time
A listener: each match runs what's on Each time, until the thread / channel closes or the message is deleted.
At most 500 per server.
After a Start a thread step, listen “In the thread … made”, not “Here”: “Here” also hears every other thread under the channel. The studio picks it for you when you wire a new Wait for after a thread step.
Other ways to wait: the Wait step (seconds to 30 days), Wait for an answer (their next message), a message's
Continue this flow buttons and menus (one wire per button / option), and Show a form. All of it survives restarts.
Conditions
Used by If, Only run this step if…, the Condition check and message situations.
A condition is rows: a value, how to compare it, another value. Match all of them or any; a
group mixes the two ("this, and one of those").
Builder
Match all of these:
{interaction.message.author} is {interaction.user}
Group, any of these:
{interaction.user} has the role @Staff
{local.team} contains {interaction.user}
In Discord
True when the person who pressed the button wrote the message - and is Staff or in the team.
What you can compare follows the value: a person has has the role and is a bot, a list has contains,
numbers have more than… People, channels, roles and messages compare by who they are: a person "is" their
mention or their ID, and a list of people contains a person.
Write it instead shows the condition as text - the same language as {if}. Values can be
written with or without braces - {interaction.user} == {user} and interaction.user == user mean the
same - and a value can have filters there: {local.team | count} >= 4.
Write
Means
Example
==
is
user.name == "Maakun"
!=
is not
r.status != "closed"
>
more than
amount > 5
<
less than
amount < 5
>=
at least
count >= 3
<=
at most
count <= 3
contains
text has it / a list has it
local.team contains reactor.user
in
is one of
tier in "S,A"
not in
isn't one of
user not in local.team
startswith
text starts with
input startswith "!"
endswith
text ends with
input endswith "?"
matches
matches a pattern (regex)
input matches "^\d+$"
has_role
a person has a role (name, mention or id)
user has_role "Staff"
and / or / not
combine; ( ) to group
target.given and not user.bot
a name alone
it has a value (not empty, "no", "false" or 0)
target.given
A value with placeholders in quotes is filled in first: input == "{app.password}". Text compares ignore upper / lower case.
Variables & tables
Kept…
Read as
Lives
This run
{local.team}
One run - shared by everything in it that waits: its buttons, waits, forms and every event a listener hears.
Each ticket is its own run, so each ticket has its own team.
The app
{app.counter}
Until changed. Settings, counters, lists the whole app uses.
Kept on a person, channel / thread, message or role - one value per thing. Any flow reads it from that thing.
Set a variable sets, adds (counts up / down), adds to a list, removes from a list or clears. Read a variable reads one
kept on someone or something other than this run's own. Put a person (or channel, message…) in a variable and it's kept as that
thing: {each p in local.team}{p.mention}{end}.
Typed variables. In the studio's Variables tab (or the app's Variables page) give a variable a kind - text, number, person,
channel, role, message, list… Values are turned into that kind. Tick server-wide and every app sees it. A person / channel
variable is that thing: {app.watcher.mention}.
From a value. Every channel, role and message picker can take a value instead of a fixed pick. In a trigger it's read when an
event comes in, so {app.watch_channel} as a Message sent trigger's channel makes a "remote switch": change the
variable and the flow listens somewhere else - no republish.
Tables
Make one on the app's Tables page (columns: text, number, yes / no, time, person, channel, role, message link, list).
Add a row, Find rows (up to 500, filtered and sorted), Change rows, Delete rows.
Each row has an id that counts up. A person / channel / role / message column is that thing:
{r.member.mention}. Show many rows with pages.
Which rows: filters are rows of column · test · value, matched all or any, with groups for "this, and one
of those". Tests: is, is not, contains, doesn't contain, starts with, is one of (red, blue or a list), more / less than,
at least / most, empty, filled. Besides your columns: # (row id), when it was added, when it last changed (compare with
{now}). Find rows can also look for text anywhere in the row.
Change rows picks, per column: set to, add / subtract (numbers), add to / take out of the list, or empty it. Turn on
Add a row when none match for "find or add": the new row gets those values and what the filters look for with is
({steps.update.added} is its id).
A list column takes a list placeholder as it is ({steps.find.rows}), or one with list filters:
{steps.find.first.teammates | add: reactor.user} puts someone on it (once),
{steps.find.first.teammates | remove: reactor.user} takes them off, and the others stay people. Text is split at commas (people written as text are their names unless you use .mention).
Calling other flows
A flow whose trigger is Called by a flow works like a function: it declares inputs (a name, what it holds - text, number, yes / no, person, channel / thread, role, message, list, anything - and whether it's needed),
and every Call a flow node fills exactly those in. Inside, {call.name} reads one; a person input
is that person ({call.member.mention}). The caller waits for it and gets its {local.…} back as
{steps.call.local.…}.
Fire an event starts every flow (of this app, or of every app) that's Called by a flow on that event name - in the
background, without waiting.
Messages, buttons & pages
A message has text and up to 10 embeds (title, description, fields, colour, pictures, footer). Discord markdown
works: **bold**, *italic*, [link](https://…), `code`, > quote.
Every text box takes placeholders. It goes as a reply, privately (commands and buttons), in this channel,
in another channel, in a thread an earlier step made, or by DM.
Situations
Whole other messages with a condition. The first situation whose condition is true is sent, else the default.
Only show if…
On an embed or a field. A field whose value comes out empty is left out by itself.
Repeat this field for each…
One field per entry of a list (r in steps.find.rows).
Live message
Post it once, then edit the same message every run (a board that stays up to date).
Ping roles
Roles pinged at the start; never @everyone.
Buttons
Up to 5: Continue this flow (its own wire), run another flow, give / take a role, count, reminders, a link. A role menu, or a menu that continues the flow (one wire per option).
After a button or menu
Everything down a Continue this flow button's wire (or a menu option's) runs as the person who pressed it, and knows the press:
{interaction.user}
who pressed it (also {user} down that wire)
{interaction.message}
the message with the button - {interaction.message.author}, .link…
{button} · {button.label}
the button pressed
{menu.label} · {menu.value}
the menu option picked
In the studio they're under The button press for nodes after such a wire. Example If: interaction.user == interaction.message.author.
Show a list in pages
In a Send a message node, open Show a list in pages: pick the list ({steps.find.rows}, {local.team}…)
and how many per page (up to 25). The message is made once per page - up to 50 pages,
<built-in method items of dict object at 0xffff789a0c40> entries - and gets ◀ ▶ buttons that turn them. In its text and embeds:
{page.items}
the entries on this page - loop over them; each row also has .position (its place in the whole list)
{page.number} · {page.count}
this page · how many pages
{page.total} · {page.from}–{page.to}
entries in all · the places shown on this page
{page.first} · {page.last}
"yes" on the first / last page
Embed
Description:
{each r in page.items}**{r.position}.** {r.member.mention} — {r.points} pts
{end}
Footer:
Page {page.number} of {page.count} · {page.total} players
Dots go inside:{user} is a name, {user.mention} pings them, {user.top_role.name} is their highest role's name.
Empty is fine: a value that isn't there comes out empty. Give it a fallback with {note | default: "none"}.
Unknown names stay as written, so a typo is visible: {usr} is sent as "{usr}". The studio underlines them as you type.
One way to write a value, everywhere:{x} works inside conditions ({if {user} == {reactor.user}}),
after a filter ({local.team | add: {reactor.user}}, | where: item != {user}) and in a loop
({each m in {local.team}}). Writing it without braces there works too. The
buttons and the Placeholders panel always put in {x}.
Times are Discord timestamps: everyone sees them in their own timezone ({message.created.relative} → "3 minutes ago").
You write
Hey {user.mention}! Arbitration is on {arbitration.node} ({arbitration.tier} tier), ends {arbitration.ends}.
In Discord
Hey @Tenno! Arbitration is on Casta (Ceres) (A tier), ends in 42 minutes.
Things and what they have
People, channels, roles, messages, reactions… are things: on their own they show as something sensible (a person as
their name, a channel as #name), and after a dot they have properties - which can be other things:
@Writer (who wrote the message someone reacted to)
Maakun (who the thread belongs to)
Tenno (who the message answers)
Staff
Sirius only asks Discord for what a node actually reads, and only once per run. The full list of things and properties is in
Types; in the studio, type a dot after a value (or press › / →) to see what's inside it.
Where things come from: the trigger ({user}, {message}, {reaction}…), {interaction} (commands and
buttons: .user, .message, .channel, .button), steps that send a message or make a
thread ({steps.send}, {steps.thread}), table rows, typed variables, and inputs of a called flow.
Filters
Add a filter after a |; chain as many as you like, left to right. Some take a setting after a colon (text in quotes,
or a number, or a field name).
leave out repeats (people / channels by who they are)
{local.team | unique | count}
| sum: field
add them up (or one field of each)
{steps.find.rows | sum: points}
| max: field
the biggest (or of one field)
{steps.find.rows | max: points}
| min: field
the smallest (or of one field)
{steps.find.rows | min: points}
| average: field
the average (or of one field)
{steps.find.rows | average: points}
| random
one entry at random
{local.team | random}
Any value
Filter
Does
Example
| default: value
a value to use when it's empty
{target.name | default: "nobody"}
| length
how many characters (text) or entries (a list)
{input | length}
| yesno: "yes word, no word"
true / false -> "Yes" / "No" (or your own words)
{baro.here | yesno: "In town, Away"}
In | where: and | sort:, item means each entry: | where: item.points > 10. Fields of the entry can be written without item. in sort, pluck, sum, max…: | sort: -points.
Lists & loops
Some values are lists: rows a Find rows step found, a list variable, the fissures open now, a message's attachments, a
person's roles. A list on its own shows its entries with commas. Without a loop you can use:
{list.count} · {list | count}
how many entries
{list.first} · {list.last} · {list.2}
one entry (numbers start at 1) - and what's inside it: {list.first.name}
r is any name you choose for the entry. {else} (optional) is shown when the list is empty. Options go
after the list, each after a |:
Option
Does
Example
| where: condition
only the entries it's true for (use the loop's name)
{each r in steps.find.rows | where: r.points > 10}
| sort: field
in order of a field; -field = highest first
{each r in steps.find.rows | sort: -r.points}
| limit: N
only the first N
{each r in steps.find.rows | limit: 15}
| skip: N
leave out the first N
{each r in steps.find.rows | skip: 15 | limit: 15}
| reverse
back to front
{each m in local.team | reverse}
| group: field
one entry per value of a field: .key, .items, .count
{each g in fissures | group: g.tier}{g}: {g.count}{end}
Inside a loop:
{loop.index}
1, 2, 3…
{loop.index0}
0, 1, 2…
{loop.first}
“yes” on the first one
{loop.last}
“yes” on the last one
{loop.count}
how many there are
You write
{each m in local.team}{m.mention}{if not loop.last}, {end}{end}
In Discord
@Maakun, @Tenno, @Writer
Loops can go inside loops ({each d in arbitration.days}{each a in d.arbitrations}…{end}{end}). For many entries,
show them in pages; a loop shows at most 100 entries.
{if} in text
Show part of a text only when a condition is true. {elif} and {else} are optional;
{end} closes it.
repeat for every entry of a list ({else} = when it's empty)
{if condition}…{elif condition}…{else}…{end}
show a part only when something is true
{elif condition}
another condition, inside an {if}
{else}
the other case, inside an {if} or {each}
{end}
closes an {if} or {each}
For whole different messages use situations; to hide an embed or a field, their Only show if….
Typing help
In any box that takes placeholders, the studio helps as you type:
{
lists what you can use here - this node's values, earlier steps, variables, loops you're inside.
{eac · {if
offers {each item in list}…{end} / {if condition}…{end}. Enter puts it in;
Tab jumps between its parts (the name, the list, the inside) - and in the list part only lists are offered.
{user.
what's inside a value; → opens the highlighted one.
|
the filters (or an {each}'s options), with what each takes.
{r. in a loop
what the entry has - a table row's columns, a person's properties…
a warning under the box
an {each} / {if} missing its {end}, a filter that doesn't exist…
The box colours what you type: values in teal (their filters in gold), {if} / {else} / {end} in purple,
{each} in blue, and anything that doesn't add up in red. The inside of a block is shaded. Under the box, an outline shows each
{if} / {each} with its parts - when it's true, otherwise, for each, when the list is empty.
Click a part to put the cursor there (empty ones say so), the condition to edit it, or + otherwise to add an {else}.
Discord formatting
Select text in a message box and a bar offers Discord's formatting: bold**, italic*,
underline __, strikethrough ~~, spoiler ||, code `, a code block, a quote > ,
a heading # , a list - and a link [text](url). Press it again to take it off.
Ctrl+B / I / U work too; Ctrl+Z undoes.
↑↓ move, Enter / Tab choose, Esc closes. The Placeholders panel and the
buttons list everything too.
4 · Reference
Types
Every thing a placeholder can be, and what it has after a dot. On its own, a thing shows as what the second column says.
Times come as ends ("in 2 hours"), ends_time, ends_date and ends_unix (a number, for sorting).
Command parameters
A command trigger can ask questions: who?, which item?. A parameter's name becomes a placeholder
(target → {target}, {target.mention}…). Required ones are asked first. Names can't be built-in
ones (app, arbitration, archon, baro, cetus, channel, circuit, clicker, coda, command, date, event, input, local, now, param, server, sortie, steps, time, timestamp, user, vallis, vars). Every parameter also has
{name.given}: "yes" when they filled it in. People answer in /c's form (up to 5) or in a real
slash command (up to 10).
Text
Any text they type
People enter
A text box.
Settings
Minimum / maximum length, fallback value.
Gives
{p} what they typed{p.length} how many characters{p.given} they filled it in
Whole number
1, 2, 30…
People enter
A text box that only accepts whole numbers.
Settings
Minimum / maximum, fallback value.
Gives
{p} the number{p.given} they filled it in
Number
Decimals allowed: 2.5
People enter
A text box for numbers; 2.5 and 2,5 both work.
Settings
Minimum / maximum, fallback value.
Gives
{p} the number{p.given} they filled it in
Yes / No
A true/false switch
People enter
A Yes / No dropdown (a true/false option as a slash command).
Settings
Fallback value.
Gives
{p} yes or no{p.given} they filled it in
Choice
Pick from your own list
People enter
A dropdown with your options (up to 25).
Settings
Options with a label, an optional value and extra columns; fallback value.
Gives
{p} the chosen label{p.label} the chosen label{p.value} its value{p.given} they filled it in
Long list
Autocomplete from rows you paste in (up to 500)
People enter
A text box; as a slash command it autocompletes from your rows (up to 500).
Settings
Rows (label, optional value, extra columns), Allow any text, fallback value.
Gives
{p} the chosen label{p.label} the chosen label{p.value} its value{p.found} yes if it matched a row{p.given} they filled it in
Empty = themselves (use the person running it when nobody is picked).
Gives
{p} display name{p.name} display name{p.mention} pings them{p.username} @username{p.id} user ID{p.avatar} avatar image URL{p.joined} when they joined the server{p.created} account age{p.color} role colour (#hex){p.top_role} highest role{p.given} they filled it in
{p} #name{p.name} name{p.mention} clickable link{p.id} channel ID{p.given} they filled it in
Warframe item
Any item, with autocomplete
People enter
A text box; as a slash command it autocompletes from every Warframe item.
Settings
Only certain kinds of items (Warframes, Mods…), fallback value.
Gives
{p} item name{p.name} item name{p.link} name linked to the wiki{p.wiki} wiki URL{p.category} Warframe, Mod, Primary…{p.description} short description{p.tradable} Yes / No{p.image} picture (wiki){p.market} warframe.market URL{p.price} lowest sell price{p.price_max} lowest sell, maxed rank{p.buy} top buy price{p.avg} average trade price{p.volume} trades lately{p.ducats} ducat value{p.rarity} rarity{p.max_rank} max rank{p.vaulted} Yes / No{p.given} they filled it in
Star chart node
A mission node, with autocomplete
People enter
A text box; as a slash command it autocompletes from the star chart.
Settings
Fallback value.
Gives
{p} node name{p.name} node name{p.planet} planet{p.tier} arbitration tier{p.tier_emoji} tier emoji{p.link} name linked to arbi.guide{p.tileset} arbi.guide URL{p.mission} mission type{p.faction} enemy faction{p.next} next time it's an arbitration{p.given} they filled it in
{p} file URL{p.url} file URL{p.name} file name{p.size} size in KB{p.image} yes if it's an image{p.given} they filled it in
Choice & Long list rows
A Choice is a dropdown (up to 25); a Long list autocompletes as they type (up to 500).
Each row has a label (what people see), an optional value (a short ID for conditions: boss.value == "nira")
and your own columns ({boss.image}). Paste many at once, one per line: Label | value | column 1 | ….
With Allow any text, anything typed is accepted and {p.found} says whether it matched a row.
Emojis, pictures & pings
{ping.ROLE}
Pings a role, e.g. {ping.arbi_ping} for “Arbi Ping”. Only roles in your own text ping; @everyone never does.
The role must be mentionable, or Sirius needs “Mention all roles”.
{emoji.NAME}
One of the bot's emojis, e.g. {emoji.Sirius_Clock}.
{asset.NAME}
A picture from the bot as a link, for image / thumbnail / icon boxes.
{bot.logo} · {bot.avatar}
Sirius's logo / avatar.
Limits
Discord's limits apply to what's sent; Sirius fits it for you.
Message text
2000 characters (cut with …)
Embeds
10 per message, 6000 characters together; title 256, description 4096
Fields
25 per embed; long values continue in extra fields
Loops
100 entries per loop, 3000 steps per message
Pages
25 per page, 50 pages, <built-in method items of dict object at 0xffff789a0c40> entries
A run
10 messages; Find rows reads up to 500 rows
A flow
60 steps
5 · More
Builds (/build)
The Builds tab of a server keeps its loadouts, built on a board like the game's arsenal:
/build show posts one as a picture, /build compare shows what one adds over another, /build has
says whether a build uses a mod.
To…
Do this
Put a mod in
Drag its card onto a slot, or click a slot and then the card. Enter in the search puts in the top result.
Change the rank
Click its dots, or select it and press − / +.
Forma a slot
Click the polarity in its corner. Auto forma makes it fit with the fewest.
Riven, Helminth, shards
The Riven mod card, a click on an ability, the shard slots under the abilities.
Everything else
Press ? on the page for all the keys. Ctrl+Z undoes.
In Discord
What it does
/build show search:me
A list of your builds to pick from (only you see it). me ogris searches only yours.
/build show search:@someone
Their public builds to pick from; @someone ogris searches only theirs.
/build show search:…
A build's name or ID (on its page). Each suggestion says where it's from: yours, the server's, official, or by whom.
/build list
Your builds (only you see it), or user:@someone's for everyone - pick one to post it.
Your own builds belong to you (public, link-only or private) and can be published to a server.
Flows can use builds with the Show a build step ({steps.build.found}, .mods, .has).
Recipes
A leaderboard from a table, in pages
App → Tables: a table Points with columns member (person) and points (number).
A flow: Commandtop → Find rows (Points, sorted by points, highest first, at most 500) → Send a message.
In the message, Show a list in pages: {steps.find.rows}, 10 per page. Then the embed:
Title: Leaderboard
Description: {each r in page.items}**{r.position}.** {r.member.mention} — {r.points | number} pts
{end}
Footer: Page {page.number} of {page.count} · {page.total} players
To give points: another flow with Find rows (member is {target}) → If {steps.find.count} is more than 0 →
Change rows (points = {steps.find.first.points | plus: 10}), else Add a row (points = 10).
A team per ticket thread
Message sent in your LFG channel → Start a thread (name it thread).
Wire a Reaction trigger after it: Every time, In the thread “thread” made, named reactor.
→ Set a variable: This run, team, Add to a list, value {reactor.reaction.message.author}.
→ Send a message in the thread: Team: {each m in local.team | unique}{m.mention} {end}
Every thread is its own run, so every thread has its own team. To check someone's in it: an If with
{local.team}contains{interaction.user}.
A command that moves another flow ("remote switch")
Variables tab: watch_channel, kind Channel.
Flow 1: Commandwatch with a channel parameter where → Set a variable app watch_channel = {where}.
Flow 2: Message sent, its channels From a value: {app.watch_channel}. It now listens wherever the command pointed it.
Only the person who opened it can press Close
Name the trigger that opens the ticket (say opener). On the Close button's wire, an If matching any of:
{interaction.user}is{opener.user}, or {interaction.user}has the role @Staff.
True → close; False → a private "Only the owner can close this". (After a button, plain {user} is whoever pressed it.)
Something different when nobody is picked
A few words: {if target.given}{target.mention}{else}everyone{end}. A whole other message: a situation with
not target.given.
Ready-made apps
New app → Thread tickets installs a working ticket system (open, close, full, a Logs table), turned off until you look it over.
The command templates on the New command page are complete examples too:
That name doesn't exist here (a typo, a renamed step or parameter, or a value another trigger gives). The studio underlines it and the
preview lists it. If it may be missing on purpose, add | default: "…".
Something inside a thing is empty ({x.author.roles}).
Sirius couldn't see it (the person left the server, the message was deleted, or it lacks access). Check the run log.
My condition is never true.
Open it in the builder: each row should read as a sentence. In Write it instead, names go without braces
(target.given); a value in quotes with braces is filled in ("{reactor.user}").
A listener reacts to things in other threads, or twice.
It listens Here - the channel the run started in and every thread under it. Make it listen in the thread a step made.
A loop shows nothing.
The list is empty (add {else}… to say so), or it isn't a list - a box's warning says if a tag is wrong.
Publish fails, or the flow won't open.
The problems are listed at the top. If the studio can't open a draft, it offers to discard the unpublished changes; the live version is safe.
Restarting Sirius…This takes a few seconds. The page reloads by itself.