AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpkZGRlNzk4MS1hOGFjLTQ1ZmYtOGNhNi1mNDczNzgxMjY0OTUAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaPFNtqG82i4uN4eOvVM6Fm8AAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDpmMmUzZjBhZi0yNDE4LTQ0NTctODgxNi0zMjY2MDcwZDJjNThscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNoN3/yiGt7PeD8LOSfISbLawAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggtUGV157hDM+yROCZskY0x2hgnnZ/9nRjvfr+o42EmJKkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaKyUjqZVrqKsis/JDLrSUNAAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCDGQrsVcKhUywTIbZfzYcsX5FV6PJ9/rJacxoshwWTEEGRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBh8Zmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmRkZGU3OTgxLWE4YWMtNDVmZi04Y2E2LWY0NzM3ODEyNjQ5NS9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjk2YWZkZTNlLTNkYmYtNDdmNi1hNmU3LTg3MTYzOWRmZGRkOHJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCC1QZXXnuEMz7JE4JmyRjTHaGCedn/2dGO9+v6jjYSYkqJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggmJ/cjNqq2u1j9Eaocf5fTLqyH/LmCCqn0dbfEQ9iLNOiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFgggLd3qq1YrGgOqG8cxru+colixwxrNRBylPRnIp2iNmV0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQEHHwG0XTlJLMin1mUu0WCM12XGEBLChkQtWihqUJD+34dr4W9Kf6GP4xNo25qe7E1Ly4neKrd5WuK2wuGJKD+Y=
Sirius
Sirius apps

