Aller au contenu
Afficher dans l'application

Une meilleure façon de naviguer. En savoir plus.

Asterobot

Une application plein écran sur votre écran d'accueil avec notifications push, badges et plus encore.

Pour installer cette application sur iOS et iPadOS
  1. Appuyez sur Icône de partage dans Safari
  2. Faites défiler le menu et appuyez sur Ajouter à l'écran d'accueil.
  3. Appuyez sur Ajouter dans le coin supérieur droit.
Pour installer cette application sur Android
  1. Appuyez sur le menu à trois points (⋮) dans le coin supérieur droit du navigateur.
  2. Appuyez sur Ajouter à l'écran d'accueil ou Installer l'application.
  3. Confirmez en appuyant sur Installer.
What a package's script can reach inside Asterobot, what it can't, and the one job every Full socket script does itself.
A script is the JavaScript code inside a package. When a bot plays a package, Asterobot runs that code for the bot, right next to the bot's connection to the game. Read this before your first script: it saves you from looking for things that don't exist.
One copy per bot
Every bot that plays a package runs its own copy of the script. Two bots playing the same package share no variables, a script can't see or control another bot, and stopping one bot's script leaves the others running.
The code runs inside Asterobot, on the computer where Asterobot runs. Asteroboard only shows you what happens, so closing it doesn't stop a script.
What a script can do
Everything a script can do comes from the built-in modules it imports:
To Use From React to messages the game server sends on(), wait() asterobot:protocol See every message, in both directions onTraffic() asterobot:protocol Send messages send(), request() asterobot:protocol Change or block messages on their way through intercept() asterobot:protocol Read game data: items, monsters, maps, game texts text(), record(), table(), find(), search(), query() asterobot:gamedata Show settings on the bot's page and read them export const parameters, values, onChange() Your index.js, and asterobot:parameters Show buttons on the bot's page export const actions Your index.js Write to the bot's console botDebug(), botInfo(), botWarn(), botError() asterobot:bot Write to Asterobot's own log debug(), log(), info(), warn(), error() asterobot:console Read the session: game ticket, server, language, why the script started session asterobot:bot Know where the character stands: its map, its cell, the actors around it and the interactive elements of the map currentMap() asterobot:bot Keep values that outlive the script, and share them with the bot's other packages memory asterobot:bot Know the character's level, kamas, energy, life and pods, whether it's in a fight, its items, how many of an item it has and its job levels character(), items(), itemCount(), jobLevel() asterobot:character Have the fight module play the character's fights, with the profile, speed and kinds of fight you choose while the script runs enable(), disable(), setProfile(), setSpeed(), setFights() asterobot:fight Follow every fight, read the fight under way, and take the fight module's decisions yourself onFight(), state(), onDecision() asterobot:fight Say things in the game chat or whisper a player, with random wording, the character's level or map and item links, while the bot's chat guard waits out each channel's cooldown say(), whisper(), setVariable() asterobot:chat Have the chat module post ads, answer private messages and act on the channels' lines from the chat profiles you pick while the script runs, answer private messages yourself, and hear what it does enable(), setProfiles(), setFlag(), onAnswer(), onChat() asterobot:chat Raise an event of your own for the alarms' rules, and hear every event of the bot: staff, whispers, the chat module's, the game messages the rules hook raise(), onAlarm() asterobot:alarms Find the route the Dofus client would walk to a cell of a map, and how long walking it takes findPath() asterobot:pathfinding Work out a cell's grid position, or the distance and the direction between two cells cellToPoint(), pointToCell(), distance(), direction() asterobot:pathfinding Walk the character to a cell of its map, and wait for the end of the walk move() asterobot:movement Take the character to the next map by one side of its map, from the exit you choose or the nearest one changeMap() asterobot:movement Use an interactive element of the map: a door, a zaap, a resource, a workshop, or an exit drawn as a sun useElement() asterobot:movement Take the character to any map, however far, through map sides, stairs, suns and doors, or just see the way there travel(), route() asterobot:movement Answer the proof of identity the game server asks a Full socket bot for commitment(), publicKey(), proof() asterobot:verification Pause for a while sleep() asterobot:timers Close the bot's game connection disconnect() asterobot:bot Read the name, version and permissions of your package or of one of its dependencies packages.info() asterobot:runtime In MITM mode (man-in-the-middle: the bot relays the game session of a Dofus client that someone plays), the same functions work on that session. A script sees what the Dofus client sends and what the server answers. It can send messages to the server as if the client had sent them, or to the client as if the server had, and it can change or drop messages before they reach the other side.
The reference describes every function, one page per module.
What a script can't do
Some things you'd expect from JavaScript elsewhere aren't there, and a package can't add them.
Not available Use instead Reading or writing files Nothing. A script only has the modules of its own package and of the packages it depends on. The internet: fetch(), sockets, HTTP requests Nothing. The bot's game connection is the only connection a script talks through. npm packages and require() import from the asterobot: modules, from your package's own files, or from library packages your package declares as dependencies. setTimeout() and setInterval() await sleep(milliseconds) from asterobot:timers. The global console The console functions of asterobot:bot or asterobot:console, imported like anything else. window, document, process and the other browser or Node.js globals Nothing. Scripts also run within limits. For example, code that runs for more than 5 s without awaiting anything, as an endless loop does, ends the script. Limits lists them all.
Built-in moves, and what isn't built in
Moving the character is built in, on both kinds of bot, the way the Dofus client does it when the player clicks. move() walks the character to a cell of its map. changeMap() takes it to the next map by one side, from the exit you choose or the one it reaches soonest. useElement() uses a door, a zaap, a resource, a workshop or an exit drawn as a sun, walking within reach first. travel() takes it to any map, however far, map after map along the game's own travel graph. currentMap() says where the character, the other actors and the interactive elements are, and findPath() gives the route the Dofus client would take and how long the walk lasts.
There's no built-in function that chooses a zaap's destination or plays a fight, and travel() doesn't take zaaps yet. A script does those the way the Dofus client does: it sends the messages the client would send and reacts to what the server sends back, as Harvest and move on does for a zaap. The Dofus protocol reference describes those messages, and How bots talk to Dofus explains how they work.
Full socket scripts identify the bot
In Full socket mode, Asterobot signs in with the Ankama account, picks the game server and opens the game connection. Then it stops. It doesn't identify the bot on the game server and doesn't choose a character: that's the script's job. Its first message on a new connection must be IdentificationRequest, and Identify and run shows the few lines that send it. Every sample in these docs includes them, so each one works in both modes.
A MITM bot doesn't need them. The Dofus client has already identified the session when the script starts, and the same lines skip the identification there.
The official package @asterobot:base, which Asterobot installs and keeps up to date by itself, takes a Full socket bot into the game for you: it identifies the connection, selects a character, enters the world and keeps the connection alive. A package that depends on it starts from a character standing on its map.
Next, read The JavaScript you need, or go straight to Create a package if you'd rather start typing.
  • 124 vues
The few JavaScript concepts scripts rely on, what's missing compared to a browser or Node.js, and where to learn the language.
Scripts are plain JavaScript. These docs don't teach the language, but four ideas come up on almost every page, so each one gets a short explanation here and a link to a proper tutorial.
Why JavaScript
There's nothing to install and nothing to build. You write a package's code in Asteroboard's editor, which completes Asterobot's functions and the names of game messages as you type, and Asterobot runs the files as they are. Files must end in .js or .mjs, so TypeScript isn't an option.
If JavaScript is new to you, javascript.info and the MDN JavaScript Guide are both good places to learn it.
Modules
Every file of a package is a module. A module brings in what it needs with import and makes things available with export. Asterobot starts a script by calling the function that index.js exports as its default export:
import { botInfo } from "asterobot:bot"; export default async function behavior() { botInfo("The script started"); } Imports have to be import statements. Calling import() as a function is refused when the script runs, and require() doesn't exist. MDN explains JavaScript modules in detail.
Promises, async and await
Anything that involves the game takes time, so most of Asterobot's functions return a promise: sending a message, waiting for one, sleeping, most game data lookups. Inside an async function, await pauses your function until the promise settles. The rest of the bot doesn't pause with it: message handlers keep running while you wait.
Code that runs without an await does hold everything up, and Asterobot ends a script whose code runs for more than 5 s in one go, as an endless loop does. A promise that rejects without a catch ends the script too, so wrap the awaits that can fail in try/catch. Async and timing has the details.
The default export has to be an async function, or a function that returns a promise.
MDN has good pages on using promises and async functions.
BigInt
Some message fields hold 64-bit integers, such as account ids, character ids or kamas. A regular JavaScript number can't store all of those exactly, so scripts receive them as BigInt values, written with an n at the end:
const id = traffic.payload.senderCharacterId; // for example 123456789012n if (id === 123456789012n) { // A BigInt compares with another BigInt, not with a number. } You can't mix a BigInt and a number in one calculation: 1n + 1 throws a TypeError. Convert with Number(value) when you know the value is small enough, or keep working in BigInt. When you send a message, a 64-bit field accepts a BigInt, a string of digits, or a whole number no bigger than Number.MAX_SAFE_INTEGER.
Settings are the exception: a setting declared as int is a regular number. MDN's page on BigInt covers the rest.
Uint8Array
Raw bytes come as Uint8Array values: message fields that hold bytes, and traffic.rawAny, the undecoded bytes of any message. A first script rarely needs them.
What's different from a browser or Node.js
Scripts run inside Asterobot's JavaScript engine. The syntax is modern: classes with private fields, optional chaining (?.), ??=, spread, generators, top-level await and BigInt all work. What surrounds the language is much smaller than in a browser or in Node.js:
Elsewhere In a script console.log() botInfo() from asterobot:bot, or log() from asterobot:console setTimeout(), setInterval() await sleep(milliseconds) from asterobot:timers fetch(), sockets Not available window, document Not available require(), process, npm packages Not available import() called as a function Refused when the script runs async function*, for await (... of ...) Not supported. The editor puts a warning on them. JavaScript support has the complete list.
  • 72 vues
