Aan de slag

KirzenScript ontleent zijn syntaxis aan JavaScript en aan Python, en aanvaardt beide. Welke van de twee je al schrijft, die kun je hier schrijven zonder iets op te zoeken.

Het is echter geen JavaScript, en dat verschil telt. Kirzen leest je script en voert het zelf uit in plaats van het door te geven aan de engine eronder. Daarom komt een script niet bij het bestandssysteem, het netwerk, het bottoken of de database, en daarom kan een fout in één opdracht de bot nooit platleggen.

const name = user.nick

if (args.length > 0) {
  reply(`Hi ${name}, you said ${input}`)
} else {
  reply(`Hi ${name}!`)
}

Scripts schrijven in Servers → je server → Opdrachten → Nieuwe opdracht → Een script. Een eigen opdracht wordt afgevuurd door wat iemand typt, dus geef je hem een voorvoegsel zoals !roll.

Wat de editor voor je doet

  • Acht kant-en-klare opdrachten. Begin met een dobbelworp, een economie, een profielkaart of een ranglijst en pas die aan, in plaats van bij nul te beginnen.
  • Hij controleert terwijl je typt. Even nadat je stopt met typen, wordt de foute regel in de kantlijn gemarkeerd en staat de reden eronder uitgeschreven.
  • Probeer het. Draait het script met verzonnen waarden en toont wat er gezegd en gedaan zou worden. Er wordt niets verstuurd en niets bewaard.
  • Hij gedraagt zich als een code-editor. Tab springt in, haakjes en aanhalingstekens sluiten zichzelf, en Enter houdt je plaats vast.

Twee manieren om het te schrijven

Deze twee opdrachten zijn dezelfde opdracht. Accolades zijn de standaard en wat de rest van deze pagina gebruikt, maar een dubbele punt met inspringing doet hetzelfde waar jij dat liever hebt. De schrijfwijze wordt per blok bepaald, dus je mag ze vrij mengen.

JavaScript
const best = args.filter(a => a.length > 3);

if (best.length > 0) {
  reply(best.join(", "));
} else {
  reply("Nothing long enough.");
}
Python
best = args.filter(a => len(a) > 3)

if len(best) > 0:
    reply(best.join(", "))
else:
    reply("Nothing long enough.")

Wat er uit Python wordt overgenomen

SchrijvenBetekent
and, or, not&&, ||, !
True, False, Nonetrue, false, null
elifelse if
def name():function name() {}
for x in list:for (const x of list) {}
# comment// comment
len(x)x.length
str(x), int(x)String(x), parseInt(x)

Wat je mag weglaten

  • Puntkomma's. In geen van beide schrijfwijzen nodig.
  • Haakjes om een voorwaarde. if x > 0 en if (x > 0) zijn hetzelfde.
  • let en const. Toekennen aan een nieuwe naam maakt hem aan. Gebruik const wanneer je de naam wilt vastzetten.
  • Accolades. Een dubbele punt en een inspringing doen hetzelfde.

De enige regel bij dubbele punten: de regels in een blok moeten verder ingesprongen staan dan de regel die het opende. Kirzen zegt het onomwonden als dat niet zo is.

Wat een script weet

Zes waarden staan voor je klaar. Het zijn kopieën, dus er een wijzigen verandert niets op Discord.

WaardeWat het bevat
user id, name, nick, mention, avatar, joinedAt, createdAt, roles, topRole, isBooster
user.roles Een lijst, de hoogste eerst. Elk heeft id, name, color, mention. topRole is de eerste daarvan.
server id, name, members, icon, boosts, createdAt, roleCount, channelCount
channel id, name, mention, topic, isNsfw
args Wat er na de opdracht kwam, gesplitst op spaties
input Hetzelfde als één tekst
command De naam van de opdracht die draait
reply(`${user.name} is in ${server.name}, which has ${server.members} members.`)

if (hasRole("123456789012345678")) {
  reply(" And they are a moderator.")
}

joinedAt en createdAt zijn seconden, klaar om door te geven aan timestamp().

Antwoordt

reply() bouwt het antwoord van de opdracht. Roep het zo vaak aan als je wilt; de stukken worden aan elkaar geplakt.

reply("Hello ");
reply(user.name);
// The command answers "Hello Terra"

send() is anders: het plaatst een apart bericht in het kanaal. Gebruik het als je een tweede bericht wilt in plaats van een langer eerste. dm() stuurt privé naar degene die de opdracht uitvoerde.

Embeds en kleur

embed() hangt een embed aan het antwoord van de opdracht. Roep het tot drie keer aan voor drie embeds, en gebruik het samen met reply() als je er tekst boven wilt.