Documentation

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

  1. Server → Apps → New app. The studio opens on an empty flow. (Or pick a ready-made app, like Thread tickets.)
  2. Triggers tab → Command. Drop it on the canvas and name it, e.g. hello.
  3. 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).
  4. Write the message. Type { to pick a placeholder: Hello {user.mention}!
  5. Check the live preview (top left): it's made by the bot with the same code it uses in Discord.
  6. Publish. Until then it's a draft (autosaved). Then run /c name:hello in Discord.

The studio

LeftTriggers, Nodes and Variables tabs. Drag onto the canvas, or click to add after the selected node.
CanvasDrag 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.
RightThe selected node's Settings, a Test run, the app's Data, and History (the last published versions; restore any).
TopThe 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.

TriggerStarts whenGives
CommandSomeone runs /c name, or its own slash command {interaction} Interaction
Message sentSomeone sends a message (in some channels, with some text or files) {message} Message
ScheduleAt set times: daily, some weekdays, or every N minutes {run.count}, {run.next}, {run.timezone}
Button or menuSomeone presses a function's button (or picks a menu option) {clicker} Person
{button} Button / menu
{interaction} Interaction
MemberSomeone joins, leaves, gets or loses a role {member} Person
{role} Role
ReactionSomeone adds or removes a reaction (reaction roles, polls…) {reaction} Reaction
Warframe eventWhen something changes in the game (new sortie, Baro, a fissure…) {event}
ThreadA thread is made, closed, opened again, deleted, or someone joins one {thread} Channel / thread
Message changedA message is edited or deleted, or pins change {message} Message
VoiceSomeone joins, leaves or moves between voice channels {voice} Voice change
Called by a flowAnother 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

NodeWhat it doesGives
Send a messageText 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 embedSirius'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 rivenRead 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 messageDelete the triggering message or one an earlier step sent, now or after a delay {steps.name.deleted} “yes”, or “later” with a delay
ReactionAdd or take off a reaction on a message (the triggering one, one a step sent, or one from a value) —
Give a roleGive 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 roleTake 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 messagePin or unpin a message: theirs, one a step sent, or one from a value —
Start a threadOn 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 / nicknameKick, 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 messageChange 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 messagesDelete the last 1-1000 messages in a channel (all, theirs, bots', or with some text) {steps.name.count} how many were deleted
Create a channelA 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 channelDelete this channel or one an earlier step made - only ones Sirius made (close a ticket) —
Change channel permissionsAllow / deny seeing, typing, reacting… for them, some roles or everyone —
Time outTime out someone - who started it or a person from a value (or remove their timeout) —
Show a buildPost 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 threadRename, 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 messageAfter a button press: turn its buttons off, change its text, answer only them —

Flow

NodeWhat it doesGives
StopEnd the run here (give it an “only if” to stop only sometimes) —
Show a formAsk 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>
WaitPause before the next step - seconds or days (long waits carry on after a restart) {steps.name.until} when it carries on
Wait for an answerWait 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 flowRun 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 eventStart every flow of this app that listens to this event (they run on their own) {steps.name.started} how many flows it started

Data

NodeWhat it doesGives
Set a variableRemember 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 variableRead 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 rowAdd 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 rowsLook 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 rowsChange 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 rowsDelete the rows that match {steps.name.count} how many rows were deleted
MathWork out a sum: + - * / % and round, min, max, abs {steps.name.result} the answer
TextReplace, 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
ListCount, sort, filter, join or pick from a list {steps.name.result} the answer
{steps.name.list} the new list
{steps.name.count} how many
TimeNow, 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
RandomA random number, or one thing from a list {steps.name.result} what came out

Logic

IfGoes down True or False - see Conditions.
SwitchLooks 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 · RepeatRuns 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 timeLets 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 rolesOnly people with (or without) some roles
In channelsOnly in (or not in) some channels or categories
CooldownWait between uses, per person, channel or server
Discord permissionOnly people with a Discord permission
ConditionAny condition over the data, like arbitration.tier in "S,A"
Message hasOnly messages with an image, a file, a link or some text
Max runsAt most N runs per person, channel or server in some hours
Only at some timesOnly on some days and/or between two times (the server's timezone)
Account / member ageOnly accounts (or members) at least N days old - stops fresh alts
In a threadOnly inside a thread: any, one under some channels, or one this app made
Is the thread's creatorOnly 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.

WhereOn 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.
ByAnyone, or only the person the run is about.
OnceThe first match carries the run on (When it happens); nothing in time goes down No match in time.
Every timeA 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.

WriteMeansExample
==isuser.name == "Maakun"
!=is notr.status != "closed"
>more thanamount > 5
<less thanamount < 5
>=at leastcount >= 3
<=at mostcount <= 3
containstext has it / a list has itlocal.team contains reactor.user
inis one oftier in "S,A"
not inisn't one ofuser not in local.team
startswithtext starts withinput startswith "!"
endswithtext ends withinput endswith "?"
matchesmatches a pattern (regex)input matches "^\d+$"
has_rolea person has a role (name, mention or id)user has_role "Staff"
and / or / notcombine; ( ) to grouptarget.given and not user.bot
a name aloneit 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 asLives
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.
The server{vars.counter}Until changed; every app of the server sees it.
On a thing{steps.thread.data.team} · {user.data.points} · {channel.data.owner} 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.

SituationsWhole 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 messagePost it once, then edit the same message every run (a board that stays up to date).
Ping rolesRoles pinged at the start; never @everyone.
ButtonsUp 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
In Discord
1. @Maakun — 120 pts 2. @Tenno — 95 pts … Page 1 of 4 · 52 players ◀ 1 / 4 ▶
3 · Placeholders & templates

The basics

Anything in braces is filled in when the flow runs. There are three kinds of tags:

{value}a value: {user}, {steps.find.count}, {app.counter}
{value | filter}a value, changed by filters: {user | upper}, {rows | count}
{each …} {if …} {else} {end}repeat or show only sometimes
  • 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:

You write
{reactor.reaction.message.author.mention}
{steps.thread.owner.display_name}
{message.reply_to.author}
{interaction.message.author.top_role.name}
In Discord
@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).

You write
{user | upper}
{steps.find.rows | where: item.status == "open" | count} open
{steps.find.rows | sort: -points | limit: 3 | pluck: member | join: ", "}
{points | number} pts · {opened | time: relative}
In Discord
TENNO 4 open @Maakun, @Tenno, @Writer 12,500 pts · 2 hours ago

Text

FilterDoesExample
| upperUPPERCASE{user | upper}
| lowerlowercase{user | lower}
| titleTitle Case{item | title}
| capitalizeFirst letter capital{reason | capitalize}
| trimremove spaces around it{input | trim}
| truncate: Ncut to N characters (…){message | truncate: 50}
| replace: "old", "new"swap text for other text{reason | replace: "_", " "}
| split: ","text -> a list, cut at a separator{input | split: ","}
| urlmake it safe inside a linkhttps://x.com/?q={input | url}
| wikiname -> [name](its wiki page){item | wiki}
| link: addressmake it a link to an address{item | link: item.wiki}
| codewrap in `code`{input | code}
| boldwrap in **bold**{user | bold}

Numbers

FilterDoesExample
| plural: "one, many"3 -> "3 items"{count | plural: "ticket, tickets"}
| round: Nround to N decimals{price | round: 1}
| number: N1234567 -> 1,234,567 (or with N decimals){points | number}
| plus: Nadd{loop.index | plus: 10}
| minus: Nsubtract{count | minus: 1}
| times: Nmultiply{price | times: 2}
| divided_by: Ndivide{points | divided_by: 10}

Times

FilterDoesExample
| time: stylea time (seconds or a Discord time) shown as relative / date / time / full / short / long{r.opened | time: relative}

Lists

FilterDoesExample
| counthow many entries{local.team | count}
| firstthe first entry{steps.find.rows | first}
| lastthe last entry{steps.find.rows | last}
| join: ", "entries as text, with a separator ("\n" = new lines){local.team | join: ", "}
| sort: fieldsort by a field (-field: highest first){steps.find.rows | sort: -points}
| reverseback to front{list | reverse}
| limit: Nonly the first N{steps.find.rows | sort: -points | limit: 3}
| skip: Nleave out the first N{list | skip: 1}
| where: conditionkeep the entries a condition is true for (item = each entry){steps.find.rows | where: item.status == "open" | count}
| pluck: fieldone field of every entry{steps.find.rows | pluck: member | join: ", "}
| add: valuethe list with one more entry (not if it's already in it){steps.find.first.teammates | add: reactor.user}
| remove: valuethe list without an entry (people by who they are){steps.find.first.teammates | remove: reactor.user}
| uniqueleave out repeats (people / channels by who they are){local.team | unique | count}
| sum: fieldadd them up (or one field of each){steps.find.rows | sum: points}
| max: fieldthe biggest (or of one field){steps.find.rows | max: points}
| min: fieldthe smallest (or of one field){steps.find.rows | min: points}
| average: fieldthe average (or of one field){steps.find.rows | average: points}
| randomone entry at random{local.team | random}

Any value

FilterDoesExample
| default: valuea value to use when it's empty{target.name | default: "nobody"}
| lengthhow 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}
{list | join: "\n"}all of it as text, one per line
the list filterssort, where, limit, pluck, sum, max, unique, random…

{each}: write it once, repeated for every entry

You write
{each r in steps.find.rows | sort: -r.points | limit: 10}
{loop.index}. {r.member.mention} — {r.points} pts
{else}
Nobody yet.
{end}
In Discord
1. @Maakun — 120 pts 2. @Tenno — 95 pts 3. @Writer — 40 pts

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 |:

OptionDoesExample
| where: conditiononly the entries it's true for (use the loop's name){each r in steps.find.rows | where: r.points > 10}
| sort: fieldin order of a field; -field = highest first{each r in steps.find.rows | sort: -r.points}
| limit: Nonly the first N{each r in steps.find.rows | limit: 15}
| skip: Nleave out the first N{each r in steps.find.rows | skip: 15 | limit: 15}
| reverseback to front{each m in local.team | reverse}
| group: fieldone 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.

You write
{if target.given}Hey {target.mention}!{else}Hey everyone!{end}
{if amount > 10}a lot{elif amount > 1}a few{else}just one{end}
{if user has_role "Staff"}(staff){end}
In Discord
Hey everyone! a few (staff)
TagUse
{each item in list}…{else}…{end}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 · {ifoffers {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 loopwhat the entry has - a table row's columns, a person's properties…
a warning under the boxan {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.

Person

shows as their display name
.display_nametextTheir name here (nickname, else display name)
.usernametextTheir @username
.mentiontextPings them
.idIDTheir user ID
.avatarlinkTheir avatar (image URL)
.linklinkA link to their profile
.botyes / no“yes” for bots
.createdTimeWhen the account was made
.joinedTimeWhen they joined this server
.roleslist of rolesTheir roles (highest first)
.top_roleRoleTheir highest role
.colortextTheir name colour (#hex)
.boostingTimeSince when they boost (empty if not)
.timed_outTimeUntil when they're timed out (empty if not)

Channel / thread

shows as its name
.nametextIts name
.mentiontextA clickable #channel
.idIDIts ID
.linklinkA link to it
.typetexttext / voice / thread / forum / category / announcement…
.topictextIts topic
.categoryChannel / threadThe category it's in
.parentChannel / threadThreads: the channel / forum it's in
.ownerPersonThreads: who made it
.createdTimeWhen it was made
.is_threadyes / no“yes” for threads
.archivedyes / noThreads: “yes” if closed
.lockedyes / noThreads: “yes” if locked
.privateyes / noThreads: “yes” if private
.tagstextForum posts: its tags
.nsfwyes / no“yes” if age-restricted
.slowmodenumberSlowmode (seconds)
.member_countnumberThreads / voice: how many people are in it

Role

shows as its name
.nametextIts name
.mentiontextPings the role
.idIDIts ID
.colortextIts colour (#hex)
.positionnumberIts place in the list (higher = more powerful)
.member_countnumberHow many people have it
.memberslist of peopleWho has it (first 50)
.mentionableyes / no“yes” if anyone can ping it
.hoistedyes / no“yes” if shown apart in the list

Message

shows as its text
.contenttextIts text
.idIDIts ID
.linklinkA link to it
.authorPersonWho sent it
.channelChannel / threadWhere it is
.createdTimeWhen it was sent
.editedTimeWhen it was last edited (empty if never)
.reply_toMessageThe message it answers
.threadChannel / threadThe thread started from it
.attachmentslist of filesIts files
.imagelinkThe first picture's URL
.first_linklinkThe first link in its text
.wordsnumberHow many words it has
.mentionslist of peopleThe people it pings
.reactionslist of reactionsIts reactions
.embedsnumberHow many embeds it has
.pinnedyes / no“yes” if pinned

Reaction

shows as the emoji
.emojiEmojiThe emoji
.messageMessageThe message it's on
.userPersonWho reacted
.countnumberHow many people reacted with it
.actiontext“added” or “removed”

Emoji

shows as the emoji itself
.nametextIts name
.idIDIts ID (custom emojis)
.customyes / no“yes” for server emojis
.imagelinkIts picture (custom emojis)

File

shows as its name
.nametextThe file name
.urllinkWhere it is
.sizenumberIts size (KB)
.typetextIts kind (image/png…)
.imageyes / no“yes” for pictures
.widthnumberPictures: width
.heightnumberPictures: height

Server

shows as its name
.nametextIts name
.idIDIts ID
.iconlinkIts icon (image URL)
.member_countnumberHow many members it has
.ownerPersonWho owns it
.createdTimeWhen it was made
.boostsnumberHow many boosts it has

Time

shows as date and time (each person sees their own timezone)
.relativetext“in 2 hours” / “3 days ago”
.datetextThe date
.timetextThe time of day
.unixnumberSeconds since 1970 (for maths and Time nodes)

Button / menu

shows as its label
.labeltextIts label (or the option picked)
.valuetextThe menu option's value
.messageMessageThe message it's on

Voice change

shows as the channel's name
.channelChannel / threadThe voice channel they joined (or left)
.fromChannel / threadMoved: where they came from
.toChannel / threadMoved: where they went
.eventtextjoined / left / moved

Interaction

shows as who did it
.userPersonWho ran the command / pressed the button / picked the option
.channelChannel / threadWhere
.messageMessageThe message with the button or menu (buttons and menus)
.buttonButton / menuThe button or menu option

A table row has .id, .position (in pages) and one property per column. A list has .count, .first, .last and .1, .2…

What each trigger gives

On top of {user}, {channel} and {server}:

Command

{interaction}Interaction

Message sent

{message}Message
{message.author}Who sent it
{message.author.mention}Pings who sent it
{message.link}A link to the message
{message.words}How many words it has
{message.first_link}The first link in it
{message.image}The first picture's URL
{message.attachments.count}How many files it has
{message.reply_to}The message it answers (empty if none)

Schedule

{run.count}How many times it has run (1 the first time)
{run.next}When it runs next
{run.timezone}The timezone it uses

Button or menu

{clicker}Person
{button}Button / menu
{interaction}Interaction
{clicker.mention}Pings them
{button.value}The menu option's value
{button.message}The message with the button

Member

{member}Person
{role}Role
{member.mention}Pings them
{member.joined}When they joined
{member.created}When their account was made
{member.account_age}How old their account is (days)
{role.mention}Mentions the role

Reaction

{reaction}Reaction
{reaction.emoji}The emoji
{reaction.message}The message it's on
{reaction.message.author}Who wrote that message
{reaction.message.link}A link to it
{reaction.count}How many reacted with it
{reaction.action}“added” or “removed”
{user}Who reacted
{user.mention}Pings them

Warframe event

{event}The thing that happened (see the event's own list)

Thread

{thread}Channel / thread
{thread.mention}A clickable #thread
{thread.link}A link to it
{thread.parent}The channel / forum it's in
{thread.owner}Who made it
{thread.owner.mention}Pings who made it
{thread.tags}Its forum tags
{thread.private}“yes” if it's private
{user}Who made it (or who joined)
{user.mention}Pings them

Message changed

{message}Message
{message.author}Who wrote it
{message.link}A link to it
{before}Edited: what it said before
{event.pinned}Pins: how many pinned messages there are
{channel}The channel

Voice

{voice}Voice change
{voice.channel}The voice channel they joined (or left)
{voice.channel.mention}That channel, clickable
{voice.from}Moved: where they came from
{voice.to}Moved: where they went
{voice.event}joined / left / moved
{user}Who it was
{user.mention}Pings them

Called by a flow

{call.<name>}A value the calling flow passed
{call.from}The name of the flow that called it

Everyday placeholders

Always there, in every flow.

You & the server

{user}Their display name
{user.mention}Pings them
{user.username}Their @username
{user.id}Their user ID
{user.avatar}Their avatar (image URL)
{server}Server name
{server.members}Member count
{server.icon}Server icon (image URL)
{server.id}Server ID
{channel}Channel name
{channel.mention}Channel link
{channel.name}Channel name (without #)
{channel.id}Channel ID
{input}Text they typed after the command

Time

{date}Today's date (their timezone)
{time}Current time (their timezone)
{now}Date and time
{timestamp}Unix timestamp

Arbitration

{arbitration.node}Current node
{arbitration.mission}Mission type
{arbitration.faction}Enemy faction
{arbitration.tier}Tier letter (S-F)
{arbitration.tier_emoji}Tier emoji
{arbitration.ends}When it ends
{arbitration.link}arbi.guide tileset page
{arbitration.next}Next hour's node

Sortie

{sortie.boss}Sortie boss
{sortie.faction}Sortie faction
{sortie.ends}When it resets

Archon Hunt

{archon.boss}This week's Archon
{archon.shard}Shard colour it drops
{archon.next}Next week's Archon
{archon.ends}When it resets

Baro Ki'Teer

{baro.status}In town / away
{baro.location}Relay he's at (or going to)
{baro.arrives}When he arrives
{baro.leaves}When he leaves

Cycles

{cetus.state}Day / Night on the Plains
{cetus.ends}When it changes
{vallis.state}Warm / Cold in Orb Vallis
{vallis.ends}When it changes

Circuit & Coda

{circuit.warframes}This week's Circuit Warframes
{circuit.incarnons}This week's Incarnon adapters
{coda.batch}Eleanor's Coda batch (A/B)
{coda.weapons}Coda weapons on sale
{coda.ends}When the batch switches

Warframe data

Updated live. list means several entries: use it with {each}; the last column is what each entry has.

Arbitration

NameWhat it isEach entry has
arbitration.upcoming listnext 24 hoursnode, mission, faction, tier, tier_rank, tier_emoji, link, tileset, day, date, starts, starts_time, ends
arbitration.days listnext 7 days (each has .arbitrations)day, date, arbitrations
arbitration.tier_rankS=6 A=5 B=4 C=3 D=2 F=1—

Sortie

NameWhat it isEach entry has
sortie.activeyes while there's a sortie—
sortie.missions listthe 3 missionsnode, type, modifier, modifier_info, number

Archon Hunt

NameWhat it isEach entry has
archon.missions listthe 3 missionsnode, type
archon.shard_emojishard emoji—
archon.imageshard picture (URL)—
archon.colorshard colour (#hex)—

Fissures

NameWhat it isEach entry has
fissures listactive fissures, sorted by tiernode, mission, tier, tier_num, enemy, steel_path, storm, ends, ends_time, ends_date, ends_unix

Invasions

NameWhat it isEach entry has
invasions listactive invasionsnode, desc, attacker, defender, attacker_reward, defender_reward, progress

Nightwave

NameWhat it isEach entry has
nightwaveseason name—
nightwave.challenges listthis week's challengestitle, desc, standing, kind, daily, weekly, elite, ends, ends_time, ends_date, ends_unix

Bounties

NameWhat it isEach entry has
bounties listone entry per syndicatesyndicate, jobs (type, levels, rewards, standing, vault), ends, ends_time, ends_date, ends_unix

Baro Ki'Teer

NameWhat it isEach entry has
baro.hereyes while he's in a relay—
baro.inventory listwhat he sells (while here)item, ducats, credits

Steel Path

NameWhat it isEach entry has
steelpathTeshin's current reward—
steelpath.rotation listTeshin's full rotationname, cost

Cycles

NameWhat it isEach entry has
cambion.stateFass / Vome—
zariman.stateGrineer / Corpus—
earth.stateDay / Night—
duviri.stateDuviri spiral mood—

Circuit & Coda

NameWhat it isEach entry has
circuit.warframes listthis week's Warframes (list)name, link
circuit.incarnons listthis week's Incarnons (list)name, link
circuit.warframe_weeks listevery week of the rotationweek, current, choices, starts
circuit.incarnon_weeks listevery week of the rotationweek, current, choices, starts
coda.weapons liston sale now (list)name, link
coda.next_weapons listnext batch (list)name, link

Sirius

NameWhat it isEach entry has
bot.logoSirius logo (URL)—
bot.avatarthe bot's avatar (URL)—
bot.mentionpings the bot—
asset.NAMEa picture from the bot's assets folder—
emoji.NAMEone of the bot's emojis, e.g. emoji.Sirius_Clock—

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

User

A member of the server · a Person
People enter
A member picker.
Settings
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

Role

A server role · a Role
People enter
A role picker.
Settings
—
Gives
{p} role name{p.name} role name{p.mention} pings the role*{p.id} role ID{p.color} colour (#hex){p.members} member count{p.given} they filled it in

Channel

A server channel · a Channel / thread
People enter
A channel picker.
Settings
—
Gives
{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

File

An uploaded file or image · a File
People enter
A file upload.
Settings
Images only.
Gives
{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 text2000 characters (cut with …)
Embeds10 per message, 6000 characters together; title 256, description 4096
Fields25 per embed; long values continue in extra fields
Loops100 entries per loop, 3000 steps per message
Pages25 per page, 50 pages, <built-in method items of dict object at 0xffff789a0c40> entries
A run10 messages; Find rows reads up to 500 rows
A flow60 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 inDrag its card onto a slot, or click a slot and then the card. Enter in the search puts in the top result.
Change the rankClick its dots, or select it and press − / +.
Forma a slotClick the polarity in its corner. Auto forma makes it fit with the fewest.
Riven, Helminth, shardsThe Riven mod card, a click on an ability, the shard slots under the abilities.
Everything elsePress ? on the page for all the keys. Ctrl+Z undoes.
In DiscordWhat it does
/build show search:meA list of your builds to pick from (only you see it). me ogris searches only yours.
/build show search:@someoneTheir 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 listYour 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

  1. App → Tables: a table Points with columns member (person) and points (number).
  2. A flow: Command top → Find rows (Points, sorted by points, highest first, at most 500) → Send a message.
  3. 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

  1. Message sent in your LFG channel → Start a thread (name it thread).
  2. Wire a Reaction trigger after it: Every time, In the thread “thread” made, named reactor.
  3. → Set a variable: This run, team, Add to a list, value {reactor.reaction.message.author}.
  4. → 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")

  1. Variables tab: watch_channel, kind Channel.
  2. Flow 1: Command watch with a channel parameter where → Set a variable app watch_channel = {where}.
  3. 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:

SortieVoid fissuresSteel Path fissuresArbitrationGood arbitrations this weekArchon HuntBaro Ki'TeerNightwaveInvasionsBountiesOpen worldsThe CircuitCoda weaponsPrice checkHug someone

Troubleshooting

A placeholder shows up as {something} in Discord.
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.