Skip to content

Command scripts

Use scripts when command variables are not enough.

Scripts can:

  • Send chat messages with ctx.sendMessage.
  • Reply to the source chat message by passing ctx.input.provider_message_id as the reply message ID.
  • Call public HTTP APIs with fetch.
  • Store values with ctx.kv.
  1. Open the Synchra Dashboard.
  2. Go to Commands.
  3. Create or edit a command.
  4. Add command triggers, phrase triggers, or activity triggers.
  5. Set the action to Custom script.
  6. Write the script.
  7. Test and save.

The script runs only after one of the command triggers matches.

Trigger typeWhat happens
Command triggerA chat message starts with a matching !command.
Phrase triggerA normal chat message matches one of the command’s phrase triggers.
Activity triggerA matching activity is created for the command.
export async function run(ctx: ScriptContext) {
  await ctx.sendMessage({
    message: `Hi ${ctx.input.viewer_display_name}.`,
  })
}
ValueMeaning
ctx.triggerchat_message or activity.
ctx.commandMatch data with exactly trigger and args.
ctx.command.triggerThe matched command trigger, pattern, or activity.
ctx.command.argsCommand arguments. Empty for phrase triggers.
ctx.inputChannel, provider, viewer, message text, message parts, timestamps, IDs, and access level.
ctx.activityActivity fields for activity runs. null for chat runs.
ctx.modetest or live.
MethodMeaning
ctx.kv.getRead a value. Missing and expired values return null.
ctx.kv.setStore or replace a JSON-compatible value.
ctx.kv.deleteRemove a value.
ctx.kv.incIncrement an integer value and return the new number.

KV values must be JSON-compatible: strings, numbers, booleans, null, arrays, and plain objects. Keys may contain letters, numbers, :, _, ., /, and -. TTL values are in seconds.

Setup: command trigger guess.

Chat guesses a number from 1 to 10. The answer stays the same until someone wins.

export async function run(ctx: ScriptContext) {
  const key = "game:guess-number"
  const guess = Number(ctx.command.args[0])
  if (!Number.isInteger(guess) || guess < 1 || guess > 10) {
    await ctx.sendMessage({
      message: "Guess a number from 1 to 10. Example: !guess 7",
      reply_message_id: ctx.input.provider_message_id,
    })
    return
  }

  const saved = await ctx.kv.get({ key })
  const answer = !!saved ? saved
    : Math.floor(Math.random() * 10) + 1

  if (!saved) {
    await ctx.kv.set({ key, value: answer, ttl: 15 * 60 })
  }

  if (guess !== answer) {
    await ctx.sendMessage({
      message: `${ctx.input.viewer_display_name} guessed ${guess}. Not it.`,
      reply_message_id: ctx.input.provider_message_id,
    })
    return
  }

  await ctx.kv.delete({ key })
  await ctx.sendMessage({
    message:
      `${ctx.input.viewer_display_name} got it. The number was ${answer}.`,
  })
}

Setup: activity trigger for subs, resubs, gift subs, donations, or Super Chats. Each matching activity gets a 10% chance to win. See activity fields for the values on ctx.activity.

export async function run(ctx: ScriptContext) {
  const activity = ctx.activity
  if (!activity) return

  const roll = Math.floor(Math.random() * 100) + 1
  if (roll > 10) {
    await ctx.sendMessage({
      message:
        `${activity.viewer_display_name} rolled ${roll}. No prize this time.`,
    })
    return
  }

  await ctx.sendMessage({
    message:
      `${activity.viewer_display_name} won the prize roll. Pick the next community night game.`,
  })
}

Setup: activity trigger for the Daily check-in redeem.

Track a per-user streak. If the user waits too long, their count starts over.

export async function run(ctx: ScriptContext) {
  if (!ctx.activity) return

  const key =
    `daily-check-in:${ctx.activity.provider}:${ctx.activity.provider_viewer_id}`

  const count = await ctx.kv.inc({
    key,
    amount: 1,
    ttl: 7 * 24 * 60 * 60, // 7 days
  })

  await ctx.sendMessage({
    message: `${ctx.activity.viewer_display_name} is on a ${count}-day check-in streak.`,
  })
}

Setup: command trigger challenge.

Fetch a challenge from your own site or API.

export async function run(ctx: ScriptContext) {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1")

  if (!response.ok) return

  const data = await response.json()
  await ctx.sendMessage({
    message: `User: ${data.name}`,
  })
}