embed({
  title: `Profile of ${user.nick}`,
  description: "Everything Kirzen knows about you.",
  color: user.topRole.color,
  thumbnail: user.avatar,
  fields: [
    { name: "Top role", value: user.topRole.mention, inline: true },
    { name: "Joined", value: timestamp(user.joinedAt, "R"), inline: true },
    { name: "Roles", value: str(len(user.roles)), inline: true }
  ],
  footer: { text: server.name },
  timestamp: true
})

Elk onderdeel van een embed

SleutelWat het vraagt
titleTekst, tot 256 tekens
descriptionTekst, tot 4096
colorEen naam, een "#rrggbb", of een getal
urlMaakt van de titel een link
thumbnailEen afbeeldings-URL, klein in de hoek getoond
imageEen afbeeldings-URL, over de volle breedte getoond
authorEen naam, of { name, icon, url }
footerTekst, of { text, icon }
timestamptrue om er de huidige tijd op te zetten
fieldsTot 25 van { name, value, inline }

Kleuren met naam

blurple green yellow red fuchsia white black grey gold orange aqua purple pink navy dark

embed({ title: "Warning", color: "red" })
embed({ title: "Done", color: "#57f287" })
embed({ title: "Match", color: user.topRole.color })

Elke URL wordt vóór gebruik gecontroleerd. Alles wat geen http of https is valt af, zodat een script niets vreemds een bericht in kan smokkelen.

Er een ergens anders heen sturen

sendEmbed({ title: "Logged", description: input }, "123456789012345678")
sendTo("123456789012345678", "A plain message, in another channel.")

Laat het kanaal weg, dan gaat het naar het kanaal waarin de opdracht is gebruikt. Kan Kirzen niet posten in het kanaal dat je noemt, dan wordt het versturen geweigerd en zegt de opdracht dat, in plaats van ergens anders te posten.

Dingen doen

Een script raakt Discord zelf nooit aan. Het legt vast wat het gedaan zou willen hebben, en Kirzen toetst elk verzoek aan de werkelijke rechten voordat er iets wordt uitgevoerd. Kan Kirzen een rol niet beheren, dan wordt het script niets voorgespiegeld: de actie wordt simpelweg geweigerd en gemeld.

AanroepWaar hij om vraagt
addRole(id, who?)Geeft een rol. Laat het tweede deel weg en het slaat op wie hem uitvoerde
removeRole(id, who?)Een rol afnemen
setNickname(name, who?)Iemand hernoemen. Leeg wist de bijnaam
timeout(secs, who?, why?)Iemand een time-out geven. Nul seconden heft hem op
react(emoji)Reageren op het bericht dat het afvuurde
deleteMessage()Dat bericht verwijderen
pin()Dat bericht vastzetten
createThread(name)Er een thread over starten
send(text)Een apart bericht plaatsen
sendTo(id, text)In een ander kanaal plaatsen
sendEmbed(obj)Een embed als eigen bericht plaatsen
dm(text)Een privébericht sturen

react(), pin(), deleteMessage() en createThread() werken op het bericht dat iemand typte om de opdracht af te vuren.

Kicken en verbannen ontbreken met opzet. Ze zijn niet terug te draaien, een script is zo verkeerd geschreven, en een moderator die ze nodig heeft heeft /ban en /kick al. timeout() is er in plaats daarvan: die doet het werk en loopt vanzelf af.

Onthouden

db is een kleine opslag die bij je server hoort en tussen runs blijft bestaan. Die maakt van een opdracht die antwoordt een opdracht die bijhoudt: een economie, een teller, een profiel, een ranglijst.

db.add(`coins:${user.id}`, 10)

reply(`You now have ${db.get(`coins:${user.id}`)} coins.`)
AanroepWat hij doet
db.get(key, fallback)Leest hem, of de terugvalwaarde als er niets staat
db.set(key, value)Schrijft een getal, wat tekst, een lijst of een object
db.add(key, n)Telt bij een getal op en geeft je het nieuwe totaal
db.has(key)Of er iets onder is bewaard
db.delete(key)Verwijdert hem
db.top(n, prefix)De n hoogste getallen, voor een ranglijst
db.keys(prefix)De sleutels die met iets beginnen
db.count()Hoeveel sleutels de server gebruikt

Sleutels benoemen

Een sleutel is gewoon tekst, dus zet erin waar hij over gaat. Een voorvoegsel maakt db.top en db.keys bruikbaar, want beide werken daarmee.

db.set(`coins:${user.id}`, 500)        // one per member
db.set("event:name", "Winter cup")     // one for the server
db.set(`profile:${user.id}`, { class: "Mage", level: 3 })

Een ranglijst

