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.
const best = args.filter(a => a.length > 3);
if (best.length > 0) {
reply(best.join(", "));
} else {
reply("Nothing long enough.");
}
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
| Schrijven | Betekent |
|---|---|
and, or, not | &&, ||, ! |
True, False, None | true, false, null |
elif | else 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 > 0enif (x > 0)zijn hetzelfde. -
let en const. Toekennen aan een nieuwe naam maakt hem aan. Gebruik
constwanneer 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.
| Waarde | Wat 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
| Sleutel | Wat het vraagt |
|---|---|
title | Tekst, tot 256 tekens |
description | Tekst, tot 4096 |
color | Een naam, een "#rrggbb", of een getal |
url | Maakt van de titel een link |
thumbnail | Een afbeeldings-URL, klein in de hoek getoond |
image | Een afbeeldings-URL, over de volle breedte getoond |
author | Een naam, of { name, icon, url } |
footer | Tekst, of { text, icon } |
timestamp | true om er de huidige tijd op te zetten |
fields | Tot 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.
| Aanroep | Waar 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.`)
| Aanroep | Wat 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.
| Aanroep | Geeft 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
| Functie | Wat 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.
| Grens | Bovengrens |
|---|---|
| Scriptlengte | 20.000 tekens |
| Lengte van het antwoord | 1.900 tekens, daarna wordt het afgekapt |
| Verricht werk | 200.000 stappen |
| Lusrondes | 10.000 per lus |
| Geneste aanroepen | 50 lagen diep |
| Aangevraagde acties | 10 per run |
| Getoonde embeds | 3 per opdracht |
| Velden per embed | 25 |
| Lees- en schrijfacties op bewaarde gegevens | 25 per run |
| Opzoekingen | 25 per run |
| Bewaarde sleutels | 2.000 per server |
| Grootte van één waarde | 2.000 tekens |
| Lengte van de lijst | 5.000 items |
| Samengestelde tekst | 20.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}`)