Messages, their names and fields, requests, events and responses, directions, and where your script sits in Full socket and MITM mode.
Dofus and the game servers talk by exchanging messages, and a bot talks to them the same way. A script is the part that decides what to send and what to do with what arrives. The words introduced here come back throughout the developer docs.
Messages, names and fields
A message is one piece of data sent between Dofus and a game server: a line of chat, a character moving, the contents of an inventory. Every message has a name and fields. When someone speaks in chat, a script receives something like this:
{ type: "ChatChannelMessageEvent", direction: "inbound", kind: "event", payload: { channel: "GLOBAL", senderName: "Airelle", senderCharacterId: 123456789012n, content: "Anyone up for a dungeon?", // ...and the other fields of the message }, // ...and a few more properties } type is the message's name and payload holds its fields. The other properties say which way the message travelled and what kind of message it is.
The game's definitions write field names with underscores, like sender_name. Scripts read them in camelCase, like senderName, and can use either spelling when they send a message. The Dofus protocol reference lists every message, its fields and what they mean.
Requests, events and responses
Most names follow Ankama's own convention: what the Dofus client sends ends in Request, and what the server sends ends in Event. The kind of a message tells you the same thing more precisely:
kind Sent by What it is request The client Something the client asks for or does event The server Something the server tells the client, on its own or in answer to a request response The server The answer to one of the rare requests that expect one, matched to it by a number called uid unknown Either side A message whose kind Asterobot couldn't read The detail that matters most: the game answers most requests with an event, not a response. When your character says something, the answer is the same ChatChannelMessageEvent that everyone on the channel receives, your own line included. So a script that sends a request usually waits for an event, and starts waiting before it sends. Send messages shows how.
Directions
A message is inbound when it travels from the game server towards the client, and outbound when it travels from the client towards the server. Handlers registered with on() only receive inbound messages. Handlers registered with onTraffic() receive both.
Where your script sits
Full socket mode
The bot is the Dofus client. Asterobot signs in, chooses the game server and opens the game connection. From there it's up to the script: identifying the bot, choosing a character, entering the world, and answering everything the server asks along the way, as the Dofus client would. The official package @asterobot:base does all of that for the packages that depend on it.
Your script ↔ Asterobot Asterobot ↔ Game server (game connection) MITM mode
MITM stands for man-in-the-middle. You play Dofus as usual, and the bot relays your game session between your Dofus client and the game server. Your script sits in the middle with it:
Your Dofus client ↔ Asterobot and your script Asterobot and your script ↔ Game server A script there sees what your client sends and what the server answers. It can send messages to the server as if your client had sent them, send messages to your client as if the server had, and change or drop messages on their way through.
Two things are different for a script on a MITM bot. The session is already identified when the script starts, because your client did it. And you're playing the same character: nothing stops you and the script from acting at the same time, so a script that moves your character competes with you. session.shared is true on such a session, so a script can check.
Full socket mode and MITM mode describe both modes from the player's side, and Sessions describes them from the script's side.
Readable names
On the wire, Dofus 3 doesn't send names like ChatChannelMessageEvent. Its messages carry short scrambled names, and those change between game versions. Asterobot translates them, so scripts work with readable names. In a traffic object, type holds the readable name and wireType the scrambled one.
Not every message has a readable name yet. When Asterobot can't name a message, its type is empty. The bot's Network tool marks such a message Schema when its fields can still be read, and Raw when only its bytes can. A script can only send messages that have a readable name.
Every property of a traffic object is described in Traffic, and After a game update explains what changes for scripts when Ankama patches the game. Next, see Your toolbox.
  • 111 vues
The tools in Asteroboard and on asterobot.net you'll use to write, run and debug scripts, and when to reach for each one.
Everything you need to write scripts is already in Asteroboard, plus one reference on asterobot.net. Here's what each tool is for, so you know where to look when a script doesn't do what you expect.
The package editor
You write code in the package editor. Open it from Library Manager: New package creates a package and opens it, and Edit code, in a package's menu, opens one you already have. Only local packages can be edited: a package downloaded from the marketplace is read-only.
The editor completes the asterobot: functions and the names of game messages as you type, checks the fields you put in a message, and counts errors across every file of the package. Its Game panel shows the map viewer (Atlas) and the game data browser (Datacenter) next to your code.
Create a package gives a tour of it, and The editor covers every feature.
Run
Run, in the editor, saves your package and starts it on a connected bot you pick, replacing whatever that bot was playing. Change a line, click Run, watch the bot: that's the loop you'll repeat most. Identify and run explains it.
The bot's Console and Problems
On a bot's page, the Console tab shows what your script writes with botDebug(), botInfo(), botWarn() and botError(). Those lines are mixed with Asterobot's own messages about the bot and with the game chat. When the console gets busy, untick Asterobot under Channels, at the left of the chat box, or type part of your lines in Filter.
When a script stops because of an error, The behavior stopped with an error appears at the top of the bot's page, whichever tab is open, with the complete error text and a Copy button.
See Console and Logging and debugging.
Server > Console
What a script writes with asterobot:console doesn't appear on the bot's page. It goes to Asterobot's own log, which you read in Server > Console and in the terminal where Asterobot runs. Those lines don't say which bot wrote them. By default, debug lines are left out: to see them in Server > Console, set Server level to DEBUG. Status and console describes the page.
The Network tool
On a bot's page, Tools > Network shows the messages going through the bot. It's how you find out which messages the game uses for something, and what they contain. It has three tabs:
Tab What it's for Traffic The live list of messages, oldest first. Click a message to see its fields. Send a game message Sends one message by hand, from a form built from the message's fields, the same way send() sends it from a script. Settings Chooses which messages Traffic shows. Nothing is shown until you select some and click Apply. What you select in Settings only changes what Asteroboard shows. A script receives every message, whatever is selected.
Network traffic, Send messages by hand and Finding messages go further.
The Memory tool
On a bot's page, Tools > Memory shows the bot's memory: every key the bot's scripts stored, its value, and when it last changed. It's how you check what a script kept for its next run. The page refreshes by itself when a script changes the memory, and only reads it: scripts are the only ones that write it.
A BigInt shows as its digits followed by n, as in 153879301n, so it can't be mistaken for a number, and null shows as a dash.
Game data and SQL
Messages are full of ids: items, monsters, maps. To find out what an id stands for, open the game version's page from Game Manager and go to its Game data tab. Search finds records by name, Tables lets you browse them, and SQL runs read-only queries, the same kind a script runs with query() from asterobot:gamedata. Try a query there before you put it in a script.
See Game data, SQL and, for scripts, Game data overview.
The Dofus protocol reference
The Dofus protocol reference on asterobot.net describes the game's messages and their fields. Keep it open while you write.
Inline scripts
For a quick test that doesn't deserve a package, a bot can run code you paste:
On the bot's page, open the menu next to Play. Choose Load a new package. Select Inline script and paste your code. Click Load a new package at the bottom of the dialog, then click Play. Load a package and play it, in the same menu, opens the same dialog and starts the script as soon as it's loaded.
An inline script is a single file that isn't saved anywhere. It can import the asterobot: modules but no other file, it can't be opened in the editor, and its settings don't appear on the bot's Settings tab. Loading one stops whatever script the bot was running. Run a package covers loading in detail.
Ready to write something? Start with Create a package.
  • 94 vues
Create a local package in Library Manager, find your way around the editor, and read the starter code line by line.
Every script lives in a package. You create one in Library Manager, and Asteroboard opens it in the editor with a few lines of starter code that already run.
Create the package
In the sidebar, open Library Manager. Click New package. The New local package dialog opens. In Name, type a name for your package, for example my-first-script. Leave Version at 1.0.0. Click Create and edit. The dialog closes and the editor opens on your package, showing index.js. Back in Library Manager, the package has its own row with the Local badge.
Field Default Rules Name Empty Required. A package you create is local, so its name can't start with @ and can't contain : or @ anywhere. Version 1.0.0 Required. Names and versions explains how to number versions. The dialog tells you when something is wrong:
Message What to do A new package is local, so its name can't start with '@' - those are assigned by the marketplace. Remove the @. Names that start with @ belong to packages installed from the marketplace. A local package name can't contain ':' or '@'. Remove the : or the @. my-first-script 1.0.0 already exists and would be replaced. Your library already has a package with that name and version. Create and edit stays disabled until you change one of them. Couldn't create the package Asterobot didn't create the package. This toast gives no reason. A tour of the editor
The top bar
Item What it does Box icon Opens the package's own page, with its details and its manifest. Name and version The package you're editing, such as my-first-script @1.0.0. Unsaved Appears while some changes aren't saved. Incompatible Appears when the Asterobot versions the package says it works with don't include the one you run. See Compatibility. Error count A badge such as 2 error(s) when the editor finds errors. It counts every file of the package, not only the open one. IntelliSense Hover it to see how many game messages the editor can complete. If it says Couldn't load the typings - editing works, completion doesn't., you can keep editing, without completion. Bot picker Chooses the bot that Run starts the package on. It only appears when at least one bot is connected to a game. Game Opens a panel next to the code with the map viewer (Atlas) and the game data browser (Datacenter). Save Saves every file of the package. Ctrl+S does the same. The button is disabled while nothing has changed. Run Saves, then starts the package on the bot in the picker. It's disabled when no bot is connected to a game, with the tooltip No bot is connected to a game. Files
The Files column lists the package's modules, index.js first. A dot after a file's name means it has unsaved changes, and a number next to it counts its errors.
The plus button adds a file. The Add a file dialog asks for a Path, such as lib/helper.js. The path must end in .js or .mjs, stay inside the package and not be taken already, and the file only exists once you save.
The trash icon, shown when you hover a file, removes it from the list, and saving deletes it from the package. index.js has no trash icon: it's where every script starts.
Saving replaces the package's files with exactly what the editor holds. A bot that's already playing the package keeps running the code it started with until it starts again, which is what Run does.
The starter code
A new package's index.js contains:
import { info } from "asterobot:console"; export default async function behavior(launch) { info(`started (${launch.reason})`); } Line by line:
import { info } from "asterobot:console"; brings in the info() function from the built-in asterobot:console module. Everything Asterobot gives scripts comes from an asterobot: module imported this way.
export default async function behavior(launch) { declares the function Asterobot calls when a bot starts the script. It has to be the default export of index.js, and it has to be async. Its name doesn't matter.
launch is an object Asterobot passes in. launch.reason says why the script is starting: "initial", "resume" or "reload". The next page explains what each value means for your script.
info(`started (${launch.reason})`); writes a line such as started (initial) to Asterobot's own log. That log is in Server > Console, not in the bot's Console tab, so don't look for it on the bot's page.
The closing } ends the function. Returning from it doesn't stop the script: anything the function set up, such as handlers for game messages, keeps working until the script is stopped. This one sets nothing up, so it has nothing left to do.
The starter code doesn't identify the bot on the game server. On a Full socket bot it runs, but nothing happens in the game. Identify and run fixes that, and moves the output to the bot's page.
  • 88 vues
Run your package on a bot, see its output, add the identification every Full socket script needs, and stop or restart it.
A package does nothing until a bot plays it. Start by running the starter package on a bot, then send its output to the bot's page and add the lines that identify a Full socket bot on the game server.
Before you start
You need a bot whose header shows Connected to game server. The easiest is a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play). There's no Ankama account to store, and you can watch the result in your own game. Your first bot sets one up, and it shows Connected to game server once your Dofus client has connected through it. A Full socket bot works too: see Full socket mode.
Run the package
Open your package in the editor. In the bot picker, choose your bot. Click Run. Asteroboard saves the package, stops whatever the bot was playing, loads your package on it and starts it. A toast says Running on followed by the bot's name.
Run also makes this package the bot's package. The bot's header now shows my-first-script@1.0.0, and Play on the bot's page starts it again later.
If the package doesn't start, the toast says Couldn't run the package instead. When something goes wrong explains where to look.
Where the output goes
The starter code writes with info() from asterobot:console, so its line lands in Server > Console: started (resume) on a MITM bot, or started (initial) the first time it starts on a Full socket bot's connection.
Scripts have two sets of output functions, and they write to different places:
Functions Module Where the lines appear botDebug(), botInfo(), botWarn(), botError() asterobot:bot The bot's Console tab, as Script lines at that level. Lines of every level show there. debug(), log(), info(), warn(), error(), also available as console.debug(), console.log() and so on asterobot:console Server > Console, and the terminal Asterobot runs in. The lines don't say which bot wrote them. By default debug lines are left out: set Server level to DEBUG on that page to see them there. That console is an object exported by asterobot:console: scripts have no global console, so it has to be imported like the rest.
While you write a script, the bot's console is almost always the one you want. Change index.js so its output lands there:
import { botInfo } from "asterobot:bot"; export default async function behavior(launch) { botInfo("Started, launch reason:", launch.reason); } Click Run again, then open the bot's Console tab: the line is there. When you pass several values, the console joins them with spaces.
Identify a Full socket bot
In Full socket mode, Asterobot signs in, picks the game server and opens the game connection, then leaves the rest to the script. The first message on a new game connection has to be IdentificationRequest, carrying the ticket the login server issued for that connection. These are the lines every sample in these docs uses:
import { session } from "asterobot:bot"; import { send } from "asterobot:protocol"; export default async function behavior(launch) { // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } Field Value What it is ticketKey session.gameToken The ticket the login server issued for this game connection, which Asterobot kept while signing in. languageCode session.language The language code the script was started with. The game server uses it for the texts it sends back. Right now that's always fr for a script started from Asteroboard, whatever language the bot itself is set to: nothing Play, Run, Load a new package or Load a package and play it send changes it. The Dofus protocol reference writes these fields ticket_key and language_code. A script can send either spelling, and these docs use camelCase because that's how scripts read fields back.
When a script has message handlers, register them above this block, as the next pages do. Registering a handler sends nothing, so the identification is still the first message out, and handlers registered earlier already receive what arrives while send() is awaited.
Caution
Never log session.gameToken or show it to anyone. It's the ticket to the bot's game session, and a stranger who has it could take that session over.
A field the message doesn't have makes send() reject. Sending token instead of ticketKey, for example, fails with IdentificationRequest.token: unknown protobuf field, and the script stops with that error.
Identifying doesn't take the bot any further. Asterobot doesn't do the rest for a Full socket bot either, and a script that wants to play has to do everything the Dofus client does by itself:
choose a character and enter the game; tell the server the character is ready on every map it arrives on; answer the server's proof of identity, which comes minutes into the session and again later; answer the server's polls, every four minutes, and ping. The official package @asterobot:base does all of this, and starts by itself in any package that depends on it. Asterobot installs it by itself and keeps it up to date: add it to your package's dependencies. To learn what the game expects yourself, watch your own Dofus client, as described below.
Watch what your Dofus client sends
A MITM bot shows the messages your client exchanges with the game server, in the order they happen. To see what logging in to a character takes:
Before you launch Dofus, open your MITM bot's page, then Tools > Network > Settings. Tick the messages you want to watch. The select button next to the filter ticks every message the filter shows. Click Apply. Open Traffic, then launch Dofus and log in to a character. Read the list from the top. Out marks what your client sent and In what the server sent. Click a message to see its fields. If Settings then shows a badge such as "12 message(s) dropped - the stream couldn't keep up", select fewer messages and log in again.
Why the check is harmless in MITM mode
The same package runs on both kinds of bot. On a MITM bot, your Dofus client identifies the session before any script starts, so Asterobot reports the script's first start as "resume", never "initial", and the if skips the identification.
Even a script without the check can't identify a relayed session a second time. Asterobot refuses to forward the message and writes Refused to forward a message that would re-authenticate a relayed session in the bot's console.
Launch reasons
launch.reason is "initial" on a Full socket bot's new game connection, until a script has started successfully on it. Every later start on that connection is "resume", and so is every start on a MITM bot. The entry function lists every reason and what each one asks of your script.
A start counts as successful once the default export's function has finished. If a script sends IdentificationRequest and its function then fails before finishing, the next start on that connection is "initial" again, and the script identifies a second time. Handle the errors that can happen after the identification so the first start succeeds.
Stop and play again
The bot's header has the controls for its package:
Control What it does Play Starts the bot's package. It's disabled while the bot isn't connected, with the tooltip Connect the bot to a game before playing a behavior. When the bot has no package yet, it opens the Load a behavior selection dialog instead. Stop Takes the place of Play while a script runs. It stops the script and nothing else: the bot stays connected to the game. The menu next to them Load a new package, Load a package and play it and Unload package. There's no Reload button. To restart a script with your latest code, click Run in the editor, or Stop and then Play on the bot's page: both start the code you last saved. Play, stop and reload explains what's kept between two starts and what starts over.
When something goes wrong
What you see What it means A number on the editor's error badge The editor found errors, such as a misspelled function or a field a message doesn't have. It doesn't stop you from saving or running, but the script will probably fail. Couldn't run the package after Run, or Couldn't start the behavior after Play The script didn't start, and the toast gives no reason. Open the bot's Settings tab: when the code can't even be loaded, for example because of a syntax error or an import that doesn't resolve, Package settings shows Couldn't read this package's settings with the reason. The editor's error badge usually points at the line too. The behavior stopped with an error, at the top of the bot's page The script started, then threw an error or had an await reject without a catch, while starting or later. The alert shows the complete error text, with Copy and Dismiss. In Bot Manager, the bot's Behavior column says Failed. The error text starts with where the script failed. Sending token instead of ticketKey while the script starts gives:
initialize behavior module "library/my-first-script/1.0.0/index.js": Promise rejected: IdentificationRequest.token: unknown protobuf field Dismiss hides the alert. A different error still shows up, even after you've dismissed the previous one.
Errors explains what stops a script and what doesn't, and Error messages lists the texts you can meet. Next, React to messages.
  • 85 vues
Find a message with the Network tool, handle it with on(), write it to the bot's console and test it on a MITM bot.
Most of a script's work is reacting to what the game says. Here you'll build the smallest useful example: a script that writes every chat line the bot receives to the bot's console.
Test it on a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play). A Full socket bot doesn't get into the game until a connection script takes it there, and chat only starts once a character is in game. Your first bot sets up a MITM bot.
Find the message
Before you write a handler, you need the message's name and the names of its fields. The Network tool shows both.
On your MITM bot's page, open Tools > Network > Settings. Type Chat in the filter box and tick ChatChannelMessageEvent. Click Apply. A toast says Now mirroring 1 message type(s). Open Traffic, then say something in the Dofus chat. A ChatChannelMessageEvent row appears: the server sends your own line back to you, as it sends everyone else's. Click the row to see its fields. The fields have the names a script reads, in camelCase: content is the text, senderName the speaker's character name, and channel the channel it was said on, as a name such as GLOBAL. There's one difference: 64-bit numbers such as senderCharacterId show in quotes here, while a script receives them as BigInt values. The Dofus protocol reference says what every field means.
The bot's Console tab doesn't need this: Asterobot follows the chat itself, so the console shows it whether or not the message is ticked here.
Write the handler
Replace the contents of index.js with:
import { session, botInfo } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; export default async function behavior(launch) { on("ChatChannelMessageEvent", (traffic) => { // A message Asterobot couldn't decode has no payload, and an error // thrown in a handler stops the whole script. if (!traffic.payload) return; const { channel, senderName, content } = traffic.payload; botInfo(`[${channel}] ${senderName}: ${content}`); }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } on("ChatChannelMessageEvent", handler) registers handler for every inbound message with that name. It returns immediately, and the handler then runs each time such a message arrives, until the script stops. That's why the default export can finish right after registering it.
The handler receives each message as a traffic object, and traffic.payload holds its fields. The check at the top of the handler matters: a handler that throws an error stops the whole script, and a message Asterobot couldn't decode has no payload to read.
The handler is registered before the identification on purpose. Registering a handler sends nothing, and while send() is being awaited, the messages that arrive already reach the handlers registered before it.
on() also takes "*" to receive every inbound message, or a function that decides which messages to keep. Listening covers those options, the order handlers run in, and off().
Test it
In the editor, pick your MITM bot and click Run. On the bot's page, open Console. Under Channels, at the left of the chat box, untick Asterobot. Say something in the Dofus chat. A script line such as [GLOBAL] Airelle: hello appears in the console.
The same message also shows as a chat line, in its channel's colour: one is the game chat as the console draws it, the other your script's line. Your script receives chat lines whatever the Network tool selects, because that selection only changes what Asteroboard's Network tool shows.
Next, Send messages makes the script answer. Traffic lists every property of a traffic object.
  • 68 vues
Send a message with send(), wait for the game's answer with wait(), know when request() applies, and handle timeouts.
Reading the game is half of what a script does. Here the chat script learns to answer: when someone says !hello, the bot replies in chat and checks that its reply went through.
Warning
On a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), everything the script sends goes out as your character. The reply on this page is a real chat message that other players can see.
Tip
This page sends the chat message itself, to learn the protocol. In a real package, asterobot:chat's say() sends it through the bot's chat guard, which waits out a channel's cooldown and keeps to the game's limits.
Try the message by hand first
Before writing code, send the message from the Network tool. If it works there, you know the payload is right.
On the bot's page, open Tools > Network > Send a game message. Keep To the server selected, and pick ChatChannelMessageRequest in the list of messages. Type some text in content and leave the other fields as they are. Empty fields aren't sent, and a channel left out means GLOBAL, the general channel. Check the payload under This is what will be sent., then click Send ChatChannelMessageRequest. Your line appears in the game. If ChatChannelMessageEvent is selected in Settings, Traffic also shows your request going Out and your line coming back In.
send()
await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" }); send() takes a message name and a payload holding the message's fields. It builds the message and puts it in line to go to the game server, and its promise resolves at once: it doesn't wait for an answer. Messages leave in the order you send them.
The promise rejects when the message can't be built:
Problem Error A field the message doesn't have ChatChannelMessageRequest.contnt: unknown protobuf field A value of the wrong type ChatChannelMessageRequest.content: expected string A channel name that doesn't exist ChatChannelMessageRequest.channel: unknown enum symbol "GENERAL" A message name Asterobot can't send semantic protobuf message is not mapped: MyMessageRequest channel takes a channel's name as a string, such as "GLOBAL", "GUILD" or "PARTY", or the channel's number. The Dofus protocol reference lists every channel.
Wait for the answer
The game doesn't answer a chat line with a response. It sends the line to everyone on the channel as a ChatChannelMessageEvent, you included. To know your line went through, wait for that event.
Send, then wait:
await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" }); await wait( (traffic) => traffic.type === "ChatChannelMessageEvent" && traffic.payload?.content === "Hello", { timeout: 5000 }, ); A wait only sees messages the bot handles after wait() is called. send() resolves before any answer can be handled, so a wait started right after await send() always gets the answer, however fast it comes. Waiting explains more.
wait() resolves with the next inbound message that matches, and you choose what matches: a message name, or a function that returns true for the message you want. The function above checks the name and the text, so another player's line doesn't count.
wait() has no timeout of its own. A wait that never matches stays open until the script stops, so always pass { timeout }, in milliseconds. When the time runs out, the promise rejects with protocol wait timed out.
request()
request() sends a message and resolves with the game's response to it. Only a handful of Dofus requests get a response, matched to them by uid, and none of them is something a first script needs to send. For every other message nothing comes back: request() waits until its timeout, 30 seconds unless you pass another one, and then rejects with context deadline exceeded.
So for almost everything, wait for the event the game answers with while you send(), as this page does. Sending covers request(), and the to option that sends a message to the Dofus client instead of the server.
Timeouts
Function Default timeout To change it send() None needed: it resolves as soon as the message is in line request() 30 seconds { timeout: milliseconds } as the third argument wait() None: it waits until the script stops { timeout: milliseconds } as the second argument A timeout must be a positive number of milliseconds. Anything else rejects with timeout/milliseconds must be a finite positive Number.
The complete script
import { session, botInfo, botWarn } from "asterobot:bot"; import { on, send, wait } from "asterobot:protocol"; const TRIGGER = "!hello"; const REPLY = "Hello from my first script"; export default async function behavior(launch) { on("ChatChannelMessageEvent", async (traffic) => { // Only the exact trigger: the game also sends our own reply back to us, // and answering that echo would start a loop. if (traffic.payload?.content !== TRIGGER) return; try { await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: REPLY }); // Started right after the send, so even a quick echo isn't missed. await wait( (echo) => echo.type === "ChatChannelMessageEvent" && echo.payload?.content === REPLY, { timeout: 5000 }, ); botInfo("Reply delivered"); } catch (error) { // An error that escapes a handler stops the whole script. botWarn("Reply not delivered:", String(error)); } }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } The handler is async so that it can await. Handlers don't wait for each other: while this one waits for the echo, the bot keeps handling other messages, the echo included, which is how wait() gets to see it.
The try/catch keeps a failed reply from ending the script, and String(error) turns the error into text the console can print.
Anyone who says !hello on a channel your character receives gets an answer in the general channel, so stop the script when you're done testing.
Test it
Run the package on your MITM bot. Say !hello in the Dofus chat. The bot answers in the general channel, and its Console shows Reply delivered. If the console shows Reply not delivered: followed by a reason, select ChatChannelMessageRequest and ChatChannelMessageEvent in the Network tool's Settings, try again, and check in Traffic whether the request went out and whether the line came back.
Next, Add a setting lets people change the trigger and the reply without editing your code. Waiting covers every way to wait for a message.
  • 69 vues
Declare settings with export const parameters, read them with values and onChange, and change them from the bot's Settings tab.
The chat bot from Send messages has its trigger and its reply written in the code. Turn them into settings, and anyone who runs your package can change them from the bot's page, without editing code and without stopping the script.
Declare the settings
A package declares its settings by exporting an object named parameters from index.js. Each key is the name of a setting, and its value describes the setting:
export const parameters = { enabled: { type: "bool", label: "Answer in chat", default: true, }, trigger: { type: "string", label: "Trigger", description: "The chat message the bot answers.", default: "!hello", }, reply: { type: "string", label: "Reply", description: "What the bot says back.", default: "Hello from my first script", }, }; Field What it does type bool, string, int, double, array or map. It decides the input shown on the bot's page: a switch for bool, a text box for string, a number box for int and double, a list for array and map. label The name shown next to the input. Without it, the setting's own name is shown. description A line of help shown with the input. default The value the setting has until someone changes it. Without it, the setting starts empty: false, "", 0, an empty list or an empty map, depending on its type. Settings can also have a placeholder, a unit, a min and a max, a list of choices, and, for lists and maps, an of type. Declare settings describes all of them and the rules they follow.
Asterobot reads these declarations by running the top level of your modules, without calling your default export. Keep the top level of index.js to imports and declarations: any other code placed there also runs when Asterobot reads the package's settings.
Use them in the script
This is the script from Send messages, reading its trigger and its reply from the settings:
import { session, botInfo, botWarn } from "asterobot:bot"; import { values, onChange } from "asterobot:parameters"; import { on, send, wait } from "asterobot:protocol"; export const parameters = { enabled: { type: "bool", label: "Answer in chat", default: true, }, trigger: { type: "string", label: "Trigger", description: "The chat message the bot answers.", default: "!hello", }, reply: { type: "string", label: "Reply", description: "What the bot says back.", default: "Hello from my first script", }, }; export default async function behavior(launch) { onChange(({ name, value }) => { botInfo(`Setting ${name} is now ${value}`); }); on("ChatChannelMessageEvent", async (traffic) => { // Read on every message, so an applied change counts from the next line. if (!values.enabled || traffic.payload?.content !== values.trigger) return; // Kept for the whole exchange, because the setting can change meanwhile. const reply = values.reply; // A reply equal to the trigger would answer its own echo forever. if (reply === values.trigger) return; try { await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: reply }); // Started right after the send, so even a quick echo isn't missed. await wait( (echo) => echo.type === "ChatChannelMessageEvent" && echo.payload?.content === reply, { timeout: 5000 }, ); botInfo("Reply delivered"); } catch (error) { // An error that escapes a handler stops the whole script. botWarn("Reply not delivered:", String(error)); } }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } values, from asterobot:parameters, holds your package's current settings by name, so values.trigger is whatever the Trigger field says. It's live: when someone applies new settings on the bot's page, the running script reads the new values from then on, without restarting. That's why the handler reads values each time a message arrives instead of copying the settings once at the start. values is read-only, so don't assign to it or delete from it: assigning throws a TypeError with 'Set' on a dynamic object returned false, and deleting throws Could not delete property "<name>" of a dynamic object. Either way, the setting keeps the value set on the bot's page. A setting declared as int or double reads as a regular number, not as a BigInt.
onChange() is for doing something the moment a setting changes. Its handler runs once for each setting whose value changed, only for your own package's settings, and receives the setting's name and its new value. Like a message handler, an onChange() handler that throws an error stops the script.
Change the settings in Asteroboard
Run the package on your MITM bot. On the bot's page, open Settings, then Package settings. Your package's section lists Answer in chat, Trigger and Reply with their current values. Change Reply: Not applied appears next to the package's name. Click Apply. A toast says my-first-script settings applied, and the bot's Console shows Setting reply is now followed by your text. Say the trigger in the Dofus chat, and the bot answers with the new reply.
Reset throws away the changes you haven't applied. Settings are saved with the bot, even while no script is running, and the next start uses them.
Package settings shows the settings of the bot's package, the one that Run or Load a new package gave it, whether or not it's running. A bot without a package shows No package selected. When you change the declarations in the editor, save them and click Refresh in this section to see the new fields, then Run the package again so the running script gets them too.
When settings are refused
What you see What it means Couldn't read this package's settings, in Package settings A declaration is invalid, for example a default that doesn't match its type. The alert gives the reason, ending with something like parameter "enabled" default: want a bool, got string. Until you fix it, the script starts with no settings at all: every read from values returns undefined. Couldn't apply the settings A value you applied doesn't fit its declaration, for example a number below the setting's min. The toast gives the reason, such as retries: 0 is below the minimum 1, and nothing from that form is saved. A package can also put buttons on the bot's page: see Declare actions. Next, Share your package.
  • 63 vues
Export your package as a .zip, give each change its own version number, and find where publishing on asterobot.net is explained.
Your package only exists in your own library until you share it. You can send someone a .zip file, or publish the package on the marketplace so anyone with Asterobot can install it.
Export a .zip
Open Library Manager. Open the menu at the end of your package's row, or right-click the row. Choose Export. Your browser downloads a file named after the package and its version, such as my-first-script-1.0.0.zip. The same menu is also on the package's own page.
The archive holds the package's files as they are in your library: index.js, your other modules, and asterobot.json if the package has one. The settings people chose for your package on their bots aren't part of it.
Import it on another Asterobot
Whoever receives the file installs it from their own Library Manager:
Click Import a .zip. Drop the file on Drop a .zip here, or click to choose one, or click that area to pick the file. Type a Name. Version fills in by itself when the file name ends with a version, as in -1.0.0.zip. Click Import. A Package imported toast confirms it. The name and version come from the dialog, not from the archive. If a package with the same name and version is already installed, the dialog warns This replaces the installed my-first-script 1.0.0. and Import replaces it. Archives can't be bigger than 32 MiB.
Manage your library covers importing from the player's side, and Zip files and the command line describes the archive in detail.
Give each change a new version
A package is identified by its name and its version together. Your library can hold my-first-script 1.0.0 and 1.0.1 side by side, and each bot plays one exact version.
So when you change a package you've already shared, give the change a new version instead of reusing the old number. Two different packages under the same name and version can't be told apart, and importing one replaces the other.
To start a new version from the current one:
In Library Manager, open your package's menu and choose Copy to a new version. The Copy this package dialog suggests the same name, with the last number of the version raised by one: 1.0.0 becomes 1.0.1. Change it if you want another number. Click Copy. The editor opens on the new version. Dependencies, permissions and the compatible version are copied along with the code. Your bots keep playing the version they were on until you Run the new one on them. Names and versions explains how to number versions.
Publish on asterobot.net
The marketplace is the Library category of asterobot.net, and any asterobot.net member can publish a package there by uploading the .zip you exported. Before you publish has a checklist, and Publish on asterobot.net walks you through it. When you publish a new version later, follow Publish an update.
  • 87 vues
The files a package holds, the index.js every script starts from, the rules for module paths, and the optional asterobot.json.
A package is a small set of JavaScript files with a fixed starting point, index.js, and not every file in it runs when a bot starts the package.
What a package contains
A package with a few files looks like this:
index.js asterobot.json lib/chat.js lib/format.mjs fightprofiles/careful.json chatprofiles/wheat-ads.json File Required What it's for index.js Yes The entry module. Every script starts here. Other .js and .mjs files No Modules that index.js imports, directly or through other modules. They can sit at the root of the package or in folders. asterobot.json No The manifest: dependencies, the Asterobot versions the package works with, and declared permissions. fightprofiles/<name>.json No The fight profiles the package ships, one JSON file each, directly in the fightprofiles folder: see Ship fight profiles. They're data, never run, and no script can import them. chatprofiles/<name>.json No The chat profiles the package ships, the same way, directly in the chatprofiles folder, in the chat profile format. Players see them on their Chat Profiles page as @publisher:package/<name>, read-only, and a script can name them in setProfiles() (asterobot:chat). Asterobot reads nothing else. Any other JSON data file, an image or a README placed in a package is left out: it's not part of the script, and no script can read it. .js and .mjs files are handled the same way, as ES modules.
A package you create in Library Manager starts with index.js alone. Create a package shows how to add files in the editor.
index.js
index.js is always the entry module, at the root of the package. It can't be renamed, and the editor shows no trash icon for it.
Asterobot looks for three exports in it. The default export is the function called when a bot starts the package, described in The entry function. export const parameters and export const actions declare the settings and the buttons shown on the bot's Settings tab: see Declare settings and Declare actions.
When your package is a dependency of another package, its index.js is also what that package receives when it imports yours. Modules and imports explains how.
Only what index.js reaches runs
When a bot starts the package, Asterobot begins with index.js, follows its import statements, then the imports of those modules, and so on. A file that isn't reached this way is never loaded: its code doesn't run, and a mistake in it doesn't stop the script.
The module limits count the same files, dependencies included:
Limit Value Modules reached from index.js 256 Total size of their source code 4 MiB Length of an import chain 64 modules Past any of them the script doesn't start. Limits lists every limit a script runs under.
Module paths
A module's path is its place inside the package, such as lib/chat.js. Every file you add in the editor follows these rules:
Rule Accepted Refused Ends in .js or .mjs lib/chat.js lib/chat.ts, data.json Relative to the root of the package lib/chat.js /lib/chat.js Uses forward slashes lib/chat.js lib\chat.js Stays inside the package lib/chat.js ../chat.js Written without ./ and without doubled slashes lib/chat.js ./lib/chat.js, lib//chat.js The Add a file dialog checks the path while you type:
Message Cause A file must be a .js or .mjs module, or a fight profile in fightprofiles/ or a chat profile in chatprofiles/, as a .json file. The path doesn't end in .js or .mjs, and isn't a .json file directly in fightprofiles/ or chatprofiles/. A profile opens as JSON. A path must stay inside the package. The path starts with /, or contains .. or a backslash. This package already has a file at that path. Another file of the package already has this path. The dialog doesn't check the last rule of the first table. A file added as ./lib/chat.js or lib//chat.js appears in the Files list, but Save then fails with Couldn't save. Remove that file and add it again as lib/chat.js.
Zip files and the command line describes how the files of an imported .zip are laid out.
How error messages name your files
Error messages refer to a module by where its package sits in your library: library/, the package name, the version, then the path. The file lib/chat.js of my-first-script 1.0.0 appears as library/my-first-script/1.0.0/lib/chat.js.
A package installed from the marketplace has its name split at the colon: index.js of @alice:mining 1.4.2 is library/@alice/mining/1.4.2/index.js. An inline script, loaded with Inline script, isn't in the library, so its name starts with local/ instead.
asterobot.json
asterobot.json is optional. A new package doesn't have one, and Asterobot only writes the file once there's something to put in it. You don't edit it in the code editor: open the package's own page and use its Manifest card.
Field Written by What it holds dependencies You The other library packages this package imports, and their versions compatibleVersion You The Asterobot versions the package works with, such as >=1.5.0 <2.0.0 permissions You What the package says it needs. Asterobot doesn't enforce them yet: see Permissions. sourceId Asterobot The asterobot.net file the package was installed from integrity Asterobot A checksum of the package's files, for a package installed from the marketplace. Asterobot checks it each time it loads the package. The manifest describes every field, and Dependencies shows how to declare one.
Next, The entry function.
  • 65 vues
The default export Asterobot calls when a script starts, the launch object it receives, what top-level code can do, and what to do for each launch reason.
Every script starts in one function: the default export of the package's index.js. Asterobot calls it each time a bot starts the package, and the way you write it decides whether that start goes through.
The default export
export default async function behavior(launch) { // ... } Asterobot checks three things before and after calling it:
Rule Error when it's broken index.js has a default export behavior module "library/my-first-script/1.0.0/index.js" has no unambiguous default export The default export is a function behavior module "library/my-first-script/1.0.0/index.js" default export is not callable The function returns a promise behavior module "library/my-first-script/1.0.0/index.js" default export must be async and return a Promise Declaring the function async takes care of the last rule. Its name doesn't matter, and an arrow function works as well: export default async (launch) => { ... }.
Asterobot calls the function once per start. Only the one in the package the bot plays is called: the default exports of its dependencies never are.
The launch object
The function receives one argument, launch:
Property Type Value launch.reason string Why the script is starting: "initial", "resume" or "reload". launch.generation BigInt 1n for the first script start on the bot's current game connection, then one more for every start on that connection. The same two values can be read anywhere in your code, not only in the entry function: session.launchReason and session.behaviorGeneration, from asterobot:bot.
Both start over with a new game connection: when a Full socket bot connects again, or when a Dofus client connects through a MITM bot (man-in-the-middle: the bot relays the game session of a Dofus client someone plays). launch.generation is a BigInt, so compare it with another BigInt, as in launch.generation === 1n.
What to do for each launch reason
launch.reason When a script starts with it "initial" On a Full socket bot's new game connection, as long as no script has started successfully on that connection yet. "resume" Every later start on the same connection, such as Play after Stop, or Run again. On a MITM bot, every start, the first one included. "reload" When a running script is replaced without being stopped first. No button in Asteroboard does that. Here is what each one asks of your script.
"initial"
Nothing has been sent on this game connection yet. On a Full socket bot the script identifies first, with the lines from Identify and run. Choosing a character, entering the game and answering what the server asks along the way are up to scripts too. @asterobot:base does all of it for a package that depends on it.
"resume"
The connection is already identified, either by a script that started on it before or, on a MITM bot, by the Dofus client. The character may already be in the game, in a fight or talking to a merchant. Your script didn't receive the messages that arrived while no script was running. What it can still know:
where the character stands, from currentMap(), which Asterobot follows for the whole connection; its level, kamas, life, pods, items and jobs, and whether it's in a fight, from asterobot:character, which Asterobot follows for the whole connection too; the fight under way, from state() of asterobot:fight, which the fight module follows from the fight's first message, or from the server handing the fight back after a reconnection; what an earlier script stored in the bot's memory next to session.connectionId, as Keeping data for the next run shows. Everything else, work out from what arrives from now on. On a MITM bot, someone also plays that character, and session.shared is true.
"reload"
For the game, this is the same as "resume". The difference is that the previous script was replaced without being stopped, so the messages that arrived during the switch are delivered to the new script instead of being lost. No button in Asteroboard starts a script this way. Handle it like "resume".
Most scripts only need to single out "initial":
import { session, botInfo } from "asterobot:bot"; import { send } from "asterobot:protocol"; export default async function behavior(launch) { // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } botInfo(`Start ${launch.generation} on this connection (${launch.reason})`); } When a start counts
Asterobot waits for the promise the function returns, and the start is complete once the function has returned without an error. Until that happens:
The Play button keeps loading, and Run in the editor doesn't show its Running on toast. The script already runs. The handlers you registered receive messages, Stop works, and the package's action buttons can be used. If the function fails before returning, the start doesn't count. On a Full socket connection where no start has succeeded yet, the next start is "initial" again, even if the failed one had already sent IdentificationRequest. A script that must never do something twice on a connection keeps the fact in the bot's memory instead: see What belongs to one connection. So set things up and return. Returning doesn't stop the script: the handlers you registered keep working until something stops it. Work that goes on for the whole session belongs in handlers, or in a loop you start without awaiting it, as Async and timing shows.
Top-level code
The code at the top level of your modules, outside any function, runs before the entry function is called, at every start, in every module index.js reaches. A dependency's top-level code runs before the code of the module that imports it. Top-level await works.
Asterobot also runs that code when it reads the package's settings, for the Package settings section of the bot's Settings tab and when a bot starts the package. That reading happens without a bot or a game session, so the built-in modules behave differently while it lasts:
During the reading What happens session Holds empty values: gameToken and language are "", and launchReason is "initial". values, from asterobot:parameters Is empty. on(), onTraffic(), intercept(), onChange() Return as usual, but the handler is never called. botInfo() and the other logging functions Do nothing. packages.info() Works as usual. send(), request(), wait(), sleep(), disconnect(), currentMap() and every function of asterobot:gamedata, asterobot:pathfinding and asterobot:verification Throw an error ending with not available while reading a package's declarations. character(), itemCount() and jobLevel() Answer as if nothing were known of the character: {}, undefined and undefined. A top-level await on something that can't settle during the reading fails with package awaits at import time, which cannot be answered without a Game session.
When the reading fails, Package settings shows Couldn't read this package's settings with the reason, and the script starts without its settings: every read from values gives undefined. Keep the top level to imports, constants, declarations and function definitions, and put everything else in the entry function.
When the start fails
What went wrong What you see The code can't be loaded: a syntax error, an import that doesn't resolve, a module limit exceeded Only a toast: Couldn't start the behavior after Play, or Couldn't run the package after Run. A rule of the default export is broken The toast, and The behavior stopped with an error at the top of the bot's page, with one of the texts above. The top-level code throws The toast, and the alert with a text starting with evaluate behavior module. export const parameters or export const actions isn't an object of objects, or an action has no run function The toast, and the alert with a text starting with read declarations of. The entry function throws, or an await in it rejects without a catch The toast, and the alert with a text starting with initialize behavior module. The top-level code or the function runs for 5 s without awaiting The toast, and the alert with a text containing JavaScript execution deadline exceeded. When the code can't be loaded, open the bot's Settings tab: reading the settings loads the same code, so Package settings shows Couldn't read this package's settings followed by the reason. That section describes the package the bot is set to play, which is the one Run gave it. It doesn't cover an inline script.
Errors covers what can go wrong once the script runs, and Play, stop and reload what each start keeps from the previous one.
  • 80 vues
Everything that starts or stops a script, what starts over and what's kept between two runs, and what happens when the connection drops or Asterobot restarts.
A script runs from the moment a bot starts it until something stops it, and it keeps little from one run to the next. States and controls describes the same buttons from the player's side.
Starting a script
Where Control What it does Package editor Run Saves the package, stops the script the chosen bot is running, sets this package as the bot's package, then starts it. Bot's page, or its row in Bot Manager Play Starts the bot's package, or the inline script loaded last. It's disabled until the bot is connected to a game. When the bot has nothing to play, it opens Load a behavior selection instead. Menu next to Play on the bot's page Load a package and play it Opens Load a behavior selection, then starts what you picked. Each start builds a new copy of the script from the files saved in the library at that moment, then calls its entry function. Saving in the editor doesn't change a script that's already running: click Run, or Stop then Play, to start the code you saved.
After a script has failed, Play starts a new copy right away. There's nothing to stop first.
Stopping a script
What stops it The game connection Error shown Stop Stays open No Load a new package, once you confirm the dialog Stays open No Unload package Stays open No Disconnect Closed No The end of a time slot in the bot's schedule, once the script is done or its time to finish runs out Closed No Delete bot Closed No An error in the script Stays open Yes, that error The script calls disconnect() Closed Yes, starting with behavior requested Game disconnect The connection closes on its own, for example when the game server ends it, or when the Dofus client of a MITM bot (man-in-the-middle: the bot relays the game session of a Dofus client someone plays) closes Gone Yes, starting with Game session closed An error shown means the alert The behavior stopped with an error at the top of the bot's page, and Failed in the Behavior column of Bot Manager. Errors explains each text.
Asterobot also connects a bot and plays its package by itself when a time slot in the bot's schedule starts. That start works like Play, with the same launch reasons.
Once a script is stopped, its handlers receive nothing more and its interceptors aren't asked anymore, so messages go through unchanged. The calls it was still waiting on reject: a sleep(), wait(), send(), request() or game data call in progress. After Stop, their error is behavior runtime closed. If a catch in your script logs errors, that line can appear in the bot's console right after you stop it.
When a schedule stops the script
A bot can have a schedule: weekly time slots during which Asterobot keeps it in the game. When a time slot ends, Asterobot doesn't cut the script off. It calls the handlers the script registered with onStopRequest(), waits until each has returned or its returned promise has settled, then stops the script and disconnects the bot. Each time slot can play its own package: when one hands over to a time slot that starts at the same minute and plays another package, the script is asked to stop the same way, and the next package plays without the bot leaving the game. It waits no longer than the schedule's Time to finish: from 0 to 30 minutes, 5 by default.
A script with no handler is stopped immediately, even in the middle of a fight, and so is every script when the time to finish is 0. A script doing something it shouldn't leave half done, such as a fight, a trade or a long trip, registers a handler that lets the current step finish and starts nothing new:
import { onStopRequest } from "asterobot:bot"; let stopping = false; onStopRequest(async () => { // The main loop checks this before starting anything new. stopping = true; }); The full example, with the step under way awaited, is on the onStopRequest() reference. Before starting something long, a script can also read session.scheduleEndsAt: the time the schedule will ask it to stop.
What starts over and what's kept
Each start is a fresh copy. Every variable in your modules starts again from its initial value, and top-level code runs again. The handlers, waits and sleeps of the previous run are gone, and the messages that arrived while no script was running are never delivered to the new one.
What survives lives outside the script: the game connection and the character's situation in the game, the package and version the bot is set to play, and the values on the bot's Package settings. launch.generation also keeps counting for as long as the connection stays open, and currentMap() still says where the character stands.
Keeping data for the next run
A script can't write files, and values is read-only. What it needs to find again goes in the bot's memory, from asterobot:bot:
import { memory, session, botInfo } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; const KEY = "@alice:chat-count/count"; export default async function behavior(launch) { on("ChatChannelMessageEvent", () => { // Only a count stored on this game connection goes on: after a // reconnection, it starts over. const stored = memory.get(KEY); const count = (stored?.connection === session.connectionId ? stored.count : 0) + 1; memory.set(KEY, { connection: session.connectionId, count }); botInfo(`${count} chat messages on this connection`); }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } The memory belongs to the bot, so it survives Stop, Play, Run and reconnections, and every package the bot runs shares it. Store session.connectionId next to anything that only holds for one game connection, as the count above does, and ignore a value whose id isn't the current one. Sessions shows why launch.reason can't tell you that. The bot's page shows the memory under Tools > Memory. Bot memory explains it in full: what it keeps, for how long, and how to keep a connection's state.
Changing the code or the version
Save only changes what the next start loads, as described above.
When you switch the bot to another installed version of its package from the bot's page, the running script also keeps going. The toast says Now set to version followed by the version, and adds The running behavior keeps going until you play or reload. when a script is running. The next start uses the new version.
Settings you apply from Package settings are the exception: they reach the running script without a restart. See Read settings.
The "reload" launch reason
A start can replace a running script without stopping it first. The new copy then takes over the game connection directly, and the messages that arrive during the switch wait for it instead of being lost. That start's launch.reason is "reload".
No button in Asteroboard starts a script this way, and there's no Reload button. Run, Load a new package and Load a package and play it all stop the running script first, so the script that follows starts with "resume", or with "initial" on a Full socket connection where no script has started successfully yet. Handle "reload" like "resume" in your code.
When the connection drops
Nothing restarts by itself, unless the bot's schedule is in one of its time slots: then Asterobot connects the bot again and plays its package after a short wait, as Schedule explains. Otherwise, when a Full socket bot connects again, or when a Dofus client connects through a MITM bot again, the bot shows Connected to game server with no script running. Click Play.
That start is the first one on a new connection: launch.generation is 1n, launch.reason is "initial" on a Full socket bot and "resume" on a MITM bot, and session.connectionId is one more than before. The bot's memory is still there, with what earlier connections stored.
After Asterobot restarts
When Asterobot starts again, every bot comes back disconnected and no script runs. Asterobot doesn't connect a bot or start a script on its own, except for a bot whose schedule is in one of its time slots, which it connects and plays immediately. Every bot's memory is empty again.
Each bot keeps the package and version it's set to play, whether that came from Run, from Load a new package with a library package, from the Add a bot dialog or from the bot's Settings. Its package settings are kept too. An inline script isn't: load it again after the restart.
Closing Asteroboard stops nothing, because scripts run inside Asterobot. Disconnect, delete and restart covers these cases from the player's side.
Next, Modules and imports.
  • 64 vues
The built-in asterobot modules, importing your own files and other packages, versioned imports, and what scripts can't import or use.
Every file of a package is an ES module. Scripts reach Asterobot's functions, their own files and other packages through import statements, and through nothing else.
The built-in modules
Module Exports Reference asterobot:protocol send, request, wait, on, off, onTraffic, intercept asterobot:protocol asterobot:bot session, currentMap, memory, disconnect, onStopRequest, offStopRequest, botDebug, botInfo, botWarn, botError asterobot:bot asterobot:timers sleep asterobot:timers asterobot:gamedata text, record, table, find, search, query asterobot:gamedata asterobot:console console, debug, log, info, warn, error asterobot:console asterobot:runtime packages, whose packages.info() describes a package asterobot:runtime asterobot:parameters values, onChange, offChange asterobot:parameters asterobot:pathfinding findPath, cellToPoint, pointToCell, distance, direction asterobot:pathfinding asterobot:movement move, changeMap, useElement, travel, route, stopTravel, trip, answerMapLoads, findMaps, mapAt, mapInfo, nearest, nearestWith, mapsWithin, reachable asterobot:movement asterobot:verification commitment, publicKey, proof asterobot:verification asterobot:character character, items, itemCount, jobLevel asterobot:character asterobot:fight enable, disable, enabled, setProfile, profile, setSpeed, speed, setFights, fights, state, onFight, offFight, onDecision, offDecision asterobot:fight asterobot:chat say, whisper, pause, resume, setVariable, variables, setDryRun, dryRun asterobot:chat asterobot:alarms raise, onAlarm, offAlarm asterobot:alarms These modules only have named exports. Import each function by its name:
import { on, send } from "asterobot:protocol"; import { record, text } from "asterobot:gamedata"; There's no default export and no object grouping a module's functions: import gamedata from "asterobot:gamedata" and import { gamedata } from "asterobot:gamedata" both fail. If you prefer one name for a module, use a namespace import: import * as protocol from "asterobot:protocol", then protocol.send(...). asterobot:console is the one module that also exports an object, console, so that console.log() reads the way it does elsewhere.
asterobot:parameters is scoped to the package that imports it. In your package, values holds your package's settings. Imported from a dependency, it holds the dependency's own. Settings in dependencies explains how those show on the bot's page.
A built-in name that doesn't exist, such as asterobot:fs, keeps the script from loading. The reason contains built-in module "asterobot:fs" is not registered.
Note
Coming soon. Asterobot has room for extension modules, named asterobot:extension/ followed by a name, which would add functions of their own. None is available yet, so importing one fails. See Extension modules.
Importing your own files
// index.js import { formatChat } from "./lib/format.js"; // lib/format.js export function formatChat(channel, sender, content) { return `[${channel}] ${sender}: ${content}`; } A relative import follows these rules:
It starts with ./ or ../, and is resolved from the folder of the importing file. It names the file completely, extension included: ./lib/format.js, not ./lib/format. It stays inside the package. ../ can't lead to another package's files. It never starts with / and never contains a backslash. A path without ./, such as lib/format.js, isn't a file path: Asterobot reads it as the name of a package your package depends on, and fails with has no locked dependency for "lib/format.js".
When an import breaks a rule, the script doesn't load, and the toast gives no reason. The reason contains one of these texts:
Problem Error The file doesn't exist, or the extension is missing module "library/my-first-script/1.0.0/lib/format" imported by "library/my-first-script/1.0.0/index.js" is not installed The path leaves the package relative import "../other.js" escapes package root "library/my-first-script/1.0.0" The path starts with / absolute module specifier "/lib/format.js" is forbidden The editor underlines an import of a file that doesn't exist as an error, and the bot's Settings > Package settings shows the full reason, as The entry function explains.
Each module runs once per start, however many files import it, so a variable at the top level of lib/format.js is shared by every file that imports it. Each bot has its own copy: two bots playing the same package never share a variable.
Importing another package
To import another package from your library, first declare it in the dependencies of your asterobot.json, as Dependencies describes. Then import it by the name you declared:
import { formatChat } from "chat-tools"; import { findSpots } from "@alice:mining"; You receive what the dependency's index.js exports, and only that. A path into the dependency, such as "chat-tools/lib/format.js", isn't supported and fails with has no locked dependency for "chat-tools/lib/format.js". A package that wants to share something exports it from its index.js.
A name your package doesn't declare fails the same way. That's also what happens with "lodash", "fs" or any other npm or Node.js module name.
Versioned imports
An import can carry a version after an @:
Import What it picks "chat-tools@1.2.0" Version 1.2.0 of chat-tools. When your manifest declares two versions of the same package side by side, this is how you pick one. "chat-tools@^1.0.0" The highest version of chat-tools that matches the range "chat-tools@latest" The highest version of chat-tools A range or latest only chooses among the versions of that package that are already part of what the bot runs, brought in by your manifest or by a dependency's. It never installs anything. When none matches, the script doesn't load, and the reason contains no installed version of "chat-tools" satisfies "^1.0.0".
What a script can't import
Not possible What happens import() called as a function The call rejects when it runs, with dynamic import() is not supported by this Asterobot runtime. The editor warns about it. require() It doesn't exist, and calling it throws a ReferenceError. npm packages and Node.js modules There's no npm. A name like "lodash" is read as an undeclared dependency. JSON, text or image files A package only holds .js and .mjs modules, and its fight profiles, which are data: importing one fails with is a data file, not a module. There's no other file to import. No browser or Node.js globals
Scripts have the standard JavaScript built-ins, such as Math, JSON, Date, Map, Promise and BigInt, and the functions of the asterobot: modules. The globals of a browser or of Node.js don't exist:
Global you might expect In a script console Import botInfo() and the rest from asterobot:bot, or console from asterobot:console. setTimeout(), setInterval(), setImmediate() await sleep(milliseconds) from asterobot:timers fetch(), XMLHttpRequest, WebSocket Not available window, document, localStorage Not available process, Buffer, require, __dirname Not available structuredClone(), TextEncoder, TextDecoder, queueMicrotask() Not available The editor knows this: it marks these names as errors. JavaScript support lists the language features scripts have and the few they don't.
Next, Async and timing.
  • 75 vues
How a bot runs its script one piece at a time, why await keeps the bot responsive, the 5 s limit, sleep(), loops and the order handlers run in.
A bot runs its script one piece of code at a time. Once you know how those pieces are scheduled, the 5 s limit, long loops and the order of handlers stop being surprises.
One piece of code at a time
Each bot runs its own copy of the script, and bots run side by side without sharing anything. Inside one bot, a single piece of your code runs at any moment: the entry function, a handler, or the rest of an async function after an await. The next piece only starts once the current one has finished or reached an await.
Everything ready to run waits in the bot's queue, and is handled in the order it arrived:
a message, for your handlers and your wait() calls the result of a request(), a game data call or a movement a sleep() that's over, or a wait() whose timeout ran out settings applied from Package settings, or an action button someone pressed a question for your interceptors about a message on its way the end of the game connection, after every message that arrived before it await keeps the bot responsive
When your code reaches await on something that hasn't settled yet, that piece of code ends there and the bot goes back to its queue: other messages are handled and other handlers run. Once the awaited thing settles, the rest of your function continues as a new piece.
That's what lets a handler await wait() for an answer while the bot keeps receiving messages. A message resolves the wait() calls open when the bot handles it, and send() resolves before any answer can be handled, so a wait() opened right after await send() always gets the answer: Waiting shows how.
Code that runs without awaiting does the opposite. As long as it runs, nothing else happens for that bot: no handler runs, and messages wait in the queue, the ones your interceptors are asked about included (see Intercepting). Nothing is lost or skipped: once your code awaits, the bot handles them all, in order. A slow PC makes a script later, never different. On a MITM bot, though, a message waiting for your interceptors is the player's, who feels the wait as lag.
The 5 s limit
A piece of code may run for 5 seconds at most. Past that, Asterobot interrupts it, and the script stops with an error containing JavaScript execution deadline exceeded. The bot stays connected.
The limit is there to stop code that never ends, such as an endless loop. It sits far above what a script's code takes, so a script that runs slower on a slower PC isn't stopped for it.
The limit applies to each of these on its own:
the top-level code of your modules, at each start the entry function, up to its first await each call of a handler registered with on(), onTraffic() or onChange() the code that continues after an await. When several awaited things settle together, the code that continues from all of them shares one 5 s. An interceptor or an action button's run that goes over the limit doesn't stop the script: the interceptor's message goes through unchanged, and the action is reported as failed.
Only time spent running counts. Waiting on an await doesn't: a sleep(10_000) or a five-second wait() is fine. What goes over is work done in one go, such as a long loop, a big calculation, or thousands of synchronous calls like record() and text() in a row.
To get through a long job, let the bot breathe now and then with await sleep(0), so its messages and interceptors aren't kept waiting. Each part then runs as its own piece, with its own 5 s:
for (let i = 0; i < ids.length; i++) { handle(ids[i]); // Every 500 items, let the bot handle its queue. if (i % 500 === 499) await sleep(0); } For game data, a single find() or query() often replaces thousands of record() calls, and it does its work outside your code's 5 s. See Game data overview.
sleep()
import { sleep } from "asterobot:timers"; await sleep(1500); sleep() is the only timer scripts have. It takes a number of milliseconds, and 0 is allowed. Its promise resolves once the time has passed, and your code continues when the bot reaches it in its queue, so a busy bot resumes a little late.
When sleep() rejects with The duration is negative, infinite or not a number timeout/milliseconds must be a finite positive Number 128 sleeps are already in progress behavior resource limit exceeded: maximum timers reached The script stops The reason it stopped, such as behavior runtime closed after Stop There's no setTimeout(). To do something later without holding up the function you're in, call an async function without awaiting it, and let that function await sleep() first. Catch the errors inside it: a rejected promise that nothing catches stops the script.
Loops
Start long loops without awaiting them
A start only counts once the entry function has returned, as The entry function explains. A while (true) loop awaited inside the entry function keeps the Play button loading for good, and keeps the next start "initial". Start the loop from the entry function without awaiting it:
import { session, botInfo, botWarn } from "asterobot:bot"; import { send } from "asterobot:protocol"; import { sleep } from "asterobot:timers"; async function doOneRound() { botInfo("One more minute"); } async function everyMinute() { while (true) { try { await doOneRound(); } catch (error) { botWarn("This round failed:", String(error)); } // Outside the try: once the script stops, sleep() rejects, and that // rejection is what ends the loop. await sleep(60_000); } } export default async function behavior(launch) { // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } // Not awaited: the start only counts once this function has returned. everyMinute(); } Keep the await sleep() outside the try. When the script stops, every pending sleep() rejects, and so does every later call. A catch around the sleep() would catch that rejection and send the loop round again instead of ending it.
To let people pause such a loop, read a setting at each round, as in if (values.enabled) await doOneRound();. Don't skip the sleep() when the setting is off: a loop that never awaits runs into the 5 s limit. Read settings covers values.
React to messages instead of polling
Most scripts don't need a loop at all. A handler registered with on() runs each time a message arrives, and wait() pauses a function until one does. Listening and Waiting cover both.
The order things run in
When a message arrives:
Your interceptors are asked first, in the order they were registered, and the first decisive answer wins. A message they drop never reaches the next steps. See Intercepting. Each handler matching the message is called, in the order it was registered. Handlers registered with on() only receive inbound messages, and those registered with onTraffic() receive both directions. The wait() calls matching the message resolve, the oldest first. The code that continues because of this message, such as a function awaiting one of those wait() calls, runs after every handler has been called. Handlers don't wait for each other. An async handler runs until its first await, then the next handler is called, and the rest of the first one runs later, possibly after other messages. A handler that throws stops the script: see Errors.
Messages are handled in the order they arrive. The onChange() handlers of settings are the exception to any ordering: they have no fixed order.
When the bot falls behind
The queue has no fixed size. If your code keeps the bot busy while messages keep arriving, which happens quickly on a busy MITM session (man-in-the-middle: the bot relays the game session of a Dofus client someone plays), the messages wait, and the bot handles every one of them, in order, once your code awaits. Only when more than 8 MB of game messages are waiting does the script stop, with the script fell too far behind the game: the PC can't keep up with the script at all. Keep handlers short, and don't do heavy work on every message. The bot doesn't even read a message nothing listens for, so only your own handlers cost time.
When the game connection closes, the bot first handles every message that arrived before, then the script stops.
A few calls have caps of their own. At most 32 request(), game data and movement calls can be in progress at once, all of them counted together, and the next one rejects with behavior resource limit exceeded: maximum pending operations reached. A call frees its place once your code receives its result, so whether a script reaches the cap depends on what it does, never on how fast the network answers. send() isn't counted: it resolves as soon as its message is in line. At most 128 wait() calls can be open, and the next one rejects with behavior resource limit exceeded: maximum waits reached. Limits lists every limit.
Next, Errors.
  • 81 vues
Where script errors show up, what stops a script and what doesn't, how to read the error texts, and habits that keep a script running.
Most mistakes in a script stop it until its next start. Knowing which ones do, where they show up and how their texts are built saves a lot of guessing.
Where errors show up
Where What it tells you The behavior stopped with an error, at the top of the bot's page The script started, then stopped because of an error. The alert shows the whole text, with Copy and Dismiss. The bot's Behavior badge, and its column in Bot Manager, say Failed. Couldn't start the behavior after Play, or Couldn't run the package after Run The start failed. When the code couldn't even be loaded, this toast is the only sign, and it gives no reason. Couldn't load the selection, after confirming Load a behavior selection The package couldn't be loaded on the bot, for example because a dependency isn't installed or the package isn't compatible with your Asterobot. The editor's error badge A problem found before running, such as a syntax error, an import of a file that doesn't exist, or a misspelled function. When the code couldn't be loaded, the bot's Settings tab gives the reason under Package settings, as The entry function explains.
The alert goes away when the script starts again, when you load or unload a package, or when the bot disconnects. Dismiss only hides the text you dismissed: a different error shows up again.
What stops a script
The text in the alert starts with where the failure happened:
What happened The text starts with The top-level code of a module threw during the start evaluate behavior module "library/my-first-script/1.0.0/index.js": The parameters or actions export isn't an object of objects, or an action has no run function read declarations of "library/my-first-script/1.0.0/index.js": The entry function threw or rejected, or anything else failed before it returned initialize behavior module "library/my-first-script/1.0.0/index.js": A handler registered with on() or onTraffic() threw Game traffic handler failed: A function you passed to on() or wait() to pick messages threw evaluate protocol handler selector: or evaluate protocol wait selector: An onChange() handler threw parameters.onChange handler followed by a number An onStopRequest() handler threw onStopRequest handler followed by a number A promise rejected and nothing caught it, which includes an error thrown in an async handler or an async interceptor unhandled Promise rejection: The code that continued after an await went over the time limit drain JavaScript Promise jobs: A few failures have their own text, whichever of the beginnings above comes first:
What happened The text contains A piece of code ran for more than 5 s without awaiting JavaScript execution deadline exceeded More than 8 MB of game messages were waiting for the script: the PC can't keep up with it the script fell too far behind the game On a MITM bot, Asterobot itself fell so far behind the game that it had to skip messages game traffic lost: Asterobot fell behind the game and missed messages A message the script sent couldn't be written send followed by the message's name The game connection closed on its own, once every message before the close was handled Game session closed The script called disconnect() behavior requested Game disconnect The default export has its own three errors, listed in The entry function.
Reading an error text
After the beginning comes the error itself.
An error your code threw shows as its type and message, such as Error: no zaap on this map or TypeError: gamedata.record id must be an integer. When it was thrown by a handler or a selector function that isn't async, at follows, with the file, the line and the column where it happened.
An error from an Asterobot function shows Asterobot's text. Promise rejected: in front of it means an await rejected. For example, sending token instead of ticketKey while the script starts gives:
initialize behavior module "library/my-first-script/1.0.0/index.js": Promise rejected: IdentificationRequest.token: unknown protobuf field Error messages lists the texts of Asterobot's functions, with their causes.
What doesn't stop a script
What happens What you see instead An action button's run throws or rejects A toast named after the action, such as recall failed, with the error text. The script keeps running. An interceptor that isn't async throws, or returns a payload that can't be built That interceptor makes no decision: the next one is asked, and when none decides, the message goes through unchanged. The bot's console shows the error. An async interceptor never decides, and an error in it stops the script. See Intercepting. A promise rejects and your code catches it Whatever your catch does record() finds no record, or text() finds no text They return undefined. off() or offChange() receives a handler that's already removed Nothing The logging functions, botInfo() and the others, never throw.
Functions that throw right away
A few functions throw as soon as they're called, instead of returning a promise that rejects. A try/catch catches them without any await:
Function Throws when on(), onTraffic(), intercept(), onChange(), onStopRequest() The handler isn't a function, the selector is empty or neither a name nor a function, or 256 handlers are already registered: maximum behavior handlers reached. on(), onTraffic() and intercept() count together, and onChange() and onStopRequest() each have their own 256. text() The key is neither a string nor a whole number record() The table name is empty, the id isn't a whole number, or the table doesn't exist packages.info() The name is empty, or matches no package the bot runs Keep a script running
A handful of habits prevent most failures, and every sample in these docs follows them:
Put each await of a handler inside try/catch. An error that escapes a handler, async or not, stops the whole script. Check that traffic.payload exists before reading its fields: a message Asterobot couldn't decode has no payload. traffic.payload?.content checks and reads at once. Give every wait() a timeout. Without one, a wait that never matches stays open until the script stops. A wait opened right after await send() always gets its answer, as Waiting shows. When you start an async function without awaiting it, catch its errors, inside it or where you start it: refreshLoop().catch((error) => botWarn("Loop stopped:", String(error)));. Check what lookups return. undefined means nothing was found, or that the bot's game version has no game data. Convert BigInt ids with Number() before you pass them to record() or text(). Log errors as String(error). Logging and debugging explains why. On a Full socket bot, make sure nothing after the identification can fail while the script starts. Otherwise the next start is "initial" again, and the script identifies a second time. This handler reads chat lines starting with !json and parses what follows. Without the try, anyone typing !json { in a channel your character reads would stop the script:
import { session, botInfo, botWarn } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; export default async function behavior(launch) { on("ChatChannelMessageEvent", (traffic) => { const content = traffic.payload?.content; if (!content?.startsWith("!json ")) return; try { const data = JSON.parse(content.slice(6)); botInfo("Parsed:", JSON.stringify(data)); } catch (error) { // An error that escapes a handler stops the whole script. botWarn("Not valid JSON:", String(error)); } }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } Limits lists every limit and what happens past it. Next, Logging and debugging.
  • 73 vues
Where each logging function writes, how logged values come out, how to keep the game ticket out of logs, and a routine for finding out why a script misbehaves.
When a script doesn't do what you expect, its own output is usually the quickest way to find out why, once you know which of the two consoles it went to.
Two places to write
Functions Module Where the lines appear botDebug(), botInfo(), botWarn(), botError() asterobot:bot The bot's Console tab, as Script lines, at every level debug(), log(), info(), warn(), error(), also available on the console object asterobot:console Server > Console, and the terminal Asterobot runs in The bot's console
This is the one you want while you write a script. Your lines show at their level, among Asterobot's own lines about the bot and the game chat. To follow your output, untick Asterobot under Channels, at the left of the chat box, and type part of your lines in Filter.
Asterobot keeps the last 200 lines of each bot's console, and that's what you see when you open the tab. A script that logs on every message pushes the other lines out quickly, so remove chatty lines once you're done. Console describes the tab.
Asterobot's log
asterobot:console writes to the log of Asterobot itself, where log() is the same as info(). The lines don't say which bot wrote them, so when several bots play the same package, you can't tell their lines apart.
Two settings decide which levels reach each place:
Where Setting Default Server > Console Server level, on that page, which is the logger.webLevel setting INFO The terminal logger.cliLevel INFO Both leave out debug lines by default. Set Server level to DEBUG and they show from then on, without restarting Asterobot. Status and console describes that page.
How values come out
Each call joins its values with spaces. Asterobot turns the values into text itself, not with JavaScript's String(), so some of them look different from a browser's console:
You log The line shows "Bouftou" Bouftou 42, 1.5, true 42, 1.5, true 123456789012n 123456789012 undefined or null <nil> [1, 2, 3] [1 2 3] { pods: 5, kamas: 10 } map[kamas:10 pods:5] An Error map[] So log an error as String(error) or error.message, which give its text. For objects and arrays, JSON.stringify() makes a more readable line, but it throws a TypeError with the message Do not know how to serialize a BigInt as soon as it meets a BigInt, and message payloads often hold some. Convert them on the way:
function toJson(value) { return JSON.stringify(value, (key, item) => (typeof item === "bigint" ? item.toString() : item)); } botInfo("Payload:", toJson(traffic.payload)) then prints the payload with its 64-bit numbers written as digits.
Keep the game ticket out of your logs
Caution
Never log session.gameToken. It's the ticket to the bot's game session, and anyone who reads it could take that session over.
The ticket also travels in the ticketKey field of IdentificationRequest. A handler registered with onTraffic() receives the messages your own script sends, so a script that logs every payload writes the ticket to the console when it identifies. Leave that message out whenever you log traffic, as the sample below does.
A debugging routine
Before running, look at the editor's error badge. A misspelled function or a field a message doesn't have is cheaper to fix there. In the editor, pick a MITM bot and click Run. On a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), you can make things happen in the game yourself and watch the script react. If the toast says Couldn't run the package and no alert appears on the bot's page, the code didn't load. Open the bot's Settings tab: Package settings shows the reason. Open the bot's Console tab and untick Asterobot under Channels. Log a line when the script starts, such as botInfo("Started:", launch.reason), to be sure it runs at all. If The behavior stopped with an error appears, click Copy and look at how the text starts. Errors says what each beginning means. When a handler never seems to run, check what really arrives: log the type of every message for a moment with the sample below, or use the Network tool as React to messages shows. When a lookup returns nothing, try it on the game version's Game data tab first. SQL queries shows how to try a query there. When a script started again acts as if it had never run, or as if it had already done something it hasn't, look at Tools > Memory: it shows what the bot's scripts stored, and when each key last changed. A value stored for an earlier game connection is the usual cause: see Keeping data for the next run. Fix the code and click Run again: it saves your change and starts it. This handler writes the direction and the name of every message to the bot's console:
import { session, botDebug } from "asterobot:bot"; import { onTraffic, send } from "asterobot:protocol"; export default async function behavior(launch) { onTraffic((traffic) => { // Its payload holds the game ticket, which must never reach a log. if (traffic.type === "IdentificationRequest") return; botDebug(traffic.direction, traffic.type || traffic.wireType); }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } traffic.type is empty for a message Asterobot has no readable name for, so the line shows its name on the wire instead. Traffic describes every property. A busy session fills the console within seconds, so run this only while you look.
Next, JavaScript support.
  • 68 vues
The JavaScript features scripts can use, the three constructs they can't, the built-in objects that are missing, the editor's warnings, and JSDoc types.
Scripts run modern JavaScript, with three constructs left out and a few built-in objects missing. JSDoc comments make what the editor tells you more precise.
What works
The syntax of JavaScript up to ES2022 works, apart from the three constructs in the next section. That includes:
classes, with private fields such as #count and static blocks async functions and await, with Promise.all(), Promise.allSettled() and Promise.any() generators (function*) and for...of optional chaining ?., ?? and ??=, spread and rest, destructuring, template literals BigInt, and typed arrays such as Uint8Array top-level await and import.meta numeric separators, as in 60_000 recent methods such as Array.prototype.at(), flatMap(), Object.hasOwn(), String.prototype.replaceAll() and matchAll() Every file is an ES module, so it runs in strict mode: assigning to a variable you never declared throws, for example.
TypeScript isn't supported. A package's files must end in .js or .mjs.
Three constructs that don't work
Construct What happens Async generators, async function* The file can't be loaded, so the script doesn't start. for await (... of ...) The file can't be loaded, so the script doesn't start. import() called as a function The file loads, but the call rejects when it runs, with dynamic import() is not supported by this Asterobot runtime. Each has a plain replacement. Instead of an async generator, write an async function that returns an array. Instead of for await, put the await in the body of a regular loop:
for (const pending of promises) { const value = await pending; } Instead of import(), import the module at the top of the file with a regular import statement.
The editor's warnings
The editor puts a warning on each of the three, in every file of the package, whether the file is open or not:
On Warning async function* Says that async generators aren't supported for await ( Says that for await...of isn't supported import( Dynamic import() is refused at runtime - a behavior's imports must be static. Warnings don't count in the error badge, and they don't stop you from saving or running, but the script won't work while one of these constructs is there. The editor finds them by searching the text, so the same words in a comment or a string get a warning too.
The editor also checks your code the way TypeScript checks JavaScript. It knows the ES2022 built-in objects, the asterobot: modules and the game's messages, and no browser or Node.js global, so document, setTimeout() or require() are marked as errors. The check isn't strict: a value that could be undefined isn't flagged, for example.
Built-in objects that don't exist
The editor completes three built-in objects that scripts don't have: Intl, WeakRef and FinalizationRegistry. Using one throws a ReferenceError when the code runs. Without Intl, format numbers and dates yourself.
Modules and imports lists the browser and Node.js globals that are missing too.
JSDoc types
Scripts are plain JavaScript, but the editor reads JSDoc comments and uses their types for completion and checks. These types can be used in any file, without an import:
Type What it describes BehaviorLaunch The launch object the entry function receives ProtocolTraffic A traffic object. ProtocolTraffic<GameMessageResults["ChatChannelMessageEvent"]> also describes its payload. GameMessageResults The payload of each message as a script receives it, by message name GameMessagePayloads The payload of each message as send() accepts it, by message name BehaviorParameters An export const parameters object BehaviorActions An export const actions object The message types are part of the typings behind the editor's IntelliSense badge. When the badge says the typings couldn't be loaded, the editor doesn't know any message name.
A handler you pass to on() with a message name already gets a typed payload, without any comment. JSDoc helps most in your own functions:
import { session, botInfo } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; /** * @param {ProtocolTraffic<GameMessageResults["ChatChannelMessageEvent"]>} traffic * @returns {string} */ function formatChat(traffic) { const payload = traffic.payload; if (!payload) return "(a chat line Asterobot couldn't decode)"; return `[${payload.channel}] ${payload.senderName}: ${payload.content}`; } /** @param {BehaviorLaunch} launch */ export default async function behavior(launch) { on("ChatChannelMessageEvent", (traffic) => { botInfo(formatChat(traffic)); }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } In the same way, put /** @type {BehaviorParameters} */ on the line above export const parameters to have each setting's fields checked. Declare settings describes them.
Next, Bot memory shows how a script keeps data from one run to the next.
  • 72 vues
Every property of the traffic object a script receives, what's left of a message Asterobot can't decode, raw bytes and sequence numbers.
Every message a script receives comes as a traffic object: in on() and onTraffic() handlers, from wait() and request(), and in interceptors.
What a traffic object looks like
A chat line on the general channel reaches a handler like this:
{ sequence: 1842n, direction: "inbound", kind: "event", uid: -1, type: "ChatChannelMessageEvent", wireType: "abc", // an example: the real name is scrambled and changes between game versions typeUrl: "type.ankama.com/abc", unknown: false, payload: { channel: "GLOBAL", senderName: "Airelle", senderCharacterId: 123456789012n, content: "Anyone up for a dungeon?", // ...and the other fields of the message }, rawAny: new Uint8Array([/* the bytes of the fields */]), } Properties
Property Value What it tells you sequence BigInt The message's place in the bot's traffic. See Sequence numbers. direction "inbound" or "outbound" "inbound" for a message travelling towards the client, "outbound" for one travelling towards the game server. kind "event", "response", "request" or "unknown" Which kind of message it is. See Direction and kind. uid number The number that pairs a response with its request. -1 for events, for requests sent without one, and for the responses to those. See Direction and kind. type string The readable name, such as ChatChannelMessageEvent. An empty string when Asterobot has no name for the message. wireType string The scrambled name the message carries on the wire. It changes from one game version to the next. typeUrl string The full type identifier on the wire: type.ankama.com/ followed by the scrambled name. payload object, or absent The message's fields, when Asterobot could decode them. Payloads explains how each kind of field reads. unknown boolean true when there's no payload. decodeError string, or absent What went wrong while decoding the message. It can be present next to a payload. rawAny Uint8Array The bytes of the message's fields, exactly as they arrived. Always present. It's empty when there were no bytes, as for a message without fields. The editor marks every property as read-only. Changing one in a handler only changes what the next handlers see: One object for every handler explains why.
Direction and kind
Message direction kind uid Something the server announces "inbound" "event" -1 The server's answer to a request that carried a uid "inbound" "response" The request's uid The server's answer to a request sent without one, such as GuildWeeklyNextResetResponse "inbound" "response" -1 A request sent with send(), from the Network tool, or by the Dofus client of a MITM bot "outbound" "request" -1 A request sent with request() or with send(..., { correlated: true }), or one the Dofus client of a MITM bot numbers "outbound" "request" 0 or more A message sent to the client, by a script with { to: "client" } or with To the client in the Network tool "inbound" "event" -1 "unknown" exists in the typings, but Asterobot never delivers a message it couldn't classify, so scripts don't receive it. When a message is so damaged that Asterobot can't even tell its kind, a Full socket bot's game connection closes, and the script stops with an error that starts with Game session closed:. A MITM bot passes such a message on without delivering it to scripts.
A numbered request doesn't always get a response: the Dofus client numbers the answers to the server's proof of identity and its latency report, and nothing answers them. See Messages sent with a uid.
On a Full socket bot, the game server isn't expected to send requests. If one arrives, it's still decoded, and its decodeError is unexpected inbound game envelope.
Messages Asterobot can't decode
When Asterobot can't decode a message, its traffic object has no payload, unknown is true, type is empty, and decodeError says why:
decodeError starts with Why obfuscated protobuf wire name is not mapped: Asterobot has no readable name for this message. This is the common case: only part of the protocol has readable names so far. unmarshal game Any The name is known, but the bytes don't match the message's definition. invalid Ankama protobuf type URL: The message's type identifier isn't valid. wireType is empty too. game envelope has no Any payload The message carried nothing at all. wireType and typeUrl are empty too. With an empty type, a handler registered with a message name never receives these messages. To catch them, use the "*" selector, a function selector, or onTraffic(), and test traffic.unknown. Listening describes selectors.
What stays usable is wireType and rawAny. wireType tells two unknown messages apart, but only within one game version, and a message without a readable name can't be sent.
The bot's Network tool sometimes shows fields for such a message, with the Schema badge, because it also decodes messages under their scrambled names. Scripts don't get that: for a script the message stays unknown, with only its bytes. Finding messages explains the badges.
Raw bytes
rawAny holds the bytes of the message's fields before decoding, whether decoding worked or not. It's a copy, so changing it changes nothing. It doesn't include the part around the fields that states the message's kind and name, so it isn't something you could send back as it is. Asterobot has no function that decodes these bytes for you.
For an unknown message, the bytes are all there is. This script writes the start of each unknown message to the bot's console:
import { session, botDebug } from "asterobot:bot"; import { onTraffic, send } from "asterobot:protocol"; export default async function behavior(launch) { onTraffic((traffic) => { if (!traffic.unknown) return; // Sixteen bytes keep each console line readable. const start = Array.from(traffic.rawAny.slice(0, 16), (byte) => byte.toString(16).padStart(2, "0")); botDebug(`${traffic.direction} ${traffic.wireType}, ${traffic.rawAny.length} bytes: ${start.join(" ")}`); }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } Sequence numbers
sequence counts the messages of one game connection, both directions together, starting at 1n. A new game connection starts again from 1n.
The numbers a script sees aren't always consecutive or in order:
A message an interceptor drops keeps its number and never reaches handlers, which leaves a gap. On a MITM bot, Asterobot skips messages for scripts when it can't keep up with the traffic, which also leaves gaps. Sessions explains when that happens. A message a script sends from inside an interceptor goes out before the message being decided, and reaches handlers first, while carrying a higher number. Handlers receive messages in the order they went through the bot. Use sequence as a label, not to sort messages.
On a MITM bot, the traffic object request() resolves with has sequence set to 0n. Handlers receive the same response with its real number.
One object for every handler
When several handlers and waits receive the same message, they all get the same object. A handler that changes traffic.payload changes what the handlers after it see, and nothing on the wire. To change a message on its way through the bot, use an interceptor, which receives its own copy: see Intercepting.
Next, Finding messages shows how to find the name and fields of the message you need. The asterobot:protocol reference lists every function that receives traffic.
  • 94 vues
Find the message a script needs and the names of its fields with the Dofus protocol reference, the editor's completion and the bot's Network tool.
Before writing a handler, you need the name of the message and the names of its fields. Three tools give you those, and each one is best at a different question:
Tool Best for The Dofus protocol reference Learning what a message is for and what each field holds. The editor's completion Writing names and fields correctly while you type. The bot's Network tool Seeing which messages the game really uses for something, with real values. Readable names and wire names
Every traffic object carries two names. type is the readable name, such as ChatChannelMessageEvent, and wireType is the scrambled name the message has on the wire. Scripts should rely on type only:
Wire names change with game versions. A script that compares wireType breaks at the next patch. on(), wait() and intercept() compare a name with type, and send() and request() take a readable name. None of them accept a wire name. Readable names come from Asterobot, which keeps them when the wire names change. An Asterobot update can still rename a message when it finds a better name for it: After a game update explains what to watch. Only part of the protocol has readable names so far. A message without one reaches scripts with an empty type, as described in Traffic.
Field names
The game's definitions and the protocol reference write field names with underscores, like sender_name. Scripts read them in camelCase, like senderName: each underscore is removed and the letter after it is capitalized. When sending, a script can use either spelling, but not both for the same field in one payload.
Some fields and enum values still have short scrambled names, such as fzbl. Asterobot doesn't know what they mean yet, and their names can change, so don't build a script on them.
The Dofus protocol reference
The Dofus protocol reference on asterobot.net describes the game's messages: what each one is for, its fields, and what they mean. Start there when you know what you want to do but not which message does it, and come back to it whenever a field's meaning isn't obvious from its name.
Completion in the editor
The package editor knows every message the Asterobot it's connected to has a readable name for. Hover IntelliSense in the editor's top bar to see how many.
With those names, the editor:
completes the fields of the payload you pass to send() and request(), and marks a field the message doesn't have, or a value of the wrong type, as an error; knows the fields of traffic.payload in on(), wait() and intercept() handlers that name a message, so reading them completes too; refuses two members of the same oneof in one payload. Payloads explains oneofs. The editor doesn't check the message name itself. A misspelled name in send() only fails when the script runs, with semantic protobuf message is not mapped: followed by the name. In on() or wait(), a misspelled name is accepted and never matches anything, but the editor then knows no field of traffic.payload, and reading one is marked as an error.
Errors add up on the editor's error badge. They don't stop you from saving or running the package. When the typings can't be loaded, hovering IntelliSense says Couldn't load the typings - editing works, completion doesn't.
The typings describe the Asterobot the editor is connected to. After updating Asterobot, open the editor again to get the names of the new version.
The Network tool
On a bot's page, Tools > Network shows the messages going through the bot. Network traffic describes the whole tool. Here's what matters when you're looking for a message.
Choose what to watch
Settings lists every message this Asterobot has a readable name for, which are the names a script can use in on() or send(). Traffic shows nothing until you tick some of them and click Apply.
The filter box narrows the list, and the select button next to it ticks every message the filter shows. Also mirror unrecovered messages adds the messages that have no readable name, the ones scripts receive with an empty type. What you select only changes what Asteroboard shows. A script receives every message, selected or not.
Caution
Traffic shows every field of the messages you select, secrets included. With IdentificationRequest selected, it shows ticketKey, the ticket to the bot's game session: the one your script sends on a Full socket bot, or the one your Dofus client sent on a MITM bot. Don't share screenshots of it.
Read a message
In Traffic, each row shows the message's name, In or Out, its sequence number after #, and its uid when it has one. Click a row to see its fields. They have the names a script reads, in camelCase. The one difference is 64-bit numbers: they show in quotes here, while a script receives them as BigInt values.
Some rows are marked:
On the row Meaning What a script receives A name in italics The message has no readable name, so the row shows its wire name. The message with an empty type. Schema No readable name, but the game's definitions describe the message, so the tool decodes its fields under their scrambled names. unknown: true and the bytes in rawAny, without the fields. Raw Neither the name nor the fields are known, so the row shows the bytes. unknown: true and the bytes in rawAny. Decode error Decoding reported a problem. Open the row to read it. The problem in decodeError. The search box above the list filters rows. Type a word, or name a field, as in name:Ping direction:inbound. A - in front of a term excludes the rows that match it.
Messages your script sends show up too, as long as they're selected: what it sends to the server as Out, and what it sends with { to: "client" } as In.
Try a message by hand
Send a game message sends one message from a form, the same way send() does. It's the quickest way to try a message before writing code for it. Send messages by hand describes the form, and Send messages walks through an example.
Find the message for an action
A MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play) is the best place to look, because you do the action yourself and watch what goes through:
On the bot's page, open Tools > Network > Settings. Type a word related to the action in the filter, such as Inventory or Chat, tick the messages that look related, and click Apply. Open Traffic, then do the action in Dofus. Read the new rows from the top. Out is what your client sent, and In is what the server sent. Click a row to see its fields. Look the names up in the Dofus protocol reference to learn what the fields mean. Write your handler with the names you found, as in React to messages. If Settings then shows a badge such as "12 message(s) dropped - the stream couldn't keep up", select fewer messages and do the action again. Those drops only affect what Asteroboard shows, never what scripts receive.
Next, Listening covers every way to receive the messages you found, and Payloads how their fields become JavaScript values.
  • 101 vues
Receive messages with on() and onTraffic(), choose them with selectors, and know the order handlers run in, how to remove them, their limits and what stops a script.
A script spends most of its time waiting for the game to say something. on() and onTraffic() register functions that Asterobot calls for each message, until you remove them or the script stops. React to messages builds a first handler; this page covers everything else.
on()
const handle = on(selector, handler); on() registers handler for every inbound message that matches selector, and returns a handle to pass to off() later. It returns right away. The handler then runs each time a matching message arrives, with the message's traffic object as its argument.
on() only receives inbound messages: what the game server sends, and what a script or the Network tool sends to the client. Messages going to the server never reach it, whatever the selector. onTraffic() receives those.
This script warns when the character's pods are nearly full:
import { session, botWarn } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; export default async function behavior(launch) { on("InventoryWeightEvent", (traffic) => { const weight = traffic.payload?.inventoryWeight; const max = traffic.payload?.weightMax; // A message Asterobot couldn't decode has no payload, and an error // thrown in a handler stops the whole script. if (weight === undefined || !max) return; if (weight >= max * 0.9) { botWarn(`Pods: ${weight} / ${max}`); } }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } inventoryWeight and weightMax are 32-bit fields, so they read as regular numbers, not as BigInt values.
Selectors
Selector Matches A message name, such as "InventoryWeightEvent" Messages whose type is exactly that name. Case matters. "*" Every message, including the ones without a readable name. A function Messages for which the function returns a truthy value. It receives the traffic object. A few rules:
Asterobot doesn't check the name. A misspelled name, or one your version of Asterobot doesn't have, is accepted, and the handler never runs. The editor can help: see Finding messages. A name never matches a message without a readable name, since its type is empty. Use "*" or a function to receive those. A function selector is called for every inbound message, so keep it quick. If it throws, the script stops with an error that includes evaluate protocol handler selector:. A function can match on more than a name. This handler only receives guild chat:
on( (traffic) => traffic.type === "ChatChannelMessageEvent" && traffic.payload?.channel === "GUILD", (traffic) => botInfo(`[guild] ${traffic.payload?.senderName}: ${traffic.payload?.content}`), ); onTraffic()
const handle = onTraffic(handler); onTraffic() has no selector: its handler receives every message, in both directions. Besides what on() receives, it gets the outbound messages, the ones going to the server:
on a Full socket bot, what scripts send and what someone sends from the Network tool; on a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), also everything your Dofus client sends. Test traffic.direction and traffic.type in the handler to keep what you need. On a MITM bot, this shows what your client does while you play:
onTraffic((traffic) => { if (traffic.direction === "outbound" && traffic.type !== "") { botDebug("Sent:", traffic.type); } }); The order handlers run in
Asterobot handles one message at a time, in the order messages go through the bot. For each message:
Its handlers run one after the other, in the order they were registered. on() and onTraffic() handlers share that order. The wait() calls it matches resolve. The next message is handled. Interceptors aren't part of this order: they decide about a message before it's delivered. See Intercepting.
An async handler runs until its first await, and the next handler starts then. The rest of it continues later, while other messages are handled. Handlers don't wait for each other, so an async handler can see other messages arrive before it finishes.
Changes made while a message is being handled work this way:
A handler that an earlier handler removed with off() isn't called for the current message. A handler registered while a message is being handled starts with the next message. off()
off(handle); off() removes a handler registered with on(), onTraffic() or intercept(). It returns nothing and never throws: a handle that was already removed, or anything that isn't a handle, is ignored.
A handler can remove itself. This one only reacts to the first weight update:
const handle = on("InventoryWeightEvent", (traffic) => { off(handle); botInfo("First weight update:", traffic.payload?.inventoryWeight); }); To act on the next matching message once, wait() is often simpler: see Waiting. Settings handlers registered with onChange() are removed with offChange(), not off(): see Read settings.
Limits
A script can register 256 handlers with on(), onTraffic() and intercept() together. One more throws maximum behavior handlers reached, and removing one with off() frees its place. onChange() handlers have a separate limit of 256. Each run of a handler's code has 5 s, there to stop code that never ends. Past that, Asterobot stops the script, with JavaScript execution deadline exceeded in the error. An await ends a run: the code after it gets its own 5 s. While your code runs, arriving messages wait in the bot's queue, with timers and other events, and every one of them is handled, in order, once your code awaits. Only when more than 8 MB of game messages are waiting does the script stop, with the script fell too far behind the game: the PC can't keep up with it. Short handlers keep the queue moving. Limits lists every limit, and Async and timing explains how the 5 s are counted.
Errors
on() and onTraffic() throw right away, as ordinary exceptions, when they can't register a handler:
Error Cause protocol.on handler must be callable The handler passed to on() isn't a function. onTraffic() says protocol.onTraffic handler must be callable. protocol selector cannot be empty The selector is "". protocol selector must be a semantic type, '*', or predicate The selector is neither a string nor a function. maximum behavior handlers reached The script already has 256 handlers. Once a handler is registered, an error that escapes it stops the whole script: a thrown error, or, in an async handler, an await that fails without a catch. The behavior stopped with an error then appears on the bot's page, with an error that includes Game traffic handler failed: for a thrown error, or unhandled Promise rejection: for a failed await.
So check the payload before reading it, and put the awaits of a handler in try/catch:
on("ChatChannelMessageEvent", async (traffic) => { // Only the exact command: the game also sends our own reply back to us. if (traffic.payload?.content !== "!ping") return; try { await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "pong" }); } catch (error) { // An error that escapes a handler stops the whole script. botWarn("Couldn't answer:", String(error)); } }); When handlers stop
Handlers live as long as the script. When it stops, because you clicked Stop, an error ended it, Run restarted it or the game connection closed, all its handlers go with it. The next start registers them again, from the top of your code.
Next, Waiting covers wait(), and the asterobot:protocol reference has every signature.
  • 89 vues
Pause a script until a message arrives with wait(), always with a timeout, wait for the answer to what you send, and handle what can go wrong.
wait() pauses your code until a message you describe arrives, and hands you that message. It's the tool for "do something, then carry on when the game answers".
wait()
const traffic = await wait(selector, { timeout: 5000 }); wait() returns a promise that resolves with the traffic object of the next message matching selector. The selector is the same as for on(): a message name, "*" for any message, or a function that returns true for the message you want. Listening describes them.
The rest of the bot doesn't pause while you wait. Handlers keep running, and other parts of your code can wait for other messages at the same time.
Only inbound messages
wait() only sees inbound messages: what the game server sends, and what a script or the Network tool sends to the client. A message going to the server never resolves a wait, even with "*". Use onTraffic() to see those.
No timeout unless you pass one
Important
wait() has no timeout of its own. Without { timeout }, a wait that never matches stays open until the script stops, and the code after it never runs. Always pass a timeout.
timeout is a number of milliseconds greater than zero. When it runs out, the promise rejects with protocol wait timed out. That's what gets your code going again when a message doesn't come, whether the game never sends it or its name changed after an update.
Function Default timeout wait() None: it waits until the script stops send() None needed: it resolves as soon as its message is in line to go out request() 30 seconds A wait can't be cancelled. It ends when a message matches, when its timeout runs out, or when the script stops, so give it the shortest timeout that makes sense.
Wait for the answer to what you send
A wait only sees messages the bot handles after wait() was called. It doesn't look back.
Right after send(), that's never a problem. send() resolves as soon as its message is in line to go out, before any answer can reach your code, so a wait started right after await send() always gets the answer, however fast it comes and however busy the PC is:
await send("ChatChannelMessageRequest", { channel: "PARTY", content: text }); const echo = await wait( (traffic) => traffic.type === "ChatChannelMessageEvent" && traffic.payload?.content === text, { timeout: 5000 }, ); What a wait can miss is a message the bot handled before it started: one that arrived while your code was awaiting something else, such as a sleep(). Start a wait as soon as you know you'll need it, not after an unrelated await.
Several waits for one message
When several waits match the same message, they all resolve with it, in the order they were started. Waits resolve after every on() and onTraffic() handler has run for that message. A function selector that throws stops the whole script, not only the wait, with an error that includes evaluate protocol wait selector:. A script can have 128 waits open at once. One more rejects with behavior resource limit exceeded: maximum waits reached. Patterns
Carry on when nothing comes
Catch the timeout and decide what happens next:
try { const traffic = await wait("InventoryWeightEvent", { timeout: 10000 }); botInfo("Pods:", traffic.payload?.inventoryWeight); } catch (error) { // No update can also mean the weight didn't change. botWarn("No weight update:", String(error)); } Wait for one of several messages
A function selector can accept several names, and the message you get tells you which one came:
const names = new Set(["InventoryWeightEvent", "InventoryContentEvent"]); const traffic = await wait((candidate) => names.has(candidate.type), { timeout: 10000 }); botInfo("Received", traffic.type); Wait inside a handler
Waiting in an async handler doesn't hold up other handlers or messages, which is how the handler gets to see the answer arrive. Put the wait in try/catch: a timeout that escapes a handler stops the script.
This script repeats in the party channel what a party member asks it to say with !say, and checks that the line went through:
import { session, botInfo, botWarn } from "asterobot:bot"; import { on, send, wait } from "asterobot:protocol"; export default async function behavior(launch) { on("ChatChannelMessageEvent", async (traffic) => { const content = traffic.payload?.content; if (traffic.payload?.channel !== "PARTY" || typeof content !== "string" || !content.startsWith("!say ")) return; const text = content.slice("!say ".length); // Never repeat a command: the game sends our own line back to us. if (text === "" || text.startsWith("!")) return; try { await send("ChatChannelMessageRequest", { channel: "PARTY", content: text }); await wait( (echo) => echo.type === "ChatChannelMessageEvent" && echo.payload?.content === text, { timeout: 5000 }, ); botInfo("Said in party:", text); } catch (error) { // An error that escapes a handler stops the whole script. botWarn("Couldn't say it:", String(error)); } }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } Errors
wait() never throws: every problem rejects its promise.
Error Cause protocol wait timed out The timeout ran out before a message matched. behavior resource limit exceeded: maximum waits reached 128 waits are already open. timeout/milliseconds must be a finite positive Number timeout is 0, negative, or not a number. timeout/milliseconds is too large timeout is too large to be a duration. protocol options must be an object The second argument isn't an object. unknown protocol option "retries" The options have a key other than timeout, to and correlated. to and correlated are accepted and ignored. protocol selector cannot be empty The selector is "". protocol selector must be a semantic type, '*', or predicate The selector is neither a string nor a function. Next, Sending covers send() and request(), and Limits lists every limit.
  • 65 vues
Send messages to the game server or to the Dofus client with send(), use request() for the few requests that get a response, and understand order, timeouts and errors.
A script acts in the game by sending the messages the Dofus client would send. send() does that for almost everything, request() is for the few requests the game answers with a response, and the to option turns a message around, towards the client. Send messages builds a first example.
Warning
Everything a script sends goes out as the bot's character. On a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), that's your character, in your game, and other players see what it says.
Tip
For chat messages, asterobot:chat is the safer way: a ChatChannelMessageRequest sent with send() goes around the bot's chat guard, which waits out a channel's cooldown and backs off when the game warns about spam.
send()
await send(type, payload, options); Argument What to pass type The message's readable name, such as "ChatChannelMessageRequest". payload An object with the fields to send. For a message without fields, pass {}: leaving the payload out is an error. Payloads explains how values convert. options Optional. to, "server" or "client", and correlated, a boolean, as in { to: "client" }. timeout is accepted and ignored. send() builds the message, puts it in line to go out, and resolves at once. It doesn't wait for an answer: Waiting shows how to wait for one. A wait() started right after await send() always gets the answer.
The payload is checked first. When it doesn't fit the message, the promise rejects and nothing goes out. A message that passes the checks but can't be written later stops the script: with Game session closed when the connection closed, or with send and the message's name in the error otherwise.
The message goes to the game server as a request with uid -1. onTraffic() handlers receive it as an outbound message, and the Network tool shows it when it's selected. Interceptors are never asked about it, which is why an interceptor can call send().
Messages sent with a uid
A few messages leave the Dofus client with a uid of their own although nothing ever answers them: ClientIdRequest, ClientChallengeInitRequest and ClientChallengeProofRequest, the three answers to the server's proof of identity, and BasicLatencyStatsRequest. In the Network tool they show a uid in Traffic, and no response with that uid ever comes back In.
To send one the same way, pass correlated: true:
await send("BasicLatencyStatsRequest", { latency: 31 }, { correlated: true }); The message takes the next uid, the one request() would have used, and the promise resolves once it's in line, like any send(). request() would wait its whole timeout for a response that never comes.
The numbers follow the Dofus client's. On every game connection, the client's first numbered request is #2, even after changing server, and so is a Full socket bot's. Each send() with correlated: true and each request() then takes the next one.
Use correlated only for the messages the client numbers: every other message goes out with -1, as the client sends it. correlated can't be combined with to: "client", since a message toward the client is delivered as an event, and events have no uid.
Order
Messages leave in the order your script calls send() and request(), awaited or not, on every PC:
// ContextReadyRequest first, every time. send("ContextReadyRequest", { mapId }); send("MapInformationRequest", { mapId }); disconnect() and the movement functions of asterobot:movement wait their turn too: they start once the messages sent before them have left. And messages your script sent still leave if it stops, whether by Stop, a reload or an error.
Sending to the client
With { to: "client" }, a message travels the other way: it's framed as an event from the server and delivered inbound.
await send( "ChatChannelMessageEvent", { channel: "PARTY", senderName: "Tester", content: "!say hello" }, { to: "client" }, ); Where it goes depends on the bot:
Bot Where the message goes Full socket To your script only. The bot is the client, so there's no one else to deliver it to, and nothing reaches the server. MITM To your Dofus client, which acts on it as if the server had sent it, and to your script. Either way, on() handlers and waits receive it as an inbound event with uid -1. That makes it a handy way to try a handler without waiting for the game: the example above triggers the !say handler from Waiting, whose reply then really goes to the server. On a MITM bot, your Dofus client believes whatever you send it this way, so only send it messages the server could really send.
to accepts "server", the default, or "client". The Network tool's Send a game message offers the same choice as To the server and To the client.
request()
const response = await request(type, payload, options); request() sends a message with a uid of its own, then resolves with the traffic object of the response that carries the same uid. It takes the same arguments as send(). to and correlated are accepted but ignored: a response can only come from the server, and a request always carries a uid.
Only a few Dofus requests are answered this way. Most are answered with an event, or not at all, and for those request() waits for its whole timeout, 30 seconds unless you pass another one, then rejects with context deadline exceeded. Use send() and wait() for them.
To know whether a request gets a response, watch your own Dofus client in the Network tool on a MITM bot. A request the client expects an answer to shows a uid in Traffic, and its response comes back In with the same uid. Finding messages explains the rows.
A request sent without a uid can still be answered with a response, which then carries -1 too. GuildWeeklyNextResetRequest is one: the Dofus client sends it plain, and GuildWeeklyNextResetResponse comes back with uid -1. request() can't wait for such a response, since it matches responses by uid. Send the request with send(), and wait() for the response by its name.
The response also reaches your handlers and waits, like any other inbound message. On a MITM bot, your Dofus client never receives it, since it didn't ask for it.
Timeouts
Function Default What the timeout covers send() None Nothing: send() resolves as soon as its message is in line, and ignores timeout. request() 30 seconds From the call, sending the message and receiving its response. wait() None Waiting for a matching message. Pass another timeout in the options, in milliseconds: { timeout: 5000 }. It must be greater than zero.
Messages that aren't sent
On a MITM bot, a script can't send IdentificationRequest to the server, because your Dofus client has already identified the session. send() resolves without sending anything, and the bot's console shows Refused to forward a message that would re-authenticate a relayed session. request() rejects right away instead, with mitm relay refuses to identify: this Game session was already authenticated by the real DOFUS client. A message without a readable name can't be sent: send() and request() only take names Asterobot knows. If Request, Event, Response or Message shows up among the message names, don't send it: those wrap every other message, and sending one rejects with game envelope cannot be sent as a payload: followed by the name. A script can't send bytes of its own as a new message. Only an interceptor can put bytes on the wire, in place of a message going through: see Intercepting. Errors
send() and request() never throw: every problem rejects the promise.
Error Cause protocol type must be a non-empty semantic message name The name is missing, empty, or not a string. semantic protobuf message is not mapped: ChatChanelMessageRequest Asterobot has no message by that name: a typo, a name that changed, or a message without a readable name. ChatChannelMessageRequest: expected an object The payload is missing. ChatChannelMessageRequest.contnt: unknown protobuf field The message has no such field. Payloads lists every payload error. protocol options must be an object The options aren't an object. unknown protocol option "retries" The options have a key other than timeout, to and correlated. send option "correlated" must be a boolean correlated isn't true or false. send option "correlated" cannot be combined with to: "client": ... Both were passed. A message toward the client has no uid. timeout/milliseconds must be a finite positive Number timeout is 0, negative, or not a number. send option "to" must be "server" or "client" to isn't a string. unknown send target "clients": expected "server" or "client" to is a string other than "server" and "client". behavior resource limit exceeded: maximum pending operations reached request() only: 32 operations are already in progress. Requests, game data lookups and movements count together; sends don't. behavior resource limit exceeded: more than 8 MB are waiting to be sent, so the connection isn't taking them More than 8 MB of messages are waiting to be written: the connection has stopped taking them. context deadline exceeded request() got no response within its timeout. A complete example
Party members can ask this script for the character's pods with !pods:
import { session, botWarn } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; export default async function behavior(launch) { let pods = "unknown"; on("InventoryWeightEvent", (traffic) => { if (!traffic.payload) return; pods = `${traffic.payload.inventoryWeight} / ${traffic.payload.weightMax}`; }); on("ChatChannelMessageEvent", async (traffic) => { // Only the exact command: our own answer comes back to us too. if (traffic.payload?.channel !== "PARTY" || traffic.payload.content !== "!pods") return; try { await send("ChatChannelMessageRequest", { channel: "PARTY", content: `Pods: ${pods}` }); } catch (error) { // An error that escapes a handler stops the whole script. botWarn("Couldn't answer:", String(error)); } }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } The answer says unknown until the game sends an InventoryWeightEvent after the script started: the script only knows what it has received.
Next, Intercepting covers changing messages on their way through, and Payloads every rule for the values you send.
  • 118 vues
Let messages through, drop them or change them on their way through the bot with intercept(), how a message waits for your answer, and use it safely on a MITM bot.
Every other function in this chapter observes messages that have already gone through. An interceptor is asked first, and its answer decides whether a message continues, and in what form. On a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), that decides what your Dofus client and the game server receive, so read On a MITM bot before using one there.
intercept()
intercept(selector, handler); // asked about the messages the selector matches intercept(handler); // asked about every message intercept() registers handler and returns a handle to pass to off(). The selector is the same as for on(): a message name, "*", or a function. Listening describes them. Interceptors count towards the 256 handlers a script can register with on() and onTraffic(), and registering one throws the same errors, with protocol.intercept handler must be callable when the handler isn't a function.
The handler receives the message's traffic object and returns a decision:
Return What happens to the message Nothing, undefined, or any other value No decision. The next interceptor is asked, and when none decides, the message continues unchanged. "drop" The message stops here. { payload: { ... } } The message continues with these fields instead of its own. See Changing a payload. { raw: bytes } The message is replaced by these bytes. See Raw replacements. What "continues" covers depends on the bot. On a Full socket bot, the message reaches your script's handlers and waits, and the bot's page in Asteroboard: the Network tool, the console's chat and live inventory. On a MITM bot, it also reaches the other side, your Dofus client or the game server. A dropped message reaches none of them, and keeps its sequence number, which leaves a gap.
What an interceptor is asked about
Bot Messages interceptors decide about Full socket Messages from the game server. MITM Messages from the game server to your Dofus client, and from your Dofus client to the server. Interceptors are never asked about:
messages a script sends with send() or request(), towards the server or the client, which is why an interceptor can call send(); messages someone sends from the Network tool's Send a game message; on a MITM bot, the responses to your script's own request() calls, and the first message of the session, your client's IdentificationRequest. Those messages still reach onTraffic() and the Network tool as usual.
The message waits for your answer
While an interceptor is registered, each message it could decide about waits for your script's answer, however long the script takes to get to it. A busy script makes the message later, never undecided: your decision always applies, on a slow PC as on a fast one.
The wait is still bounded. Like any piece of code, an interceptor that runs for more than 5 s is stopped, and the message continues unchanged (see When an interceptor fails). When the script stops, messages continue unchanged.
An interceptor applies to the messages that arrive after it's registered. Register it as your package loads, at the top level of a module or before the first await of the entry function, and it decides every message from Play on: until the package has loaded, every message waits for it.
Three things follow:
Keep interceptors short. A message waiting for your interceptor also waits for the code running when it arrives, and for the messages before it. Do the quick test in the interceptor, and anything slow somewhere else, such as an on() handler broken up with await. An interceptor must answer synchronously. An async function returns a promise, and a promise isn't a decision, so the message always continues unchanged. An error inside an async interceptor also becomes an unhandled rejection, which stops the script. A message no interceptor names doesn't wait at all. An interceptor registered with a function selector, "*" or no selector makes every message of the kinds above wait for the script, because the script has to look at each one. Name the messages you decide about, and remove interceptors you no longer need with off(). The first decisive answer wins
Interceptors are asked in the order they were registered. The first one that returns "drop", { payload } or { raw } decides, and the ones after it aren't asked. An interceptor whose selector doesn't match isn't asked, and doesn't count as an answer.
An interceptor that fails doesn't decide either, and the next one is asked. That covers a handler that throws, a function selector that throws, and a { payload } that can't be built. When an object holds both raw and payload, raw wins.
Changing a payload
Return { payload } to let a message continue with other values. This interceptor defangs links in the chat lines your handlers receive, and on a MITM bot, the lines your Dofus client shows:
intercept("ChatChannelMessageEvent", (traffic) => { const content = traffic.payload?.content; if (typeof content !== "string" || !content.includes("http")) return undefined; // The spread keeps every other field: the replacement is built from this object alone. return { payload: { ...traffic.payload, content: content.replaceAll("http", "hxxp") } }; }); Rules for { payload }:
The message is rebuilt from your object alone. A field you leave out is sent unset, or at its zero value, so start from ...traffic.payload. Bytes of the original that Asterobot's definitions don't describe can't be carried over. The message keeps its type, its kind, its uid and its wire name. To send a different message, drop this one and call send(). A message without a readable name can't get a new payload. Your handlers and the Network tool receive the replacement, not the original. When the replacement can't be built, the original message continues unchanged, and the bot's console says why, as in build replacement payload for ChatChannelMessageEvent: ChatChannelMessageEvent.contnt: unknown protobuf field. For a message without a readable name, the line starts with cannot replace the payload of followed by its wire name.
Dropping a message
Return "drop" to stop a message. On a MITM bot, this script turns the chat lines you type starting with ! into commands for the script: they never reach the server, so other players don't see them.
import { session, botInfo } from "asterobot:bot"; import { intercept, send } from "asterobot:protocol"; export default async function behavior(launch) { intercept("ChatChannelMessageRequest", (traffic) => { const content = traffic.payload?.content; if (typeof content !== "string" || !content.startsWith("!")) return undefined; // Writing a line is quick enough to do before answering. botInfo("Command:", content); return "drop"; }); // A new game connection starts with this message. MITM bots never launch // with "initial": the Dofus client has already identified their session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } } On a Full socket bot, this interceptor is never asked anything: every message going to the server there comes from a script, and those aren't intercepted.
On a Full socket bot, dropping a message keeps it from your own handlers, which makes an interceptor a filter. Dropping a response that a request() is waiting for makes that request() time out.
Raw replacements
{ raw } replaces a message with the bytes you give, as a Uint8Array or an ArrayBuffer. Any other value doesn't count as an answer.
The bytes replace the whole message as it travels: the part that states the message's kind, uid and wire name, and the fields inside it. That's more than traffic.rawAny, which only holds the fields, and Asterobot has no function that builds those bytes for you. { raw } is only for a message nothing else can handle, when you know that format yourself.
When Asterobot can't read your bytes as a message, your handlers receive the original message. On a MITM bot, the bytes still go to the other side as they are.
Warning
On a MITM bot, a wrong { raw } sends your Dofus client or the game server bytes they can't read.
When an interceptor fails
Unlike an on() handler, an interceptor that throws doesn't stop the script, unless it's async (see The message waits for your answer). The message continues unchanged, and the bot's console shows an error line from the script:
The line starts with Cause interceptor failed: The handler threw an error, or ran for more than 5 s. interceptor selector failed: The function selector threw an error. build replacement payload for A field or value in { payload } doesn't fit the message. cannot replace the payload of { payload } was returned for a message without a readable name. An interceptor that fails on every message writes one of these lines for each message, so fix it before it fills the console.
An async interceptor isn't reported anywhere: the message continues unchanged.
On a MITM bot
Caution
On a MITM bot, an interceptor acts on the game of the person playing. A dropped message from the server never reaches their Dofus client, a dropped message from the client never reaches the server, and a replacement reaches the other side exactly as you built it. What their client shows and what the server knows can then differ.
While an interceptor is registered, each message it's asked about waits for your script, and the rest of the session waits behind it, since messages keep their order. A script that's slow to answer, or busy with other work when such a message arrives, is felt as lag in the game.
If the script stops or runs into trouble, messages continue unchanged: a failing interceptor can't block a session.
What interceptors are good for
Keeping noise out of your own handlers on a Full socket bot, with "drop". Turning chat lines into commands on a MITM bot, as above. Changing what your Dofus client receives on a MITM bot, with { payload }. Replacing a message with a different one: return "drop" and send() the new message from the interceptor. Next, Payloads lists the rules a { payload } follows, and Sessions what else changes on a MITM bot.
  • 67 vues
How message fields become JavaScript values and back, from field names, BigInt and enums to lists, maps, nested messages, oneofs, missing fields and every payload error.
A message's fields travel as protobuf values, and scripts work with JavaScript values. Asterobot converts them both ways: when a script reads traffic.payload, and when it builds a payload for send(), request() or an interceptor's { payload }. The two directions aren't symmetric, and this page lists every rule.
The Dofus protocol reference gives the type of each field.
Types at a glance
Field type Read as Accepted when sending string A string A string. bool A boolean A boolean. int32, sint32, sfixed32 A number A whole number from -2147483648 to 2147483647. uint32, fixed32 A number A whole number from 0 to 4294967295. int64, sint64, sfixed64 A BigInt A BigInt, a decimal string such as "123456789012", or a whole number between -Number.MAX_SAFE_INTEGER and Number.MAX_SAFE_INTEGER. uint64, fixed64 A BigInt The same, without negative values. float, double A number A number. bytes A Uint8Array A Uint8Array or an ArrayBuffer. An enum The value's name, such as "GUILD" The value's name, or its number. A message An object An object, or null to leave the field unset. A repeated field An array An array. A map An object An object. Field names
Payloads always read with camelCase names: the field sender_name reads as senderName. When sending, both spellings work, so { ticketKey: session.gameToken } and { ticket_key: session.gameToken } set the same field. Give each field only once: the same field under both spellings is an error, even with the same value.
Every key of the object you send must be a field of the message. An unknown key is an error, not something Asterobot skips.
Present, absent and zero
When reading
Plain fields are always there. A field the server didn't set reads as its zero value: "", 0, 0n, false, an empty array or object, or for an enum, its first value, such as "GLOBAL" for a chat channel. Fields marked optional in the protocol reference, and fields that hold a message, are only there when they're set. In ChatChannelMessageEvent, for example, originServerId only appears on lines from the cross-server community channels. In a oneof, only the member that's set is there. A field is never null, and never present with the value undefined. An absent field reads as undefined because it isn't there, so read fields with ?., and give defaults with ??:
// No originServerId means the line was said on this server. const server = traffic.payload?.originServerId ?? session.serverId; For plain fields, a zero value and an unset field look the same: a 0 may have been sent as 0, or not sent at all.
When sending
Leave a field out to leave it unset. A plain field then arrives as its zero value. undefined as a value is refused, even though the field would be left out anyway. Watch for variables that can be undefined, and only add the fields you have a value for. null is accepted only for a field that holds a message, and leaves it unset. On any other field, it's refused. const payload = { channel: "PARTY", content: text }; // A metadata key set to undefined would be refused. if (metadata) payload.metadata = metadata; await send("ChatChannelMessageRequest", payload); Numbers and BigInt
64-bit fields read as BigInt values, whatever their size:
const id = traffic.payload?.senderCharacterId; // for example 123456789012n if (id === 123456789012n) { // A BigInt equals another BigInt, never a number. } When sending, a 64-bit field takes a BigInt, a decimal string, or a number that's a safe integer. A bigger number is refused, because a JavaScript number can't hold it exactly.
32-bit fields work the other way: they read as numbers and refuse BigInt values. Convert with Number() before putting a BigInt in a 32-bit field. The JavaScript you need covers BigInt itself.
Enums
An enum field reads as the name of its value, such as "GUILD". When sending, give the name or the value's number. Names are case-sensitive: "guild" is refused.
A value Asterobot has no name for reads as its number. Some enum values still have scrambled names, which can change: don't rely on those.
Lists
A repeated field reads as an array and takes an array. Each item follows the rules of the field's type, and an error in an item gives its position after the field name, such as [2].
Maps
A map field reads as a plain object. Its keys are always strings, even when the map's keys are numbers or booleans: the key 12 reads as "12", and true as "true". When sending, give an object with keys written the same way. An error in an entry gives its key after the field name, such as ["12"].
Nested messages
A field that holds another message reads as an object with that message's fields, which follow the same rules, and takes an object when sending. Errors give the whole path, from the message name down to the field, separated by dots.
Oneofs
A oneof is a group of fields of which at most one is set. ChatPrivateMessageRequest has one called target: a private message goes to a character, with name, or to an account, with tag.
When reading, only the member that's set is in the payload. When sending, set one member. Setting two is an error, and the editor marks it too. await send("ChatPrivateMessageRequest", { name: "Airelle", content: "On my way" }); Errors
A payload that doesn't fit its message makes send() and request() reject, before anything is sent. In an interceptor's { payload }, the same error shows on the bot's console after build replacement payload for and the message name, and the original message continues. The Network tool's Send a game message checks payloads its own way, with different texts: see Send messages by hand.
Every error starts with the path to the problem: the message name, then the fields as you wrote them, with list positions and map keys.
Error Cause ChatChannelMessageRequest: expected an object The payload is missing, null or undefined. ChatChannelMessageRequest: expected an object, got string The payload isn't an object. The last word says what it was instead. ChatChannelMessageRequest.contnt: unknown protobuf field The message has no such field. IdentificationRequest: fields "ticketKey" and "ticket_key" address the same protobuf field The same field under both spellings. ChatPrivateMessageRequest: oneof target selects both "name" and "tag" Two members of one oneof. ChatChannelMessageRequest.content: explicit undefined is not a protobuf value A field set to undefined. ChatChannelMessageRequest.content: null is only valid for a message field null on a field that doesn't hold a message. ChatChannelMessageRequest.content: expected string A string field got another type. PingRequest.quiet: expected boolean A boolean field got another type. InventoryWeightEvent.weightMax: expected integral Number A 32-bit integer field got a number with decimals, a BigInt, or another type. InventoryWeightEvent.weightMax: Number is outside int32 range The number is too big or too small for a signed 32-bit field. ChatChannelMessageEvent.senderCharacterId: expected signed 64-bit BigInt or decimal string A signed 64-bit field got a number that isn't a safe integer, a string that isn't a whole decimal number, a value out of range, or another type. ChatChannelMessageRequest.channel: unknown enum symbol "GENERAL" The enum has no value with that name. ChatChannelMessageRequest.channel: expected enum symbol or number An enum field got something other than a name or a whole number. The other field types give these texts after the field's path:
Text after the path Cause expected integral Number in uint32 range An unsigned 32-bit field got a negative number, a number with decimals, a number too big, or another type. expected unsigned 64-bit BigInt or decimal string The same problems as for a signed 64-bit field, or a negative value. expected Number A float or double field got something other than a number. expected Uint8Array or ArrayBuffer A bytes field got something else. expected an array A repeated field got something that isn't an array. invalid array length A repeated field got an object whose length isn't a whole number from 0 up. expected a plain object map A map field got something that isn't an object. invalid int32 map key "abc" A map key that doesn't fit the map's key type. The text names that type: int32, int64, uint32, uint64 or boolean. Next, Sessions covers what a script knows about its game connection, and Error messages lists every error text a script can meet.
  • 77 vues

Compte

Navigation

Recherche

Recherche

Configurer les notifications push du navigateur

Chrome (Android)
  1. Appuyez sur l'icône de cadenas à côté de la barre d'adresse.
  2. Tap Autorisations → Notifications.
  3. Ajustez vos préférences.
Chrome (Desktop)
  1. Cliquez sur l'icône représentant un cadenas dans la barre d'adresse..
  2. Select Paramètres du site.
  3. Find Notifications et ajustez vos préférences.