let text = ""
let place = 1

for (const entry of db.top(5, "coins:")) {
  const id = entry.key.replace("coins:", "")
  text += `${place}. ${mentionUser(id)}, ${numberFormat(entry.value)}\n`
  place++
}

embed({ title: "Richest members", description: text, color: "gold" })

Wat het niet doet

  • Het geldt per server. Niets wat jij bewaart is zichtbaar voor een andere server, en niets wat een andere server bewaarde is hier zichtbaar.
  • Het is met opzet klein. 2.000 sleutels per server, 2.000 tekens per waarde en 25 lees- of schrijfacties per run. Het is een geheugen voor opdrachten, geen database.
  • Een run die halverwege stopt, behoudt wat hij al had geschreven. Er is geen ongedaan maken, dus schrijf eerst bij en dan af, niet andersom.

Met de hand nakijken

Servers → je server → Opdrachten toont een meter van hoe vol de naamruimte is zodra er iets is bewaard, en Bewaarde gegevens opent het paneel erachter.

  • Kijk wat er is. Elke sleutel met type, waarde en grootte, gefilterd op voorvoegsel.
  • Een waarde bewerken. Geschreven als JSON, zodat een getal een getal blijft en een object een object. De volgende run leest wat je achterlaat.
  • Een sleutel hernoemen. Verplaatst de waarde. Landt hij op een sleutel die al bestaat, dan wordt dat geweigerd in plaats van overschreven.
  • Verwijder er één, een voorvoegsel, of alles. Filteren op coins: en verwijderen ruimt de gegevens van die ene opdracht op zonder de rest aan te raken.

De ring wordt oranje voorbij 70% en rood voorbij 90%, zodat een vollopende naamruimte zichtbaar is voordat een script wordt geweigerd.

Andere personen

Een script kent om te beginnen de persoon die het uitvoerde. Hiermee lees je iedereen anders op de server.

AanroepGeeft terug
getMember(id) Dezelfde vorm als user: id, name, nick, mention, avatar, joinedAt, isBooster, roles. Null als die persoon hier niet is.
getRole(id) id, name, color, mention, position
roleCount(id)Hoeveel leden die rol hebben
getXp(who?) Hun levelstand: xp, level, rank, messages, percent. Laat de id weg om de persoon te bedoelen die hem uitvoerde.
mentioned()De eerste id die de opdracht kreeg, als vermelding of kaal
mentions()Allemaal, als lijst
const target = getMember(mentioned())

if (!target) {
  reply("Mention somebody first.")
} else {
  const level = getXp(target.id)

  embed({
    title: target.nick,
    thumbnail: target.avatar,
    color: target.roles.length ? target.roles[0].color : "blurple",
    fields: [
      { name: "Level", value: str(level.level), inline: true },
      { name: "Rank", value: `#${level.rank}`, inline: true },
      { name: "Joined", value: timestamp(target.joinedAt, "R"), inline: true }
    ]
  })
}

Opzoeken is begrensd op 25 per run, net als bewaren. Een hele server in een lus uitlezen is hier niet voor bedoeld.

De taal

Variabelen

let count = 0           // can change
const name = "Kirzen"   // locked, cannot be changed
total = 0               // no keyword at all also works

count += 5
count++

Tekst

const a = "double quotes";
const b = 'single quotes';
const c = `a template with ${user.name} in it`;
const d = "joined " + "with plus";

Voorwaarden

if (server.members > 100) {
  reply("Busy server")
} else if (server.members > 10) {
  reply("Getting there")
} else {
  reply("Cosy")
}

const label = args.length ? "with arguments" : "without"

&&, || en ?? werken zoals je verwacht, inclusief kortsluiting. === en == vergelijken beide op waarde.

Lussen

for (const word of args) {
  reply(word.toUpperCase() + " ")
}

for (let i = 0; i < 5; i++) {
  if (i === 2) continue
  reply(i)
}

let n = 3
while (n > 0) {
  reply(n)
  n--
}

Lijsten en objecten

const colours = ["red", "green", "blue"];
reply(colours[1]);              // green
reply(colours.length);          // 3

const profile = { name: user.name, level: 7 };
reply(`${profile.name} is level ${profile.level}`);

Functies

function double(n) {
  return n * 2
}

const shout = (text) => text.toUpperCase() + "!"

reply(double(21))
reply(shout("hello"))

Reacties

// a line
# also a line
/* or several
   lines */

Ingebouwde functies

FunctieWat hij doet
len(x)Hoe lang een tekst of een lijst is
random(a, b)Een geheel getal van a tot b, beide inbegrepen
pick(list)Eén item uit een lijst, willekeurig
range(a, b)Een lijst getallen vanaf a tot maar zonder b
keys(object)De namen in een object
sum(list)Telt een lijst getallen op
unique(list)Laat herhalingen vallen
shuffle(list)Dezelfde items in willekeurige volgorde
json(value)Maakt van alles tekst, handig tijdens het werken
numberFormat(n)1234567 wordt 1.234.567
date()year month day hour minute weekday
values(object)De waarden in een object
mentionUser(id)Maakt van een id een vermelding
mentionRole(id)Hetzelfde voor een rol
mentionChannel(id)Hetzelfde voor een kanaal
Number(v), String(v), Boolean(v)Omzettingen
parseInt(v), parseFloat(v)Leest een getal uit tekst
str(v), int(v)De Python-schrijfwijze van diezelfde twee
hasRole(id)Of de persoon die hem uitvoerde die rol heeft
color(name)Maakt van een kleurnaam een getal
now()De tijd nu, in seconden
timestamp(s, style) Een tijd die Discord in de eigen zone van elke lezer toont. Vormen: t T d D f F R
truncate(text, n)Kapt tekst af en zet er een beletselteken achter
bold, italic, underline, strike, spoiler, quote, code, codeBlock, link Discords opmaak, zonder de leestekens te hoeven onthouden
Math.… floor, ceil, round, abs, min, max, pow, sqrt, random

Methoden op waarden

Tekst

length toUpperCase toLowerCase trim includes startsWith endsWith indexOf charAt slice split replace replaceAll repeat padStart padEnd

Lijsten

length join includes indexOf slice concat push reverse map filter find some every reduce sort

Getallen

toFixed toString

const names = args.map(a => a.toLowerCase()).filter(a => a.length > 2);
reply(names.sort().join(", "));

Grenzen

Elk script draait tegen een budget. Zo'n grens raken is geen crash: de opdracht antwoordt met een bericht en Kirzen gaat verder.

GrensBovengrens
Scriptlengte20.000 tekens
Lengte van het antwoord1.900 tekens, daarna wordt het afgekapt
Verricht werk200.000 stappen
Lusrondes10.000 per lus
Geneste aanroepen50 lagen diep
Aangevraagde acties10 per run
Getoonde embeds3 per opdracht
Velden per embed25
Lees- en schrijfacties op bewaarde gegevens25 per run
Opzoekingen25 per run
Bewaarde sleutels2.000 per server
Grootte van één waarde2.000 tekens
Lengte van de lijst5.000 items
Samengestelde tekst20.000 tekens

Wat er niet is

Deze bestaan in JavaScript en hier niet. Elk ontbreekt omdat het een uitweg uit de zandbak zou zijn of een manier om de bot te laten vastlopen; geen ervan wordt dus alsnog toegevoegd.

eval Function require import process globalThis setTimeout fetch new class this async await try __proto__ constructor prototype

Het lezen van .constructor of .__proto__ geeft niets terug in plaats van een fout, en beide beschrijven wordt geweigerd.

Recepten

Een dobbelworp

const sides = int(args[0]) || 6

reply(`${user.mention} rolled a ${random(1, sides)} on a d${sides}.`)

Een zelf te kiezen rol

addRole("123456789012345678")
reply("Done, you have the role.")

Willekeurig er een kiezen

if (args.length < 2) {
  reply("Give me at least two things to choose between.")
} else {
  reply(`I pick **${pick(args)}**.`)
}

Een nette lijst

const items = args
  .map(a => a.trim())
  .filter(a => a.length > 0)
  .sort();

for (let i = 0; i < items.length; i++) {
  reply(`${i + 1}. ${items[i]}\n`);
}

Een profielkaart

embed({
  title: user.nick,
  color: user.topRole ? user.topRole.color : "blurple",
  thumbnail: user.avatar,
  fields: [
    { name: "Joined", value: timestamp(user.joinedAt, "R"), inline: true },
    { name: "Account made", value: timestamp(user.createdAt, "D"), inline: true },
    { name: "Roles", value: user.roles.map(r => r.mention).join(" ") || "none" }
  ],
  footer: { text: `${server.name} · ${server.members} members` }
})

Een dagelijkse beloning

const key = `daily:${user.id}`
const last = db.get(key, 0)
const wait = 86400 - (now() - last)

if (wait > 0) {
  reply(`Come back ${timestamp(last + 86400, "R")}.`)
} else {
  const prize = random(50, 200)
  const total = db.add(`coins:${user.id}`, prize)

  db.set(key, now())
  reply(`You picked up ${prize} coins. You now have ${numberFormat(total)}.`)
}

Reageren en opruimen

react("✅")
deleteMessage()
send(`${user.mention} said: ${input}`)