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.

Smooth

Administrateurs
  • Adhésion

  • Dernière visite

Tout ce qui a été publié par Smooth

  1. Smooth a posté un record dans Reference
    asterobot:movement walks the bot's character. Its one function, move(), walks the character to a cell of the map it stands on and resolves once the walk is over. The same call works on a Full socket bot and on a MITM bot (man-in-the-middle: the bot relays the game session of a Dofus client that someone plays). import { move } from "asterobot:movement"; This is the module's only export. move() move(cell, options?) returns Promise<number>. Parameter Type Description cell number The cell to walk to, a whole number from 0 to 559 options.cautious boolean Walk instead of running, whatever the distance. Default false. options.timeout number How many milliseconds the server gets to grant the walk, and then to acknowledge its end once the walk is over. Default 5000. It resolves with the cell the character stands on, once the server has acknowledged the end of the walk. try { const cell = await move(301); botInfo("Standing on cell", cell); } catch (error) { botWarn("Couldn't walk:", String(error)); } How it walks It reads where the character and the other actors stand, as currentMap() does, and plans the route to cell the way findPath() does, around every other actor. When cell can't be reached, the route ends on the reachable cell closest to it. When there's nowhere to walk, move() resolves right away with the character's cell and sends nothing. It sends MapMovementRequest and waits for the server's MapMovementEvent granting the walk. On a Full socket bot, it waits as long as the Dofus client takes to walk the cells the server granted, then sends MapMovementConfirmRequest. On a MITM bot it sends nothing more: the Dofus client walks the character on screen and confirms the walk itself. It resolves when the server answers with MapMovementConfirmResponse. When the server grants a shorter walk than the one asked for, move() times the walk the server granted and resolves with the cell where it ends. On a Full socket bot the confirmation leaves as early as a Dofus client could send it. The path explains how that time is found, and why it can be wrong for a mounted character. One walk at a time A bot walks one walk at a time, whoever asks for it. While a walk is under way, a second move() rejects at once and sends nothing, and so does a walk started with Walk here on the bot's map in Asteroboard. Await each move() before starting the next. move() also refuses to start while the character is still walking a walk it didn't ask for, such as one the player started on a MITM bot. That walk counts until the server acknowledges its end, or for one second at most past the time the Dofus client takes to walk it. The walk belongs to the bot, not to your script. If the script stops during a walk, the promise of move() rejects with the reason the script stopped, and the walk still ends and is confirmed, so the server doesn't keep the character walking. Doing something else during the walk move() doesn't hold up the rest of your script. Other code keeps running while the character walks, and on a Full socket bot the confirmation still leaves on time. To send a chat message while walking, start both together: const [cell] = await Promise.all([ move(301), send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" }), ]); Don't keep the promise of move() in a variable to await later. If it rejects before your code reaches the await, nothing catches it and the script stops with unhandled Promise rejection:. Await it, put it in a Promise.all, or give it a .catch(). Until the end of the walk is acknowledged, the server considers the character still walking. Wait for move() to resolve before an action that needs the character standing, such as using an element of the map. Errors A mistake in the arguments rejects the promise with a TypeError, and nothing is sent: Error Cause movement.move cell must be an integer cell id cell isn't a whole number in the 32-bit range, or is a BigInt or a string movement.move cell: pathfinding: not a map cell, cell ids go from 0 to 559 cell is outside 0 to 559 movement.move options must be an object options is set, not to null or undefined, and isn't an object movement.move options has no property "<name>" options has a key other than cautious and timeout movement.move options.cautious must be a boolean cautious is set to something other than true, false, null or undefined movement.move options.timeout: timeout/milliseconds must be a finite positive Number timeout isn't a number above 0 A walk that is refused or fails rejects it with an error whose message is one of these texts: Text Cause move: the bot isn't connected to a game server The bot has no game connection. Nothing is sent. move: the character is already walking; await the move in progress first Another walk of the bot is under way, from a script or from Asteroboard. Nothing is sent. move: the character is still walking The character hasn't finished a walk it didn't ask for. Nothing is sent. move: the character's map and cell aren't known yet Asterobot hasn't seen the character's map yet, or where the map places the character, for example right after the character enters the game. Nothing is sent. move: the game has no path from cell <cell> to cell <cell> The Dofus client itself would find no route. Nothing is sent. move: the server didn't grant the walk in time No MapMovementEvent granted the walk within timeout. A walk the server refuses ends this way too. move: another walk of the character started before this one ended A new walk of the character started first, or its walk was cancelled, for example because the player clicked elsewhere on a MITM bot move: the server didn't acknowledge the end of the walk in time No MapMovementConfirmResponse came within the walk's time plus timeout move: the game session closed during the walk The game connection closed before the walk ended behavior resource limit exceeded: maximum pending operations reached 32 operations are pending, and a walk counts as one until it ends. Nothing is sent. It also rejects with the texts of findPath() when the game data can't be read, such as failed to load map <mapId> for pathfinding: datacenter: no such record: maps/<mapId>. asterobot:pathfinding lists them. String(error) gives TypeError: or GoError: followed by the text. Limits move() walks on the map the character stands on. It doesn't change maps. It's made for moving outside fights, like asterobot:pathfinding. Walk along a path shows the messages move() exchanges, for a script that walks by hand.
  2. Smooth a posté un record dans Reference
    asterobot:pathfinding computes moves on a map the way the Dofus client does. findPath() gives the route the client would walk from one cell to another, the key cells to send for it and how long the walk lasts. The other functions work with cell positions: where a cell sits on the map's grid, and the distance and the direction between two cells. It only computes. To walk, call move() from asterobot:movement, or send the walk yourself as Walk along a path shows. import { findPath, cellToPoint, pointToCell, distance, direction } from "asterobot:pathfinding"; These are the module's only exports. There's no pathfinding object to import. Export Returns What it does findPath() A path or undefined, right away Finds the route from one cell of a map to another cellToPoint() A point, right away Gives a cell's position on the grid pointToCell() A cell or undefined, right away Gives the cell at a grid position distance() A number, right away Measures the grid distance between two cells direction() A direction or undefined, right away Gives the direction from one cell to another None of them returns a promise. Like the functions of asterobot:gamedata, they throw when the top level of a module calls them while Asterobot reads the package's declarations: see Declarations. Everything here is about moving outside fights. Moves in a fight follow other rules, and this module doesn't cover them. Cells and directions A map has 560 cells, numbered 0 to 559. Every function takes cell ids as regular whole numbers: a BigInt or a string is refused. A direction is a number from 0 to 7: Number Direction on screen 0 East 1 South-east 2 South 3 South-west 4 West 5 North-west 6 North 7 North-east findPath() findPath(mapId, from, to, options?) returns a Path, or undefined. Parameter Type Description mapId BigInt, number or string The map's id: a BigInt such as currentMap().mapId, a whole number up to Number.MAX_SAFE_INTEGER, or the id written in digits, such as "154010883". Any map of the bot's game version works, not only the one the character stands on. from number The cell the walk starts from to number The cell to walk to options.occupied number[] The cells other actors stand on, as described below. Default none. options.cautious boolean The cautious flag the walk will be sent with. It changes how the character moves, and so the walk's times. Default false. options.disableDiagonals boolean true only for a character whose look has no diagonal walking animations. Player characters have them, so leave it false. It reads the map from the game data of the bot's game version and returns the route the Dofus client would take. When to can't be reached, the route ends on the reachable cell closest to it instead, and end tells you which. findPath() returns undefined only when the Dofus client itself would find no route at all, which doesn't happen on the game's maps. Occupied cells The Dofus client's router makes a cell with an actor on it more expensive to walk onto, so its routes tend to go around actors. To get the route the client would walk, pass in occupied the cell of every actor the server shows on the map except the bot's own character: other players, monster groups, NPCs, merchants, tax collectors, prisms and the like. Items on the ground, marks and glyphs don't count. currentMap() lists the actors: import { currentMap } from "asterobot:bot"; import { findPath } from "asterobot:pathfinding"; const map = currentMap(); if (map?.cellId !== undefined) { const others = map.actors.filter((actor) => actor.id !== map.characterId); const path = findPath(map.mapId, map.cellId, 301, { occupied: others.map((actor) => actor.cellId), }); } The path Property Type Value start number The cell the route starts from: from end number The cell the route ends on: to when the router reached it, otherwise the reachable cell closest to to steps array The route's steps, in walking order. Each one is an object with cell, the cell the character leaves, and direction, the direction it walks from there. keyCells number[] What MapMovementRequest carries in keyCells for this route. Send them as they are. Empty when end is start: there's nowhere to walk, and the Dofus client sends nothing. gait string "walk" or "run": how the character moves along the route duration number How long the walk lasts, in milliseconds: from its start to the moment the Dofus client sends MapMovementConfirmRequest. 0 when end is start. stepEnds number[] When each step ends, in milliseconds from the start of the walk. The last one is duration. A character walks a route of 3 cells or fewer, the start included, and runs a longer one. With cautious: true it always walks. duration is the earliest moment a Dofus client can confirm the walk. A real client only ends a step when it draws its next frame, so it confirms a little later: in walks recorded from the Dofus client, the confirmation left 10 to 48 ms after duration. The times are those of a character on foot. A mounted character runs at a speed Asterobot doesn't cover yet, so its duration can be wrong. Errors Error Type Cause pathfinding.findPath mapId must be an integer Number, BigInt or decimal string TypeError mapId is a fraction, a number above Number.MAX_SAFE_INTEGER, a string that isn't a whole number written in digits, or another type pathfinding.findPath from must be an integer cell id, and the same with to TypeError The cell isn't a whole number in the 32-bit range, or is a BigInt or a string pathfinding.findPath options must be an object TypeError options is set, not to null or undefined, and isn't an object: an array or a number, for example pathfinding.findPath options has no property "<name>" TypeError options has a key other than occupied, cautious and disableDiagonals, such as the misspelled ocupied pathfinding.findPath options.occupied must be an array of cell ids TypeError occupied isn't an array pathfinding.findPath options.occupied[<index>] must be an integer cell id TypeError That item isn't a whole number in the 32-bit range pathfinding.findPath options.cautious must be a boolean, and the same with disableDiagonals TypeError The option is set to something other than true, false, null or undefined pathfinding.findPath: map <mapId>, cell <from> to cell <to>: pathfinding: not a map cell, cell ids go from 0 to 559: <cell> TypeError from or to is a whole number outside 0 to 559. For a cell of occupied, the text ends with occupied cell <cell> instead. failed to load map <mapId> for pathfinding: datacenter: no such record: maps/<mapId> Error The bot's game version has no map with that id failed to open the datacenter: followed by the reason Error Asterobot can't read the game data of the bot's game version, for example because it isn't extracted. See Extract game data. String(error) gives TypeError: or GoError: followed by the text. cellToPoint() cellToPoint(cell) returns { x, y }, the cell's position on the map's grid. cellToPoint(300); // { x: 17, y: -4 } The grid is the map turned 45 degrees, so both numbers change along a row of the screen. One cell to the east adds 1 to x and 1 to y. One cell to the south adds 1 to x and takes 1 from y. Error Type Cause pathfinding.cellToPoint cell must be an integer cell id TypeError cell isn't a whole number in the 32-bit range, or is a BigInt or a string pathfinding.cellToPoint cell: pathfinding: not a map cell, cell ids go from 0 to 559 TypeError cell is outside 0 to 559 pointToCell() pointToCell(x, y) returns the cell at that grid position, or undefined when the position is off the map. pointToCell(18, -3); // 301 pointToCell(0, 1); // undefined It throws a TypeError with pathfinding.pointToCell x and y must be integers when x or y isn't a whole number in the 32-bit range, or is a BigInt or a string. distance() distance(a, b) returns the grid distance between two cells, as the Dofus client measures it: how far apart their x are, plus how far apart their y are. distance(300, 301); // 2 So the next cell to the south-east, south-west, north-east or north-west is 1 away, and the next cell to the east, west, north or south is 2 away. Error Type Cause pathfinding.distance a must be an integer cell id, and the same with b TypeError The cell isn't a whole number in the 32-bit range, or is a BigInt or a string pathfinding.distance cells: pathfinding: not a map cell, cell ids go from 0 to 559 TypeError A cell is outside 0 to 559 direction() direction(from, to) returns the direction from one cell to another, or undefined when the two cells aren't on one line. direction(300, 301); // 0, east direction(300, 316); // undefined Two cells on one line give that line's direction, whatever their distance. The same cell twice gives 1, as it does in the Dofus client. Error Type Cause pathfinding.direction from must be an integer cell id, and the same with to TypeError The cell isn't a whole number in the 32-bit range, or is a BigInt or a string pathfinding.direction cells: pathfinding: not a map cell, cell ids go from 0 to 559 TypeError A cell is outside 0 to 559 Walk along a path A walk takes two exchanges with the game server: The script sends MapMovementRequest with the path's keyCells, the cautious flag and the map's id. The server grants the walk with MapMovementEvent, whose cells list every cell of the walk. The server sends that message for every actor that walks on the map, so compare its characterId with the character's. It can also grant a shorter walk than the one asked for. Once the walk is over, the character confirms it with MapMovementConfirmRequest, which has no field, and the server answers with MapMovementConfirmResponse. Until then, the server considers the character still walking. Who sends the confirmation depends on the bot: Bot Who sends MapMovementConfirmRequest Full socket Your script, duration milliseconds after MapMovementEvent arrived. Nothing else will. MITM, where session.shared is true The Dofus client, once it has walked the character on screen. Your script only waits for the server's answer. import { currentMap, session } from "asterobot:bot"; import { findPath } from "asterobot:pathfinding"; import { send, wait } from "asterobot:protocol"; import { sleep } from "asterobot:timers"; async function walkTo(cell) { const map = currentMap(); if (map?.cellId === undefined) return; const others = map.actors.filter((actor) => actor.id !== map.characterId); const path = findPath(map.mapId, map.cellId, cell, { occupied: others.map((actor) => actor.cellId), }); if (!path || path.keyCells.length === 0) return; await Promise.all([ wait( (traffic) => traffic.type === "MapMovementEvent" && traffic.payload?.characterId === map.characterId, { timeout: 5000 }, ), send("MapMovementRequest", { keyCells: path.keyCells, cautious: false, mapId: map.mapId }), ]); if (session.shared) { // The Dofus client confirms the walk once it has walked it. await wait("MapMovementConfirmResponse", { timeout: path.duration + 5000 }); return; } await sleep(path.duration); await Promise.all([ wait("MapMovementConfirmResponse", { timeout: 5000 }), send("MapMovementConfirmRequest", {}), ]); } wait() comes first in each Promise.all, so it's already listening when the message goes out, as Send messages explains. This sample times the confirmation with the route it asked for. When the server grants a shorter walk, time the walk it granted instead, for example with findPath() from the first of its cells to the last. Start one walk at a time: a walk started before the previous one ends would start from a cell the character hasn't reached yet. To stop a walk before its end, the Dofus client sends MapMovementCancelRequest with the cell the character stopped on, and the server doesn't answer it. stepEnds tells you which step the character has reached at a given moment. move() does all of this in one call, shorter walks and both kinds of bots included. asterobot:bot describes currentMap(), and the Dofus protocol reference lists the fields of the movement messages.
  3. Smooth a posté un record dans Reference
    asterobot:parameters gives a script the current values of its package's settings: the ones declared with export const parameters and shown on the bot's Settings tab, under Package settings. Declarations describes how to declare them, and Read settings shows how to use them. import { values, onChange, offChange } from "asterobot:parameters"; Whose settings Each package gets its own copy of this module. Any file of a package that imports it reads that package's settings, declared in that package's index.js, and nothing else. A dependency reads its own settings, never those of the package that uses it, and no package can read another package's settings. Settings in dependencies explains how that looks on the bot's page. values values is a read-only object with one property per declared setting. Declared type Value in values bool A boolean string A string int A whole number. It's a regular number, not a BigInt, unlike 64-bit fields in game messages. double A number array An array of values of the of type map An object whose keys are strings and whose values have the of type The values are the ones saved for this bot, on top of the declared defaults. Settings are saved under the package's name, whatever its version, so they carry over when the bot moves to a new version. A saved value that no longer fits the declaration, because the setting was renamed, removed or given another type, is ignored, and the default applies. values is live. When someone clicks Apply on the bot's page, the running script reads the new values from its next read on, without restarting. A package's settings are applied all at once, so a script never reads half of an edit. Don't assign to values or delete from it. Package code always runs as a module, which JavaScript makes strict, so Asterobot's refusal throws: assigning a property throws a TypeError with 'Set' on a dynamic object returned false, and deleting one throws Could not delete property "<name>" of a dynamic object. Either way the setting keeps the value set on the bot's page. Object.keys(values) lists the settings, in no particular order, and "name" in values tells whether a setting exists. When Asterobot can't read the package's declarations, the bot's page shows Couldn't read this package's settings, and the script starts with no settings at all: values is empty, and every read returns undefined. While Asterobot reads the declarations themselves, values is an empty object too, so a declaration can't depend on a setting's value. onChange() onChange(handler) returns a handle, { id }, where id is a BigInt. Parameter Type Description handler function Called with { name, value } for each setting whose value changed When settings of this package are applied to the running script, handler runs once for each setting whose value actually changed, with the setting's name and its new value, of the type shown in the table above. Settings applied with the value they already had don't call it, and arrays and maps count as changed only when their content differs. It doesn't run when the script starts, and it never runs for another package's settings. With several handlers, the order they run in isn't defined. Settings applied while no script runs are saved, and the next start reads them from values. In the rare case where the script already has 32 operations pending when someone applies settings, the settings are saved, but the running script only gets them at its next start. See Limits. An error in a handler stops the script: What happened Text in the Problems alert The handler threw parameters.onChange handler <number>: <error> An async handler's promise rejected unhandled Promise rejection: <error> onChange() throws a TypeError at once when: Error Cause parameters.onChange handler must be callable handler isn't a function maximum behavior handlers reached 256 onChange() handlers are already registered. This count is separate from the message handlers of asterobot:protocol. If the script is already stopping, onChange() throws the reason it's stopping. onChange(({ name, value }) => { botInfo(`Setting ${name} is now ${value}`); }); offChange() offChange(handle) returns undefined. It removes the handler that onChange() registered, given the handle it returned. It never throws: a handle that was already removed, or anything else, is ignored. It doesn't remove handlers registered with on(), onTraffic() or intercept(): use off() for those. Types Type Definition ParameterScalar A boolean, a string or a number ParameterValue A ParameterScalar, a read-only array of them, or a read-only object of them ParameterChange { name: string, value: ParameterValue }, what onChange() handlers receive ParameterSubscription { id: bigint }, the handle onChange() returns When a value applied from the bot's page doesn't fit its declaration, nothing is saved and the page shows Couldn't apply the settings with the reason. Error messages lists those reasons.
  4. Smooth a posté un record dans Reference
    asterobot:runtime lets a script read facts about the packages it's made of: its own package, the packages it depends on, and the built-in asterobot: modules. Nothing in it can change a package. import { packages } from "asterobot:runtime"; packages is the module's only export. It's a frozen object with one function, info(). There's no flat info export, so it can't be confused with info() from asterobot:console. packages.info() packages.info(name) returns a frozen PackageInfo object. Parameter Type Description name string A package name, such as "my-first-script" or "@alice:pathfinder", or the name of a built-in module, such as "asterobot:protocol" The names it knows are: every package in the running script: the bot's package and, all the way down, the packages its dependencies lock; the built-in modules asterobot:protocol, asterobot:bot, asterobot:timers, asterobot:gamedata, asterobot:console and asterobot:runtime. asterobot:parameters isn't one of them: each package has its own copy of that module, and asking for it throws package "asterobot:parameters" is not installed. An inline script, loaded as code rather than as a library package, is the package api-script with the version local. packages.info() throws when: Error Type Cause packages.info requires a module or package name TypeError name is missing, empty or not a string. Despite the wording, a module path isn't accepted. package "<name>" is not installed Error No package in the running script has that name package "<name>" is ambiguous across <count> locked versions Error The script's dependencies lock more than one version of that package, so the name alone doesn't say which const self = packages.info("my-first-script"); botInfo(`${self.name} ${self.version}, ${self.provenance}`); On a local package, this writes my-first-script 1.0.0, local to the bot's console. packages.info() also answers while Asterobot reads the package's declarations, so a declaration can use it. PackageInfo Property Type Value name string The package's name version string The package's version. "embedded" for a built-in module. publisher string The part before the colon in @publisher:name: "asterobot" for official packages and built-in modules, "" for a local package provenance string "local", "official", "community", or "builtin" for a built-in module integrity string The checksum Asterobot recorded when it installed the package from the marketplace, starting with sha256-. "" when there's none, as for a local package, and "embedded" for a built-in module. permissions array of strings The permissions listed in the package's asterobot.json. An empty array when it lists none, and always for a built-in module. compatibleVersion string The Asterobot versions the package declared it works with, such as ">=1.5.0 <2.0.0", or "" The type in the editor also lists "builtin-native" as a possible provenance. Asterobot never returns it. A package's provenance comes from its name: a name starting with @asterobot: is official, another name starting with @ is community, and a name without @ is local. Names and versions explains the rules, The manifest describes asterobot.json, and Compatibility covers compatibleVersion. Permissions permissions only reports what a package declares. Asterobot doesn't check it today: any package can use every function of every built-in module, whatever it lists. Note Coming soon. Asterobot will enforce the permissions a package declares. Until then, treat the list as information for the people who install your package. Permissions has the details.
  5. Smooth a posté un record dans Reference
    asterobot:console writes to Asterobot's own log, the one shown in Server > Console and in the terminal Asterobot runs in. It's for a script's diagnostics. To show something to the person watching a bot, use botInfo() and the other functions of asterobot:bot instead: they write to that bot's own console. import { console, debug, log, info, warn, error } from "asterobot:console"; Scripts have no global console. The console here is an object this module exports, so it has to be imported like the rest. Exports Export Level of the line debug(...values) debug log(...values) info. log is the same function as info. info(...values) info warn(...values) warn error(...values) error console A frozen object with the same five functions: console.debug(), console.log(), console.info(), console.warn() and console.error() Each function takes any number of values, writes them as one line joined with spaces, and returns undefined. They never throw. info("Route computed in", elapsed, "ms"); console.warn("No zaap found for map", mapId); Where the lines appear Lines go to Server > Console and to the terminal. By default, debug lines are left out of both: set Server level to DEBUG on Server > Console to see them there. In Server > Console, a line doesn't say which bot wrote it. When several bots run scripts, write the bot's name or something that identifies it into the text yourself. How values print Values are turned into text the same way as for botInfo(): strings, numbers and booleans print as JavaScript would, a BigInt prints without its n, undefined and null print <nil>, an array prints as [1 2 3], an object as map[name:value], and an Error as map[]. Log String(error) for errors. The table on asterobot:bot has every case. At a module's top level Code at the top level of a module also runs while Asterobot reads the package's declarations, and what these functions write at that moment is thrown away. The same code runs again when the script starts, and its lines appear then. Logging and debugging compares the two consoles and when to use each.
  6. Smooth a posté un record dans Reference
    asterobot:gamedata reads the texts and the data of the game version the bot uses: items, monsters, maps, spells and everything else Asterobot extracts from the game files. It only reads. Game data overview, Lookups and SQL show how to use it. import { text, record, table, find, search, query } from "asterobot:gamedata"; These are the module's only exports. There's no gamedata object to import. Export Returns What it reads text() A string or undefined, right away One game text, by id or key record() An object or undefined, right away One row of a table, by id table() A promise of an array The rows of a table, a page at a time find() A promise of an array The rows whose columns equal given values search() A promise of an array Records found by their name query() A promise of a result The answer to a read-only SQL query What the data is The data comes from the game version the bot is set to. text() and search() answer in the language the script was started with, session.language, which is fr for every start made from Asteroboard today. Asterobot looks for the data when the script starts. When that game version has no extracted game data at that moment, record() returns undefined, and table(), find(), search() and query() resolve with undefined rather than an array, until the script starts again after the data is extracted. When Asterobot can't load the game's texts in that language, text() returns undefined for every key. Extract game data explains how to get the data. Table and column names, such as monsters or name_id, are the ones of the extracted data, and they can change between game versions. Browse them on the Game page's game data (see Game data), and try queries in the SQL tab. In the rows these functions return, numbers are regular JavaScript numbers, not BigInt. A packed column arrives as an array of objects, and a JSON column as the value it holds. record(), table() and find() leave empty columns out of their objects. table(), find(), search() and query() share these rules: They reject after 30 seconds, and there's no option to change that. Each call counts as one of the 32 operations a script can have pending at once, along with send() and request(). Past that, the promise rejects with behavior resource limit exceeded: maximum pending operations reached. limit defaults to 200 rows and can't go above 5000. A limit or offset that isn't a whole number greater than 0 is ignored. text() text(key) returns a string, or undefined. Parameter Type Description key number or string A text id, as found in columns such as name_id, or a text key such as "ui.common.classic" It returns the text in the script's language, or undefined when no text has that id or key. When the game's texts are loaded, text() throws a TypeError with gamedata.text key must be a string or integer for any other key: a fraction, a number outside the 32-bit range, a BigInt, undefined or an object. Check a value exists before passing it. record() record(table, id) returns an object, or undefined. Parameter Type Description table string The table's name, such as "monsters" id number The record's id: a whole number, not a BigInt It returns the row with that id, or undefined when there's none. Error Type Cause gamedata.record table must be a non-empty string TypeError table is missing, empty or not a string gamedata.record id must be an integer TypeError id isn't a whole number, is too big to be exact in JavaScript, or is a BigInt or a string datacenter: no such table: "<table>" Error This game version has no table with that name. String(error) gives GoError: datacenter: no such table: "<table>". const monster = record("monsters", 31); if (monster) botInfo("Monster 31 is", text(monster.name_id)); table() table(name, options?) returns Promise<object[]>. Parameter Type Description name string The table's name options.limit number How many rows to return. Default 200, at most 5000. options.offset number How many rows to skip first. Default 0. It resolves with the rows, in the order of their ids. A call without options returns the first 200 rows, not the whole table. The promise rejects with gamedata.table name must be a non-empty string when name isn't a non-empty string, and with datacenter: no such table: "<name>" for a table this game version doesn't have. const page = await table("items", { limit: 50, offset: 100 }); find() find(name, filter, options?) returns Promise<object[]>. Parameter Type Description name string The table's name filter object Column names and the values they must equal. A row matches when every column equals its value. options.limit number How many rows to return. Default 200, at most 5000. offset is ignored. Error Cause gamedata.find table must be a non-empty string name isn't a non-empty string gamedata.find filter must be a non-empty object filter is missing, empty, an array, or not an object datacenter: no such table: "<name>" This game version has no table with that name datacenter: no such column: table "<name>" has no column "<column>" A filter column doesn't exist in the table const spots = await find("maps__interactive_elements", { gfx_id: 1018 }); search() search(query, options?) returns Promise<Match[]>. Parameter Type Description query string The name, or part of it options.table string Only search this table's records, such as "monsters". Without it, every named record is searched. options.limit number How many matches to return. Default 200, at most 5000. Each word of query matches the beginning of a word, so a partial name works. Shorter names come first. Without options.table, the results take one match from each table in turn, so the first results show every kind of thing that has that name. A query made only of spaces resolves with an empty array. Error Cause gamedata.search query must be a non-empty string query isn't a non-empty string gamedata.search options.table must be a string options.table is there but isn't a string datacenter: no such table: "<table>" This game version has no table with that name datacenter: no text for this language: "<language>" The extracted data has no names in the script's language const [hit] = await search("Bouftou Royal", { table: "monsters" }); if (hit) botInfo(hit.name, "is monster", hit.id); query() query(sql, params?, options?) returns Promise<QueryResult>. Parameter Type Description sql string One or more SQL statements params array Values for the ? placeholders, in order options.limit number How many rows to return. Default 200, at most 5000. The game data is opened read-only, so any statement that writes fails, and attaching another database is refused. Every statement in sql runs, and the result is the last one's: "SELECT 1; SELECT 2" answers 2. A query runs for at most 10 seconds, and its text can't be longer than 65,536 bytes. Pass values through params instead of writing them into the SQL: a number written into the text arrives as text, and doesn't equal the number stored in a column. In rows, an empty value is null. Error Cause gamedata.query sql must be a non-empty string sql isn't a non-empty string gamedata.query params must be an array params is there but isn't an array datacenter: empty query sql holds only spaces datacenter: query is <size> bytes, over the 65536 byte limit The SQL text is too long datacenter: followed by the database's message The SQL is wrong, names a table or column that doesn't exist, or tries to write, in which case the message mentions attempt to write a readonly database The promise also rejects when the query runs for more than 10 seconds. const { columns, rows, truncated } = await query( "SELECT _id, name_id FROM monsters WHERE _id = ?", [31], ); Types Type Shape Row An object whose keys are the table's column names Window { limit?: number, offset?: number }, the options of table() and find() SearchOptions { table?: string, limit?: number } QueryOptions { limit?: number } Match { table: string, id: number, nameId: number, name: string }: the record's table and id, to pass to record(), the id of the text its name comes from, and the name in the script's language QueryResult { columns: string[], rows: unknown[][], truncated: boolean }. columns lists the column names in order, and they can repeat, which is why each row is an array aligned with them. truncated is true when the query had more rows than limit.
  7. Smooth a posté un record dans Reference
    asterobot:timers has a single export, sleep(). Scripts have no setTimeout() or setInterval(), so pauses, delays and repeated tasks are all built with it. import { sleep } from "asterobot:timers"; sleep() sleep(milliseconds) returns Promise<void>. Parameter Type Description milliseconds number How long to wait. 0 and fractions such as 2.5 are accepted. The promise resolves once the time has passed and the script gets to it: when other events are waiting to be handled, it resolves after them. While a script awaits sleep(), its message handlers keep running. The promise rejects with: Error Cause timeout/milliseconds must be a finite positive Number milliseconds is negative, NaN or Infinity, or isn't a number at all. A BigInt such as 1000n and a string such as "1000" are refused too. timeout/milliseconds is too large milliseconds is too big to be a duration, more than about 292 years behavior resource limit exceeded: maximum timers reached 128 calls to sleep() are already pending. See Limits. When the script stops while a sleep() is pending, its promise rejects with the reason the script stopped, such as behavior runtime closed after Stop. Repeating a task Start the loop without awaiting it, so the entry function can finish. A start only counts as successful once that function has returned, which matters for launch.reason. import { session, botInfo, botWarn } from "asterobot:bot"; import { send } from "asterobot:protocol"; import { sleep } from "asterobot:timers"; async function everyMinute() { while (true) { await sleep(60000); botInfo("Still running"); } } export default async function behavior(launch) { // Not awaited, and caught: a rejection nobody catches stops the script. everyMinute().catch((error) => botWarn("Timer loop stopped:", 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, }); } } When you click Stop, the pending sleep() rejects, so the console shows Timer loop stopped: behavior runtime closed as the script ends. Don't poll in a tight loop to wait for a message: await wait(selector, { timeout }) from asterobot:protocol resolves as soon as the message arrives. And never keep the script busy without an await: code that runs for more than 250 ms in one go stops the script. Async and timing explains why.
  8. Smooth a posté un record dans Reference
    asterobot:bot gives a script what belongs to its own bot: the facts about the game session, the map its character stands on, a way to close the game connection, and the bot's console. The launch argument of the entry function is described on this page too, because session repeats its two values. import { session, currentMap, disconnect, botDebug, botInfo, botWarn, botError } from "asterobot:bot"; These are the module's only exports. There's no bot object to import. The launch object Asterobot starts a script by calling the default export of the package's index.js with one argument, launch: import { session, botInfo } from "asterobot:bot"; import { send } from "asterobot:protocol"; export default async function behavior(launch) { botInfo("Launch:", launch.reason, launch.generation); // 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 the first start of a Full socket bot, the console shows Launch: initial 1. Property Type Value reason string "initial", "resume" or "reload", as described below generation BigInt Which start this is on the current game connection: 1n for the first, then one more for each start. A start whose script fails while starting still counts. A start that fails before any code runs, such as one with a syntax error, doesn't. A new game connection counts from 1n again. reason When a script starts with it "initial" On a Full socket bot's game connection, as long as no script has started successfully on it yet. A start is successful once the default export's function has finished. "resume" Every later start on the same connection, such as Play after Stop. 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. The default export must be a function that returns a promise, usually an async function. The entry function explains what to do for each reason. session session is a frozen object. Its values are set when the script starts and don't change while it runs. Property Type Value gameToken string The ticket the login server issued for this game connection. A Full socket script sends it as ticketKey in IdentificationRequest. serverId number The game server chosen when signing in, fixed for the whole connection. On a MITM bot, the server the Dofus client connected to. It says nothing about where the character is. language string The language code the script was started with. Right now every start made from Asteroboard, with Play, Run or a load from the bot's menu, gives fr, whatever language the bot itself is set to. text() and search() from asterobot:gamedata use this language too. launchReason string The same value as launch.reason behaviorGeneration BigInt The same value as launch.generation shared boolean true on a MITM bot, where a person plays the same character through their Dofus client. false on a Full socket bot. Every function behaves the same whatever shared says. It exists so that a script that acts on its own, moving or fighting, can decide to stay passive while someone else is playing. 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. While Asterobot reads a package's declarations, there's no session yet. session then holds empty values: "" for the strings, 0 for serverId, "initial", 1n and false. See Declarations. currentMap() currentMap() returns the map the bot's character stands on, or undefined until the character has entered a map. import { currentMap, botInfo } from "asterobot:bot"; const map = currentMap(); if (map?.cellId !== undefined) { botInfo("Map", map.mapId, "cell", map.cellId, "actors", map.actors.length); } For a character on cell 300 of map 154010883, next to a monster group, the console shows Map 154010883 cell 300 actors 2. Property Type Value mapId BigInt The map's id characterId BigInt The id of the bot's character, the one MapMovementEvent gives in characterId when the character walks. Absent until the character is selected. cellId number The cell the character stands on. Absent while the map doesn't place the character yet. actors array Every actor the server placed on the map, the character included, sorted by id. Each one is an object with id, a BigInt, and cellId, a number. Asterobot follows the map itself, for the whole game session, from the messages the bot sends and receives: MapInformationRequest when the character arrives on a map, MapComplementaryInformationEvent for the actors already there, then GameRolePlayShowActorsEvent, MapMovementEvent and ContextRemoveElementEvent for the actors that come, walk and leave. So a script started or reloaded in the middle of a session reads where the character is, not only what it saw since it started. Each call returns a new object holding the values of that moment. An actor's cellId is the cell its walk ends on from the moment the server grants the walk, before the actor gets there. When the character's own walk stops early, for example because the player clicked elsewhere on a MITM bot, the MapMovementCancelRequest sent for it moves the character to the cell where it stopped. Fights aren't followed. disconnect() disconnect() returns undefined. It closes the bot's game connection and returns right away, without waiting for the connection to close. The script stops as a result, and the bot's page shows The behavior stopped with an error with the text behavior requested Game disconnect. Calling disconnect() again, or while the script is already stopping, does nothing. Warning On a MITM bot, the connection is the game session of the person playing. disconnect() closes it for their Dofus client too. Check session.shared first if that matters to your package. Asterobot doesn't connect the bot again by itself. Disconnect, delete and restart describes what happens next from the player's side. botDebug(), botInfo(), botWarn() and botError() Each takes any number of values and returns undefined: botInfo("Kamas:", traffic.payload?.kamas); botWarn("Reply not delivered:", String(error)); The line goes to the bot's Console tab as a Script line, at the level the function names: debug, info, warn or error. Lines of every level show there, debug included. The values are joined with spaces. These functions never throw. Asterobot turns each value into text on its own, and only strings, numbers and booleans come out the way JavaScript would print them: Value Printed as A string As it is A number 42, 1.5 A BigInt Its digits, without the n: 1500 A boolean true, false undefined or null <nil> An array Items separated by spaces, in brackets: [1 2 3] An object map[ then its properties sorted by name: map[kamas:1500 name:Airelle] An Error map[], so log String(error) instead To print an object readably, build the text yourself, for example with a template string. JSON.stringify() throws on any value that holds a BigInt, with the message Do not know how to serialize a BigInt, and message payloads often do. When you open a bot's Console tab, it starts with the last 200 lines Asterobot kept for that bot. Code at the top level of a module also runs while Asterobot reads the package's declarations. What these functions write at that moment is thrown away. To write to Asterobot's own log instead, use asterobot:console. Logging and debugging compares the two and suggests a way to track down a problem.
  9. Smooth a posté un record dans Reference
    asterobot:protocol is how a script exchanges messages with the game: it sends them, waits for them, reacts to them, and changes or blocks them on their way through. Listening, Waiting, Sending and Intercepting explain how to use each part. import { send, request, wait, on, onTraffic, intercept, off } from "asterobot:protocol"; These are the module's only exports. There's no protocol object to import. Export What it does send() Sends a message, and resolves once it's sent request() Sends a request, and resolves with the response matched to it wait() Resolves with the next inbound message that matches on() Calls a handler for every inbound message that matches onTraffic() Calls a handler for every message, in both directions intercept() Decides whether a message goes on unchanged, is dropped or is replaced off() Removes a handler registered with on(), onTraffic() or intercept() Message names and fields are the ones in the Dofus protocol reference. send() send(type, payload, options?) returns Promise<void>. Parameter Type Description type string The message's name, such as "ChatChannelMessageRequest" payload object The message's fields. Pass {} for a message you send without fields: leaving payload out is an error. options.timeout number Milliseconds before the promise rejects. Default: 30000. options.to "server" or "client" Where the message goes. Default: "server". send() builds the message and writes it. The promise resolves as soon as the message is written: it doesn't wait for an answer, so use wait() when you need to know what the game did. With to: "server", the message goes to the game server as if the Dofus client had sent it. onTraffic() handlers see it go out, and no interceptor is asked about it. With to: "client", the message is delivered as an event from the server: On a MITM bot, Asterobot writes it to the Dofus client, which acts on it as if the server had sent it. On a Full socket bot there's no Dofus client, so the message only reaches the script's own on(), onTraffic() and wait(). Either way it arrives as an inbound message, so the same script works on both kinds of bot. On a MITM bot, an IdentificationRequest sent to the server is never forwarded, because the Dofus client has already identified the session. The promise resolves anyway, and the bot's Console shows the warning Refused to forward a message that would re-authenticate a relayed session. The promise rejects with: Error Cause protocol type must be a non-empty semantic message name type is missing, empty or not a string semantic protobuf message is not mapped: <type> Asterobot has no message with that name. Check the spelling, and see After a game update. <type>: expected an object payload is missing, null or undefined <type>.<field>: unknown protobuf field The message has no field with that name, for example IdentificationRequest.token: unknown protobuf field Another text starting with <type> A field's value doesn't fit. Payload errors lists them all. protocol options must be an object options isn't an object, for example send(type, payload, 5000) unknown protocol option "<name>" options has a key other than timeout and to timeout/milliseconds must be a finite positive Number timeout isn't a number greater than 0 timeout/milliseconds is too large timeout is too big to be a duration send option "to" must be "server" or "client" to isn't a string unknown send target "<value>": expected "server" or "client" to is a string other than those two behavior resource limit exceeded: maximum pending operations reached 32 operations are already pending. See Limits. It also rejects when the message can't be written before the timeout, or when the game connection closes. await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" }); request() request(type, payload, options?) returns Promise<ProtocolTraffic>. Parameter Type Description type string The request's name payload object Its fields, as for send() options.timeout number Milliseconds before the promise rejects. Default: 30000. options.to is accepted and ignored: a request always goes to the server. request() sends the message as a request carrying a new uid, and resolves with the response the server sends back with the same uid, as a traffic object whose kind is "response". Only a few Dofus requests get such a response. The game answers most of them with an event, or not at all, and request() then waits for its whole timeout before rejecting. For those, send the message with send() and wait() for the event. The promise rejects with the same argument, payload and limit errors as send(), apart from the two about to, and with: Error Cause context deadline exceeded No response came back before the timeout mitm relay refuses to identify: this Game session was already authenticated by the real DOFUS client The script sent IdentificationRequest on a MITM bot. The request isn't forwarded. wait() wait(selector, options?) returns Promise<ProtocolTraffic>. Parameter Type Description selector string or function A message name, "*" for any message, or a function that receives a traffic object and returns true for the message you want options.timeout number Milliseconds before the promise rejects. There's no default: without a timeout, wait() waits until the script stops. options.to is accepted and ignored. wait() looks at inbound messages that arrive after the call, and resolves with the first one that matches. When several waits match the same message, they all resolve with it. For each message, the handlers registered with on() and onTraffic() run before the waits are checked. Since it only sees what arrives after the call, start the wait before you send the message it waits for, for example with Promise.all(): the answer can arrive before await send() has resolved. Waiting shows how. A selector function runs for every inbound message until the wait settles. If it throws, the whole script stops, with evaluate protocol wait selector: <error>. The promise rejects with: Error Cause protocol wait timed out The timeout passed before a message matched protocol selector cannot be empty selector is "" protocol selector must be a semantic type, '*', or predicate selector is neither a string nor a function protocol options must be an object, unknown protocol option "<name>", timeout/milliseconds must be a finite positive Number, timeout/milliseconds is too large The options are wrong, as for send() behavior resource limit exceeded: maximum waits reached 128 waits are already open const echo = await wait( (traffic) => traffic.type === "ChatChannelMessageEvent" && traffic.payload?.content === "Hello", { timeout: 5000 }, ); on() on(selector, handler) returns a handle, { id }, where id is a BigInt. Parameter Type Description selector string or function A message name, "*" for any message, or a function that returns true for the messages to handle handler function Called with the traffic object of each matching inbound message. It may be async. on() sends nothing and returns right away. From then on, handler runs for every inbound message that matches, until you pass the handle to off() or the script stops. Outbound messages never reach it, whatever the selector. Handlers run one message at a time, in the order they were registered, onTraffic() handlers included. A handler registered while a message is being handled starts with the next message. An async handler returns at its first await, and the next handler doesn't wait for it to finish. An error in a handler stops the script: What happened Text in the Problems alert The handler threw Game traffic handler failed: <error> An async handler's promise rejected unhandled Promise rejection: <error> The selector function threw evaluate protocol handler selector: <error> on() throws a TypeError at once, instead of returning, when: Error Cause protocol selector cannot be empty selector is "" protocol selector must be a semantic type, '*', or predicate selector is neither a string nor a function protocol.on handler must be callable handler isn't a function maximum behavior handlers reached 256 handlers from on(), onTraffic() and intercept() are already registered If the script is already stopping, on() throws the reason it's stopping. const handle = on("ChatChannelMessageEvent", (traffic) => { if (!traffic.payload) return; botInfo(`${traffic.payload.senderName}: ${traffic.payload.content}`); }); onTraffic() onTraffic(handler) returns a handle, { id }. It works like on("*", handler), except that it receives messages in both directions: what the game server sends; what the Dofus client sends, on a MITM bot; what the script sends with send() and request(); what someone sends from the bot's Network tool. A message dropped by an interceptor never reaches it. onTraffic() throws a TypeError with protocol.onTraffic handler must be callable when handler isn't a function, and with maximum behavior handlers reached past the limit. Errors in the handler stop the script, with the same texts as for on(). intercept() intercept(selector, handler) or intercept(handler) returns a handle, { id }. Parameter Type Description selector string or function Optional. A message name, "*", or a function that returns true for the messages to decide about. With only one argument, handler is asked about every message. handler function Called with the traffic object, and returns a decision. It must answer synchronously. An interceptor is asked about the messages the bot carries: every inbound message from the game server, and on a MITM bot every outbound message from the Dofus client. It's never asked about: a message the script sends, nor one sent from the bot's Network tool, although both still reach onTraffic(); on a MITM bot, the Dofus client's first message, and the responses to the script's own request() calls; a message Asterobot can't read at all. A MITM bot forwards such a message without showing it to the script, and a Full socket bot closes its game connection when it receives one. For each message, interceptors are asked in the order they were registered. One whose selector doesn't match, or that returns no decision, leaves the message to the next. The first decision wins, and the interceptors after it aren't asked. As soon as one interceptor is registered, every message the bot carries waits for the script, whatever the selectors say, because Asterobot checks them inside the script. Remove an interceptor with off() once you no longer need it. Return value What happens undefined, null, or any value not listed below No decision. The next interceptor is asked, and when none decides, the message goes on unchanged. "drop" The message stops here. On a MITM bot the other side never receives it, and on every bot on(), onTraffic() and wait() never see it. On a Full socket bot, dropping the response to a request() makes that request() wait for its timeout. { payload } The message goes on, rebuilt from these fields. It keeps its name, its kind and its uid. A field you leave out of the object is left out of the message, and the fields Asterobot has no name for are lost. { raw } The message is replaced by these bytes, a Uint8Array or an ArrayBuffer. They stand for the whole message as it travels on the connection, envelope included, not for traffic.rawAny. raw is used when both raw and payload are present. After a replacement, the script's handlers and waits see the new message. A MITM bot forwards raw bytes even when Asterobot can't read them, and its handlers then see the original message. On a Full socket bot, bytes Asterobot can't read change nothing. An async handler returns a promise, and a promise isn't a decision: the message goes on before the promise settles. If that promise rejects, nothing catches it, and the script stops with unhandled Promise rejection: <error>. The message waits at most 50 ms for a decision, counted from when it arrives, so time the script spends on other work counts too. Past that, it goes on unchanged and nothing is written anywhere: the handler keeps running, and its answer is ignored. On a MITM bot, the person playing feels a slow interceptor as lag. Intercepting explains these rules with examples. When a handler throws, or its selector function throws, the script doesn't stop. The message goes to the next interceptor, or on unchanged, and the bot's Console gets an error line for that message. When several interceptors fail on the same message, the line is written for the first failure only, and again at the tenth, ending with (10 times). Console line Cause interceptor failed: <error> The handler threw, or ran for more than 250 ms interceptor selector failed: <error> The selector function threw build replacement payload for <type>: <error> The payload doesn't fit the message. <error> is one of the payload errors. re-encode <type>: <error> Asterobot couldn't rebuild the message with the new fields cannot replace the payload of <wire name>: this build has no name for it, so there is nothing to build a replacement from - use { raw } instead { payload } was returned for a message without a readable name intercept() throws a TypeError, like on(), for a bad selector, with protocol.intercept handler must be callable when handler isn't a function, and with maximum behavior handlers reached past the limit. intercept("ChatChannelMessageRequest", (traffic) => { if (!traffic.payload) return; return { payload: { ...traffic.payload, content: traffic.payload.content.toUpperCase() } }; }); On a MITM bot, this turns everything the player types in chat into capital letters before the server receives it. off() off(handle) returns undefined. It removes a handler registered with on(), onTraffic() or intercept(), given the handle they returned. A handler removed while a message is being handled doesn't run for that message if its turn hasn't come yet. off() never throws: a handle that was already removed, or anything else, is ignored. It doesn't remove what onChange() from asterobot:parameters registered: use offChange() for that. The traffic object Handlers, selector functions and interceptors receive a traffic object, and wait() and request() resolve with one. Traffic explains its properties in depth. Property Type Value sequence BigInt The message's position in the bot's traffic. A dropped message leaves a gap. On a MITM bot, the object request() resolves with has 0n. direction string "inbound" from the server towards the client, "outbound" from the client towards the server kind string "request", "event" or "response". The type also lists "unknown", which scripts never receive, because a message Asterobot can't read isn't delivered. uid number The number that pairs a request with its response. -1 on events, and on messages sent with send(). type string The message's readable name, or "" when Asterobot has no name for it wireType string The name the message has on the connection, which changes between game versions typeUrl string The full type identifier of the message's content payload object, or absent The message's fields, when Asterobot could read them unknown boolean true when there's no payload decodeError string, or absent What went wrong while reading the message's content. It can be there along with a payload. rawAny Uint8Array The message's content as bytes, without its envelope. Always present, possibly empty. Payload values A payload's field names are the camelCase names, such as senderName. When sending, the names from the protocol definitions, such as sender_name, work too, but not both for the same field. Payloads covers every case. Field type A script reads A script can send 64-bit integer A BigInt A BigInt, a string of digits, or a whole number no bigger than Number.MAX_SAFE_INTEGER either way 32-bit integer A number A whole number within the field's range Floating point A number A number Boolean A boolean A boolean String A string A string Bytes A Uint8Array A Uint8Array or an ArrayBuffer Enum The value's name, such as "GLOBAL", or a number for a value without a name The name or the number List An array An array Map An object with string keys A plain object Nested message An object An object, or null to leave it out Group of alternatives (oneof) Only the field that's set One field of the group at most A nested message, an optional field or an alternative that the message doesn't set is absent from payload. Other fields are always there, with 0, "", false or an empty array when they're not set. When sending, a field you leave out isn't sent. Setting a field to undefined is an error, and null is only accepted for a nested message. The editor completes message names and payload fields, and knows the types ProtocolTraffic, ProtocolOptions, ProtocolSelector, ProtocolSubscription and ProtocolDecision.
  10. Smooth a posté un record dans Publication
    Every package installed from the marketplace gets a name of the form @publisher:name, built from its file on asterobot.net. You don't type that name anywhere, but you choose what it's built from. The name at first install The first time someone installs a file, Asterobot builds the package's name from @, the file author's asterobot.net name, :, then the file's title. Both parts go through the same conversion: Letters are put in lowercase. Every run of characters other than the letters a to z and the digits 0 to 9 becomes a single hyphen, and hyphens at the start or at the end are dropped. Spaces, punctuation and accented letters all count as other characters. Author Title Package name Alice Chat Tools @alice:chat-tools Some_Author Auto Harvest 2! @some-author:auto-harvest-2 Alice Récolte auto @alice:r-colte-auto The version is the file's version on asterobot.net. In Asteroboard, the preview shows the full name and version after Will install as, before anything is installed. A title with no letter from a to z and no digit leaves nothing to build a name from, and neither does an author name written only with other characters. Such a file can't be previewed or installed. And since people type your package's name in their import statements when they depend on it, a title that converts to a readable name, such as Harvest Helper, serves them better than one full of accents or symbols. Official packages A file the Asterobot team marks as official on asterobot.net installs under @asterobot: followed by its converted title, whoever uploaded it. It shows the Official badge, and the preview's Author line says asterobot. A package imported from a .zip, or installed with library install, takes the name typed for it, which can start with @asterobot: too. It then shows Official without coming from the marketplace. Only a package installed from the marketplace has a Source row on its page: see Trusting a package. The name sticks Asterobot remembers which file each marketplace package came from. When someone installs or updates that file again, the package keeps the name it already has on their Asterobot, even if you've changed the title since. Updates keep reaching them. The name is only built again for someone who didn't have the package yet. So after you rename the file, new users get your package under the new name, while earlier users keep the old one. Bots, saved settings and dependencies all refer to a package by its name, so for Asterobot these are two different packages, and a package that depends on yours by one of the names gets its own copy under that name. The same happens with two files that have the same author and title: they install under the same name. Choose the title before you publish, keep it, and publish every new version on the same file. When the author or the official mark changes Each time Asterobot reads the file again, it checks the publisher part of the name: the file's current author, or asterobot when the file is marked official. When it no longer matches the name a package has: Someone who installs or updates the file again gets it as a new package, under a name built from the new author or mark. Their bots and saved settings stay with the package under the old name. A package that depends on yours under the old name can't be installed any more, because Asterobot refuses to fetch a dependency whose file now has another publisher. A change of your asterobot.net name has the same effect, since it's the publisher part of every name built from your files. A change of title alone is never refused. Names typed by hand Only an installation from the marketplace builds a name from a file. Import a .zip and library install use the name typed for them, which can have the same @publisher:name shape. Such a package isn't tied to any file: it has no Source row, Check for updates skips it, and Asterobot records no checksum of its files. Names and versions gives the rules every name follows. The marketplace describes the marketplace from the website's side.
  11. Smooth a posté un record dans Publication
    A package on asterobot.net changes by getting new versions on the same file. The people who installed it see the update the next time they check for updates, and choose when to move their bots to it. Prepare the new version In Library Manager, open your package's menu and choose Copy to a new version, with a higher version such as 1.0.1 or 1.1.0. Names and versions says which number to raise. Make your changes in the copy, and test it on your bots. Check what your changes do to the settings people saved: a renamed setting loses its values, and a new default doesn't reach the bots that already saved one. See Read settings. Go through Before you publish again. Export the new version, which gives a file such as my-bot-1.0.1.zip. Upload it to the same file On your file's page on asterobot.net, upload a new version of the file: Replace the attachment with the new .zip, so the file still has exactly one attachment. Set the file's version to the new version, written exactly as in your library. Say what changed, especially anything people have to do, such as checking a setting. Keep the title, since a new title gives new users a different package name: see How your package is named. Always update the existing file, never submit a new one for a new version. Asterobot checks for updates against the file a package was installed from, so people who have your package never see a new file as an update. And since a package's name comes from its author and title, a second file with the same title installs under the same name as the first, which only adds confusion. How people get the update Nothing changes on anyone's Asterobot by itself. When someone clicks Check for updates in Library Manager, Asterobot compares each package they installed from the marketplace with the current version of its file: The file's current version is They see Higher than theirs, both being semantic versions Update to followed by the version Lower than theirs Nothing: Asterobot never offers to go back The same as theirs Nothing Different, when either version isn't a semantic version Update to, even for an older version Update to installs the new version next to the old one. Their bots stay on the old version until they move them, one by one, and keep the settings they saved. The old version stays until they remove it. Update packages describes their side. Packages that were installed as dependencies of another package are checked the same way. Packages that depend on yours A package that depends on yours locks one exact version of it. Which version it gets depends on what its author declared, at the moment someone installs that package: The dependency's version The version installed An exact version, such as 1.0.0 That version: your file's current version when it's the same, otherwise the same version from your file's history A range, such as ^1.0.0 The highest matching version, among your file's current version and its history latest Your file's current version Afterwards, the installed package keeps the version it got. Your update doesn't change it, even when the same person installs your new version with Update to. It moves to a newer version of yours when its author publishes an update of their own package, or when someone installs that package again and its declaration picks your newer version. Keep older versions available Asterobot fetches an older version of your package from your file's version history on asterobot.net. A package that locks your 1.0.0, while your file's current version is 1.1.0, can only be installed as long as 1.0.0 is still in that history. When it isn't, installing that package fails. So keep your previous versions in the file's history for as long as other packages may lock them. Never reuse a version number Once a version is published, never publish different code under the same number, not even to fix a mistake a minute later. Asterobot tells versions apart by their number alone: People who already installed that version aren't offered the new code, since Check for updates sees the same version. They only get it if they happen to install that version again. People who install it afterwards get the new code under the same number. Two people with 1.0.1 then run different code, and nobody can tell from the version which one they have. The same goes for packages that lock that exact version: installed before, they keep the old code, installed after, they get the new one. Asterobot can't notice it. It records a checksum of a package's files when it installs the package from the marketplace, and refuses the package if those files change on that computer afterwards. That protects what was downloaded, but the checksum comes from the download itself, so it can't tell that the file on asterobot.net changed. Publish the fix as a new version, such as 1.0.2, instead. That one reaches everyone through Check for updates. When a version goes wrong If you publish a broken version, don't set the file back to an older version number: the people who installed the broken one wouldn't be offered the older one, since Asterobot never offers to go back. Publish a fixed version with a higher number, and say in the description which version to avoid. Next, How your package is named.
  12. Smooth a posté un record dans Publication
    The marketplace is the Library category of asterobot.net, and any asterobot.net member can publish a package there. Publishing means submitting a file on the website with your exported .zip attached. You need an asterobot.net account, as Create an asterobot.net account explains, and the .zip of your package, exported once you've gone through Before you publish. What Asterobot reads from your file When someone pastes the link of your file in Asteroboard, Asterobot reads a few things from the file on asterobot.net: On asterobot.net What Asterobot does with it The file's title Turns it into the second part of the package's name, in lowercase with hyphens: Chat Tools gives chat-tools. Your asterobot.net name, as the file's author Turns it into the first part of the name, the publisher: @alice:chat-tools. The official mark, set by the Asterobot team Replaces the publisher with asterobot, and shows Official instead of Community. The file's version Uses it as the package's version. The file's attachment Installs it as the package. The file must have exactly one attachment: your .zip. Who can download the file Decides who can install the package. Asterobot doesn't read the description, the screenshots or anything else on the file's page, and doesn't check which category the file is in. Those are for the people who look for packages, and they look in the Library category. How your package is named goes into the naming rules. Submit the file Sign in on asterobot.net and open the Library category: https://asterobot.net/files/category/10-library/. Start submitting a new file in that category. Attach your exported .zip, and nothing else. Give the file its title. Choose it with care: it becomes part of the package's name, and a title changed later gives new users a different name. Set the file's version to your package's version, written the same way, such as 1.0.0. A file without a version can't be installed. Write the description: what the package does, whether it works on Full socket bots, MITM bots or both, its settings and actions, the packages it depends on and the permissions it declares. Submit the file. Anything else you fill in on the website is for the people reading your file's page: it doesn't change how Asterobot installs the package. Check that it installs Install your package on your own Asterobot, the way anyone would: Copy the address of your file's page. In Library Manager, click Download package, paste the address in File ID or URL, then click Preview. Check the card: your title, the Community badge, your name after Author, and after Will install as, the name and version you expect, such as @alice:chat-tools@1.0.0. Click Install. Package installed appears, and the package has its own row in Library Manager. Start it on a bot. Remove it from your library afterwards if you don't need it. If Preview says File not found, or you don't have permission to view it., check that your file is visible on asterobot.net, that your Asterobot is signed in, and that the title and your asterobot.net name each contain at least one letter from a to z or a digit. When the installation itself fails, Asteroboard only says Couldn't install the package. The causes on your side: Cause Fix The file has more than one attachment, or none Keep your .zip as the file's only attachment Asterobot doesn't find index.js in the archive Export the package again from Library Manager and attach that archive. See Zip files and the command line. The archive is larger than 32 MiB Leave out what the package doesn't need The file has no version, or its version contains / or \ Set a version such as 1.0.0 The archive's asterobot.json isn't valid JSON, or a dependency's version isn't valid Fix the manifest, export again and upload the new archive as a new version A dependency has no URL, its URL points to a file whose author doesn't match the dependency's name, or the version it locks isn't available See Dependencies When a dependency is what failed, your package itself may already be installed, without that dependency. Remove it before trying again. Share the link The address of your file's page, such as https://asterobot.net/files/file/12-chat-tools/, is all people need. They paste it in Download package, or in Download one wherever they choose a package for a bot, as Install from the marketplace shows. The number at the start of the last part of the address, 12 here, is the file's ID, which works on its own too. Asterobot only reads that number, so an address that still has an old title in it keeps working after you rename the file. Other authors use the same address as the URL of a dependency on your package. Who can install it Asterobot downloads the file with the asterobot.net account it's signed in with. When that account isn't allowed to download your file, the preview says You don't have permission to download this file. and Install stays disabled. Who can download a file is decided on asterobot.net, not in Asterobot. The marketplace describes the website's side. Next, when you change your package, Publish an update.
  13. Smooth a posté un record dans Publication
    Once your package is on asterobot.net, people install it on their own Asterobot, with their own bots and their own settings, and a version they installed stays as it is. So go through this list before you upload anything. Each item links to the page that explains it. It runs on both kinds of bot The script identifies a Full socket bot on its initial launch, with the lines from Identify and run. Without them, a Full socket bot never gets into the game. You tested it on a MITM bot (man-in-the-middle: the bot relays the game session of a Dofus client someone plays), and on a Full socket bot when what the package does allows it. Every await inside a handler sits in try/catch, and every wait() has a timeout. An error that escapes a handler stops the script: see Errors. The editor's error badge shows no error. The script says so on the bot's console when something it expects doesn't happen, and never writes session.gameToken anywhere. Settings and actions Package settings, on a bot set to your package, shows your cards and not Couldn't read this package's settings. Every setting has a label, a sensible default, a description when the label isn't enough, and a unit for a number that has one. See Declare settings. The keys of your settings are the ones you'll keep. Renaming a setting in a later version loses the values people saved: see Read settings. Each action works while the script runs, and its key reads well in the toasts that end with finished and failed. Dependencies and manifest Every dependency is published, has the URL of its file on asterobot.net, its marketplace name and an exact version. See Dependencies. If the package declares a compatibleVersion, its page shows Compatible on your Asterobot. See Compatibility. The permissions list what the package really uses. See Permissions. The manifest has no sourceId and no integrity. A package created, copied or imported in Asteroboard never has them. Version and name The version is a semantic version, three numbers such as 1.0.0. See Names and versions. You never published different code under this version. See Publish an update. The title you'll give the file, and your asterobot.net name, contain letters from a to z or digits, since the package's name is built from them. See How your package is named. The archive You exported the version you tested, with Export in Library Manager. See Zip files and the command line. The archive weighs less than 32 MiB. You imported the exported .zip under another local name, such as my-bot-check, and that package starts on a bot. That's the code people will install. Remove the check package afterwards. Next, Publish on asterobot.net.
  14. A package leaves Asterobot and comes back as a .zip file: that's what Export produces, what Import a .zip reads, and what you upload to publish on asterobot.net. The command line installs packages from a folder instead, which lets you write them in the code editor you already know. What goes in a .zip The simplest archive, and the one Export produces, has the package's files at its root: index.js asterobot.json lib/format.js The rules: index.js has to be at the root of the archive. Only .js and .mjs files are read, in any subfolder, following the paths described in Package layout. Other files, such as a README or images, are left out and not kept. asterobot.json is optional, next to index.js. Its sourceId and integrity are removed at import, as The manifest explains. An archive can't be larger than 32 MiB, whether it's imported or installed from the marketplace. The archive doesn't carry the package's name and version. They come from the import dialog, or from the file on asterobot.net. Asterobot also accepts an archive whose files all sit in a single folder, such as my-bot/index.js, which is what compressing a folder produces. That only works when the archive lists nothing but the folder's contents. With a file or a second folder next to it, or with an entry for the folder itself, which some archiving tools add, Asterobot doesn't find index.js and the import fails. To avoid surprises, compress the files of the package rather than the folder that holds them. Export In Library Manager, open a package's menu, or right-click its row, and choose Export. The package's page has the same menu. Your browser downloads the archive. The file is named after the package and its version, with the @ removed and the : turned into -: version 1.2.0 of @alice:chat-tools gives alice-chat-tools-1.2.0.zip, and my-bot 1.0.0 gives my-bot-1.0.0.zip. The archive holds every file of the package at its root: its modules, and asterobot.json when it has one. The manifest is exported as it is. For a package installed from the marketplace, that includes its sourceId and integrity, which an import removes but library install keeps. Every package can be exported, local or not. Import a .zip Share your package walks through the dialog, and Manage your library lists its messages. The details that matter to an author: Version fills in by itself when the file's name ends with a hyphen and a version starting with a digit, as in my-bot-1.2.0.zip, and only while Version is empty. A version with its own hyphen defeats it: my-bot-1.2.0-rc.1.zip suggests nothing. Any valid name works, local or @publisher:name. Only a local name gives a package you can edit. An installed package with the same name and version is replaced. The dialog warns you, but, unlike New package, it doesn't disable the button. Importing never installs the package's dependencies. Install them first, or the package won't start. When the import fails, the toast Couldn't import the package gives no reason. Unzip the archive and install the folder with library install: the command prints the reason for most failures. The command line Three commands of the library group manage the library from a terminal. Command line explains how to run Asterobot's commands on each system. library install asterobot library install --path ./my-bot --name my-bot --version 1.0.1 Flag Required What it takes --path Yes The folder holding the package's .js and .mjs files, and its asterobot.json if it has one --name Yes The package's name: a local name, or @publisher:name --version Yes The package's version What it does: It copies every .js and .mjs file of the folder and of all its subfolders, whether index.js imports it or not, and asterobot.json from the root of the folder. Other files are ignored. Keep the folder to your package's own files. It replaces the package if that name and version are already installed. It refuses a folder that contains a symbolic link anywhere, with module source "<path>" is a symbolic link. It keeps the sourceId and integrity of the folder's asterobot.json, when the file has them. It doesn't download dependencies. On success, it logs Package my-bot@1.0.1 installed. On failure, the message contains failed to install package: followed by the reason, such as package my-bot@1.0.1 entry "index.js" is missing from its sources. library list asterobot library list Prints a table with one row per installed version, sorted by name, then by version: the name, the version, the provenance and the publisher, which is empty for a local package. NAME VERSION PROVENANCE PUBLISHER @alice:chat-tools 1.2.0 community alice my-bot 1.0.0 local my-bot 1.0.1 local library remove asterobot library remove --name my-bot --version 1.0.0 Both flags are required. The command removes that version and logs Package removed. Unlike Library Manager, it doesn't warn about the packages that depend on it or the bots set to it. A version that isn't installed fails with a message containing package my-bot@1.0.0: package is not installed. Asteroboard shows what these commands changed the next time it loads the library, for example after you reload its page. Work with your own code editor Only Asteroboard's editor completes the asterobot: modules and the game's messages. If you'd still rather write the code in the editor you're used to, keep the package in a folder of your own and install it with library install after each change: Create a folder with index.js, your other modules and, if the package needs one, its asterobot.json without sourceId or integrity. To start from a package you already have, export it, unzip it, and remove those two keys from its manifest if they're there. Edit the files. Install the folder, under the same name and version for as long as you work on that version: asterobot library install --path ./my-bot --name my-bot --version 1.0.1 The first time, set a bot to that package and version with Load a new package, reloading Asteroboard's page first if the package isn't listed. Then click Play, and Stop then Play after each new install: every start reads the files installed at that moment. Go back to step 2. While you work this way, don't save that package from Asteroboard's editor: saving replaces the installed files with what the editor holds, and Run saves first. When the version is done, move on to a new --version for the next changes. You can also edit the files of a local package directly in the library, inside Asterobot's data folder, and the next start reads them. The same caution about Asteroboard's editor applies. Never change the files of a package installed from the marketplace there: its integrity check then refuses it. Files and folders says where the data folder is. Next, the Publishing chapter starts with what to check before you publish.
  15. Smooth a posté un record dans Paquets
    The editor is where you write a package's code, in Asteroboard. Create a package gave a first tour of it. Some of its parts aren't obvious: saving replaces the package's files, Run does four things in one click, and a clean error badge doesn't mean the script will run. Open it In Library Manager, open a package's menu, or right-click its row, and choose Edit code. On a package's page, click Edit code at the top. New package and Copy to a new version open the editor on the package they create. Edit code is only offered for local packages. When the editor can't load a package's files, for example because the package was removed in the meantime, it shows Couldn't load this package's code instead of the files and the code. The top bar Item What it does Box icon Opens the package's page, where its manifest is edited. Name and version The package open in the editor. Unsaved Shown while the files differ from what was last saved. Incompatible Shown when the package's range of Asterobot versions doesn't include the one you run. See Compatibility. Error count A badge such as 2 error(s), shown when the editor finds errors. Resting the pointer on it says "Across every file in this package, not only the open one." IntelliSense Grey while the editor loads its typings, green once it completes and checks the asterobot: modules and the game's messages, orange when the typings couldn't be loaded. Resting the pointer on it says how many game messages it knows, or Couldn't load the typings - editing works, completion doesn't. Bot picker The bot that Run starts the package on. It lists the bots connected to a game server, and only appears when there's at least one. Game Opens or closes the Game panel next to the code. Save Saves every file. Disabled while nothing has changed. Run Saves, then starts the package on the bot in the picker. 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, then the others in alphabetical order, each with its folders in its path, such as lib/format.js. Click a file to show it. Each file keeps its own cursor, scroll position, selection, folded blocks and undo history while you move between files. A dot after a file's name marks unsaved changes in it, and a number next to it counts its errors. The plus button at the top of the column opens Add a file. Type a Path, such as lib/helper.js, then click Add a file or press Enter. The dialog refuses a path that doesn't end in .js or .mjs, one that starts with / or contains .. or \, and one already used. The new file is empty, opens right away, and only exists in the package once you save. Package layout gives every path rule, including one the dialog doesn't check. Resting the pointer on a file shows a trash icon. It removes the file from the list at once, without asking, and saving then deletes it from the package. index.js has no trash icon. The other files see a new file as soon as you add it, before you save: an import of it completes and isn't marked as an error. Jumping to a function's definition, or renaming it, works across the files of the package the same way. Saving Click Save, or press Ctrl+S (Cmd+S on a Mac). Saving replaces the package's files with exactly the files the editor holds. A file you removed from the list is deleted, and so is any file that isn't in the list, such as one added to the package from somewhere else while the editor was open. The manifest isn't touched. What else to know about saving: Unsaved also goes away when you undo your changes back to what was last saved. Removing a file counts as a change, even when every other file is unchanged. Nothing asks you to save when you leave the editor: unsaved changes are lost. The editor reads the files when it opens. If the package changes elsewhere in the meantime, in another tab or from the command line, the editor doesn't show it, and saving from the editor overwrites that change. With the same package open in two tabs, the last save wins. A script that's already running keeps the code it started with. The next start uses what you saved: see Play, stop and reload. Saving doesn't check the declarations of settings and actions. Package settings, on a bot set to the package, does: see Declare settings. When saving fails, a toast says Couldn't save, without a reason. The usual causes are a file path Asterobot refuses, such as ./lib/helper.js, a package that was removed in the meantime, or a package that isn't local. Run Run does in one click what you'd otherwise do in four steps: It saves the package. If saving fails, it stops there, with Couldn't save. It stops the script the picked bot is running, if there's one. It sets the bot's package to this package, at this version. It starts the script. A toast then says Running on followed by the bot's name, or Couldn't run the package when a step failed. The bot keeps this package afterwards, so Play on its page starts it again. Identify and run shows where the output goes and what to check when the script doesn't start. The Game panel Game opens a panel next to the code, with two tabs: Atlas, the map viewer, and Datacenter, the game data browser. Drag the line between the code and the panel to share the width differently. Closing the panel only hides it: when you open it again, each tab is where you left it. The panel shows one game version: The game version Asterobot is tested against, when it's downloaded and its game data extracted. A badge shows the version number, and resting the pointer on it explains that Atlas and Datacenter answer from that version, the one bots meet. Otherwise, the most recent game version with extracted game data. The badge then reads Incompatible version, and resting the pointer on it explains the difference: fine for writing a script, since most IDs carry over between neighbouring versions, but not the version Asterobot connects bots with, and map IDs, table columns and game texts can differ. When no game version has extracted game data, the panel says No extracted game version. Extract one, as Extract game data shows, then open the panel again. Names and texts in the panel use the language picked for game data on the game's page. Map viewer and Game data describe both tools. Completion and error checking The editor loads its typings from the Asterobot it's connected to: the asterobot: modules, and a type for each game message that version of Asterobot knows. With them, it completes functions, message names and payload fields, and checks your code the way TypeScript checks JavaScript. JavaScript support describes that check, the warnings for the three constructs scripts don't support, and the JSDoc types you can use. The error badge and the numbers in Files count errors only, in every file, open or not. Warnings aren't counted. Neither errors nor warnings stop you from saving or running. Two mistakes are worth looking for in the editor before anywhere else: a syntax error, and an import of a file that doesn't exist. When a script can't even load because of one of them, Run only says Couldn't run the package, while the editor shows the line, and Package settings, on the bot's Settings tab, gives the full reason. On the other hand, code with no error in the editor can still fail when it runs: the editor knows the shape of messages, not what the game will send. Marketplace packages are read-only A package installed from the marketplace, or any package with a @publisher:name name, can't be edited. Library Manager doesn't offer Edit code or Copy to a new version for it, Asterobot refuses to save its files, so Couldn't save appears if you reach the editor for it anyway, and its Manifest card is read-only. The package stays exactly as it was installed, which the integrity check described in The manifest relies on. When you need another author's code in your package, depend on their package, as Dependencies explains. When you really need to change it, export it and import the .zip under a local name: the imported package is yours to edit. It has no link to the marketplace any more, so no updates and no Source row, and bots save its settings under its new name. See Zip files and the command line. Next, Zip files and the command line.
  16. Smooth a posté un record dans Paquets
    A package can list permissions in its manifest: short names for what it uses. For now the list is information for the people who install your package, and Asterobot doesn't limit anything based on it. Note Coming soon. Asterobot will enforce the permissions a package declares. Until then, any package can use every function of every built-in module, whatever it lists. Declare them Open your package's page and find the Manifest card. Under Permissions, type a name in the box that reads e.g. gamedata, then click Add or press Enter. The name appears as a badge. Add the other permissions the same way. The button on a badge removes it. Click Save manifest. In the file, the list is the permissions key: { "permissions": [ "gamedata" ] } The rules: Each permission is a string. Asterobot doesn't define permission names yet, so it accepts any text and shows it as you wrote it. The card leaves out empty names and names already in the list. In a file, an empty entry or a name listed twice stops the package from starting, with empty package permission or duplicate package permission "<permission>". The list comes along when the package is copied, exported or imported. Where they show On the package's page, the Manifest card shows each permission as a badge, or "No permissions declared." when there's none. For a package installed from the marketplace, the card is read-only, so the people who install yours read your list there. A script reads the list with packages.info(), from asterobot:runtime, for its own package and for each of its dependencies. This one writes its own list to the bot's console when it starts, assuming the package is named my-bot: import { session, botInfo } from "asterobot:bot"; import { send } from "asterobot:protocol"; import { packages } from "asterobot:runtime"; export default async function behavior(launch) { const self = packages.info("my-bot"); botInfo(`${self.name} ${self.version} declares: ${self.permissions.join(", ") || "no permission"}`); // 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, }); } } permissions is always an array, empty when the package lists nothing. asterobot:runtime describes the other properties, and the errors packages.info() throws for a name the script doesn't load. What to list Since nothing checks the list today, it's worth exactly as much as it's honest. Name what your package really does, with words people understand, such as gamedata for a package that reads game data, the example the card itself gives. The names Asterobot will check aren't defined yet: expect to update your list, in a new version of your package, once they are. Whatever the list says, people deciding whether to run your package should know what any package can do with their bot. Trusting a package explains it from their side. Next, The editor.
  17. Smooth a posté un record dans Paquets
    compatibleVersion says which versions of Asterobot your package works with. When someone runs an Asterobot outside that range, the package is marked Incompatible and doesn't start. It's about Asterobot's version, not the game's: a Dofus update changes nothing here. After a game update covers what a game update changes for scripts. Declare it On your package's page, type the range in Compatible with, in the Manifest card, then click Save manifest. In the file, it's the compatibleVersion key: { "compatibleVersion": ">=1.5.0 <2.0.0" } Without a range, the package works with every version of Asterobot and nothing is checked. To find the version you run, see Updating, or run asterobot --version. Write the range A range is one or more conditions on the version of Asterobot: Range Matches >=1.5.0 1.5.0 and every later version >=1.5.0 <2.0.0 From 1.5.0 up to 2.0.0, without 2.0.0. A space or a comma between two conditions means both must hold. ^1.5.0 The same as >=1.5.0 <2.0.0: this version and the later ones with the same first number ^0.4.0 From 0.4.0 up to 0.5.0, without 0.5.0. When the first number is 0, the second one has to stay the same. ~1.5.0 From 1.5.0 up to 1.6.0, without 1.6.0 1.5.x Every 1.5 version 1.5.0 - 1.8.0 From 1.5.0 to 1.8.0, both included. The hyphen needs a space on each side. <1.6.0 || >=1.7.0 Either condition: here, every version except the 1.6 ones !=1.6.2 Every version except 1.6.2 1.5.0 Only 1.5.0 A version with a suffix after a hyphen, such as 1.6.0-rc.1, only matches a range that has such a suffix itself, as in >=1.6.0-0. Without one, the range leaves those versions out, even when their numbers are inside it. Where it shows Where What you see Library Manager, Compatibility column Compatible or Incompatible, only for a package that declares a range The package's page The same badge next to the provenance badge, and a Compatible with row with the range The top bar of the editor Incompatible, when the Asterobot you run isn't in the range Package pickers, in Add a bot, Load a behavior selection and the bot's Settings tab (incompatible) after the package's name and version. The package can still be picked. The bot's header The package's name in orange, and incompatible versions in orange in its version menu A script packages.info(name).compatibleVersion, described in asterobot:runtime What it blocks Asterobot checks the range each time it loads the package: at every start, whether from Play, Run or Load a package and play it, and each time it reads the package's settings. When the Asterobot you run isn't in the range: The script doesn't start, and the toast gives no reason. Package settings, on the bot's Settings tab, and the Add a bot dialog show Couldn't read this package's settings with the reason: resolve package my-bot@1.0.0: package "my-bot@1.0.0": package "my-bot" requires asterobot >=1.5.0 <2.0.0, running 1.4.2 A package that depends on an incompatible package doesn't start either, even when its own range is fine. The reason then names the dependency. Nothing else is blocked. Installing from the marketplace, importing, copying, editing and saving all work on an incompatible package, and it stays in the library. After Asterobot is updated and started again, the badges and the checks follow the new version. Not checked when you save Asterobot doesn't check that a range is written correctly when you click Save manifest, nor when it installs a package. A range it can't read is saved as it is, and then: The package shows Incompatible everywhere, whatever version you run. Every start fails, and Package settings shows a reason containing has an invalid compatibleVersion constraint, followed by the range and what's wrong with it. So after saving a range, look at the badge at the top of the package's page. If it says Incompatible while the Asterobot you run should match, read the range again: >=1.5.0 and <2.0.0 or 1.5.0+, for example, aren't ranges Asterobot can read. When to declare a range Declare a lower bound when your package needs something a given Asterobot version brought: a function, a message name, a fix. Use the first version that has it. Add an upper bound only when you know a later version breaks your package. A bound that's too tight marks your package Incompatible for people whose Asterobot would have run it fine. A new range reaches the people who installed your package only with a new version of it: see Publish an update. Next, Permissions.
  18. Smooth a posté un record dans Paquets
    A package can use the code of other packages in the library, its dependencies. That's how you share helpers between your own packages, or build on a package someone published. How dependencies work A package declares its dependencies in the dependencies field of its manifest. The key of each dependency is the exact name your code imports. An import gives you what the dependency's index.js exports, and nothing else. Each dependency is locked to one exact version, and that version has to be in the library when the script starts. Starting a script never downloads anything. A dependency's code runs in the same script as your package, on the same bot. Its settings and actions show on the bot's page next to yours: see Settings in dependencies. The modules of your dependencies count toward the module limits of your script. See Limits. Depend on a package from your library Make sure the package you need is in your library, at the version you want: install it from the marketplace, as Install from the marketplace shows, or create it yourself. Open your own package's page: click its name in Library Manager. In the Manifest card, open Add an installed package and pick the package, such as chat-tools@1.0.0. A row appears with its name and version. Click Save manifest. A toast says Manifest saved. Your manifest now holds: { "dependencies": { "chat-tools": { "version": "1.0.0" } } } And your code can import it by that name: import { formatChat } from "chat-tools"; Settings in dependencies has a complete example of both packages. When you save, Asterobot checks that a dependency without a URL is installed at that exact version, and refuses the manifest otherwise. Make it installable for others When someone installs your package from the marketplace, Asterobot also installs its dependencies, but only those with a URL. A dependency without a URL has to be in their library already, at exactly that version, or the installation fails. So every dependency of a package you publish needs a URL, which means it has to be published too. If the dependency is one of your own packages, publish it first: see Publish on asterobot.net. Install it from the marketplace on your own Asterobot. It gets a marketplace name, shown as Will install as before you install, such as @alice:chat-tools@1.2.0. In your package's Manifest card, add it with Add an installed package, then paste the address of its file page in the third box of that row, the one that reads asterobot.net file URL (optional). If a row with its local name remains, remove it. Change your imports to the marketplace name: import { formatChat } from "@alice:chat-tools";. Click Save manifest, then start your package on a bot to check that it still works. About the URL: It's the address of the file's page on asterobot.net, such as https://asterobot.net/files/file/12-chat-tools/. The file's ID alone, 12, works too. The name of the dependency has to match the file. The part before the : must be the publisher Asterobot derives from the file: its author's name, or asterobot for an official file. When they don't match, the installation of your package fails. So a dependency with a local name, such as chat-tools, can't be fetched from a URL. Asterobot only uses the URL when your package is installed or updated from the marketplace. Starting your package, importing it from a .zip or installing it with library install never downloads its dependencies. Choose the version Version On your Asterobot When someone installs your package from the marketplace An exact version, such as 1.2.0 Loads that version, which has to be installed Uses that version if they have it. Otherwise Asterobot downloads it, from the file's current version or from the file's version history on asterobot.net. A range, such as ^1.2.0, ~1.2.0 or >=1.2.0 <2.0.0 Doesn't start Uses the highest version of the file that matches, among its current version and its history, installing it if they don't have it latest, or nothing, for a dependency with a URL Doesn't start Uses the file's current version, installing it if they don't have it Once Asterobot has chosen a version during an installation, it writes that exact version in the installed copy's manifest. The package keeps using it from then on, even when the dependency publishes a newer version. A dependency without a URL always needs an exact version. A range or latest in your own package's manifest stops it from starting on your Asterobot, because Asterobot only loads exact versions. The start fails with a reason starting with resolve package <name>@<version>: dependency "<name>" locked to version "^1.2.0": read package, which Package settings on the bot's Settings tab shows. So use exact versions. Your package then runs for you exactly as it will for the people who install it, and you choose when to move to a newer dependency, by publishing an update of your package. A version in the name A dependency's name can end with @ and an exact version, such as chat-tools@1.0.0. That's how one package uses two versions of the same package at once: { "dependencies": { "chat-tools@1.0.0": { "version": "1.0.0" }, "chat-tools@2.0.0": { "version": "2.0.0" } } } import { formatChat as formatChatV1 } from "chat-tools@1.0.0"; import { formatChat } from "chat-tools@2.0.0"; Write the same version in Version as in the name. Asterobot loads the version written in Version: a dependency whose version is only in its name doesn't start. A version in the name that differs from Version is refused when you save, and so is one that isn't an exact version. Both versions read the same settings, since settings are saved under the package's name. An import can also pick a version with a range, such as chat-tools@^1.0.0, among the versions the script already loads: see Modules and imports. Dependencies of dependencies A dependency declares its own dependencies in its own manifest. They're loaded the same way, and installed with it from the marketplace. A package needed by several packages at the same version is loaded once, and its top-level code runs once. Different versions of the same package can be loaded side by side, each locked by the package that declared it. Every package loaded is checked: an incompatible compatibility range or a checksum that no longer matches, in any dependency, stops the whole script from starting. When something's wrong What you see Cause Fix dependency "<name>" needs <name>@<version> installed, or a URL to fetch it from, on the Manifest card That version of the dependency isn't in your library Install it, or correct the version dependency "<name>" needs an exact version, on the Manifest card A dependency without a URL has no version Fill in Version A reason containing read package <name>@<version> sources, on Package settings The locked version isn't in the library: it was removed, or the version is a range or latest Install that version, or lock an installed version A reason containing has no locked dependency for "<name>" An import uses a name that isn't a key of dependencies, often a typo or an old name Make the import and the key identical A reason containing dependency "<name>" targets package named The version in the dependency's name differs from its Version Write the same version in both Library Manager lets you remove a package other packages depend on, after a warning listing them. Those packages then fail to start until the version they lock is installed again. Error messages lists the other texts. Reuse a connection package Every package that runs on a Full socket bot has to identify the bot on the game server, as Identify and run shows. You can already put that part in a package of your own and depend on it from your other packages. Note Coming soon. Ready-made basic and full connection scripts will be published for you to run and reuse, so your package can depend on one instead of doing the identification itself. Until then, the identification stays in code you write: in your package, or in a package of yours that it depends on. Next, Compatibility.
  19. Smooth a posté un record dans Paquets
    A package is identified by its name and its version together: my-bot 1.0.0 and my-bot 1.0.1 are two packages in your library. The way you number versions matters once other people use your package. Where a name and a version come from Neither is written in the package's files. A package gets them from how it was installed: How Name and version New package, Import a .zip, Copy to a new version The Name and Version typed in the dialog The library install command The --name and --version flags Installing from the marketplace Built from the file on asterobot.net. See How your package is named. Asterobot stores each version in its own folder of the library, named after the package and the version, which is why the two follow the rules below. You can't rename a package or change its version in place. Copy it to a new name or version, or export it and import it again. Local names A local name is a plain name, such as my-bot. Only local packages can be edited in Asteroboard. It can't contain @ or :, since both mean something in marketplace names and in imports. It can't be empty, . or .., and can't contain / or \. Everything else is allowed, dots included, even at the start as in .dotted. Lowercase words joined by hyphens, such as chat-logger, read best in lists and in import statements. Marketplace names A marketplace name looks like @publisher:name, such as @alice:chat-tools. Asterobot gives one to every package installed from the marketplace. The name starts with @, then a publisher, a : and a name, and contains no other @ or :. A publisher of asterobot makes an Official package. Any other publisher makes a Community package. The Import a .zip dialog and library install also accept a name of this shape. The package then shows Community or Official without coming from the marketplace, and it can't be edited or copied in Asteroboard. Name Accepted Why my-bot Yes A local name .dotted Yes A local name @alice:chat-tools Yes A community name @asterobot:core Yes An official name my@bot No Reads as the package my at version bot local:bot No A : introduces a name after a publisher, and there's no @ @alice, @alice:, @:chat-tools No The publisher or the name is missing @alice:chat:tools, @alice:chat@tools No A second : or @ The dialogs refuse a wrong name as you type: Message Where A local package name can't contain ':' or '@'. New package, Import a .zip, Copy to a new version A new package is local, so its name can't start with '@' - those are assigned by the marketplace. New package and Copy to a new version, which only create local packages A marketplace name looks like @publisher:name. Import a .zip A marketplace name has exactly one ':' and one leading '@'. Import a .zip The command line gives Asterobot's own wording, such as local package name "my@bot" must not contain ":" or "@", marketplace package name "@alice" must be "@publisher:name" or marketplace package name "@alice:chat:tools" must contain exactly one ":" and one leading "@". Version numbers A version is required, and Asterobot accepts almost any text: it can't be . or .., and can't contain / or \. New packages start at 1.0.0. A version breaking these rules is refused with invalid version "<version>": contains a reserved character, or empty or reserved path component. Copy to a new version shows that reason, while New package and Import a .zip only show a toast. Use semantic versions Write versions as three numbers separated by dots, such as 1.4.2: a semantic version. Asterobot accepts other text, but several features only understand semantic versions: Feature With semantic versions With other text Check for updates Offers a version only when it's higher than the installed one Offers any different version, even an older one Version ranges such as ^1.2.0 or latest, in imports and in dependencies Consider this version Skip this version, as if it weren't installed Declaring the package as a dependency Works Save manifest refuses the version Copy to a new version Suggests the next version: 1.4.2 becomes 1.4.3 Raises the last number in the text, or suggests the same version when there's no number The usual meaning of the three numbers helps the people who use your package, especially other authors who depend on it with a range such as ^1.2.0, which accepts any 1.x.y from 1.2.0 on: Raise the last number for a fix that changes nothing else: 1.4.2 to 1.4.3. Raise the middle number when you add something without breaking what exists, and reset the last one: 1.4.3 to 1.5.0. Raise the first number when something that worked before stops working: a setting renamed, an exported function removed or changed. Reset the other two: 1.5.0 to 2.0.0. Some details of how Asterobot reads semantic versions: 1, 1.4 and v1.4.2 are understood too, but they're different text from 1.4.2. An exact version is always compared as text, so a dependency locked to 1.4 doesn't match an installed 1.4.0. Write three numbers, without v, everywhere. Numbers can't have leading zeros: 1.04.0 isn't a semantic version. A suffix after a hyphen, as in 2.0.0-rc.1, makes a version that comes before 2.0.0. Check for updates offers 2.0.0 to someone on 2.0.0-rc.1, never the reverse, and ranges only match such versions when the range itself has a suffix. A suffix after +, as in 1.4.2+build.7, doesn't change the order. Versions side by side Each version of a package is installed on its own, and stays until someone removes it. A bot is set to one exact version, so installing a new version never changes what a bot plays. Update packages describes this from the player's side. For you as the author: To start a new version, use Copy to a new version on the current one, as Share your package shows. The old version stays as it was. Give every change you share a new version. Two packages with the same name and version can't be told apart, and the second one installed replaces the first. Settings are saved under the package's name, so a bot moved to a new version keeps its values, and a package copied under a new name starts from its defaults on every bot. See Read settings. A script can load several versions of the same package at once, when its dependencies lock different versions. See Dependencies. Next, Dependencies.
  20. Smooth a posté un record dans Paquets
    asterobot.json is the one file of a package that isn't code. It says which other packages the package depends on, which Asterobot versions it works with and what it declares it uses. Where it lives At the root of the package, next to index.js. It's optional. A new package has none, and Asterobot only writes one when there's something to put in it. The editor doesn't show it. The Files list only holds modules, and saving code never changes the manifest. It doesn't hold the package's name or version. Those come from where the package is installed: the Name and Version typed when the package was created, copied or imported, or the file it came from on asterobot.net. See Names and versions. In Asteroboard, you edit the manifest on the package's own page, in its Manifest card. A manifest can also come with the package: inside a .zip you import, or in a folder you install with library install, as Zip files and the command line explains. An example This is the manifest of a package installed from the marketplace, as Asterobot writes it: { "dependencies": { "@alice:chat-tools": { "url": "https://asterobot.net/files/file/12-chat-tools/", "version": "1.2.0" } }, "compatibleVersion": ">=1.5.0 <2.0.0", "integrity": "sha256-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "permissions": [ "gamedata" ], "sourceId": 34 } The author wrote dependencies, compatibleVersion and permissions. Asterobot added integrity and sourceId when it installed the package from asterobot.net. Fields Key Type Written by What it holds dependencies Object You The library packages this package imports. Each key is the name the code imports, and each value is an object with a version and a url, both optional. See Dependencies. compatibleVersion String You The range of Asterobot versions the package works with, such as >=1.5.0 <2.0.0. See Compatibility. permissions Array of strings You What the package says it uses. Asterobot doesn't enforce it yet. See Permissions. sourceId Number Asterobot The ID of the asterobot.net file the package was installed from. Check for updates compares the package with that file, and it's how a marketplace package keeps its name when its file is renamed. integrity String Asterobot A checksum of the package's .js and .mjs files, starting with sha256-, recorded when the package is installed from the marketplace. Asterobot compares the files with it each time it loads the package, and refuses them with package source integrity mismatch when they changed. And inside each entry of dependencies: Key What it holds version An exact version such as 1.2.0, a range such as ^1.2.0, or latest. url The address of the dependency's file on asterobot.net, or its ID. Without it, the dependency has to be in the library already. Asterobot keeps only these keys. Anything else in the file, such as a name or a description, is dropped as soon as the package is installed, because Asterobot writes the file again from what it understood. The file has to be valid JSON: a package whose asterobot.json doesn't parse can't be imported or installed, and if the file breaks after installation, the package disappears from Library Manager and doesn't start. What Asterobot does with sourceId and integrity When sourceId integrity Installing or updating from the marketplace Set to the file's ID Computed from the files just downloaded Import a .zip Removed Removed Copy to a new version Removed Removed Save in the editor Kept Removed, since the code changed Save manifest Kept Kept The library install command Kept as they are in the folder's file Kept as they are in the folder's file So whatever you write in these two keys is replaced or removed, except by library install. Leave them out of a package you're working on, which matters most when you start from a package you exported: a leftover integrity stops your package from starting as soon as you change a file, and a leftover sourceId ties it to a file on asterobot.net it didn't come from. The Manifest card To open a package's page, click its name in Library Manager, or click the box icon before the name at the top of the editor. The Manifest card is under the details card. For a local package, the card is a form: Part What it does Dependencies One row per dependency, with three boxes and a remove button. Empty, the boxes read Package name, as imported, Version and asterobot.net file URL (optional). Without any row, the card says "No dependencies." Add an installed package Lists every other package in your library as name@version. Picking one adds a row with its name and version, and no URL. A row that already had that name is replaced. Add by URL Adds an empty row for you to fill in. Permissions One badge per permission, each with a remove button, or "No permissions declared.". Type a new one in the box that reads e.g. gamedata, then click Add or press Enter. Compatible with A single box for the range of Asterobot versions, reading e.g. >=1.5.0 <2.0.0 while empty. Save manifest and Reset Both stay disabled until something changes. Reset puts the card back as the manifest was last saved. When you click Save manifest, spaces around each value are removed, a dependency row with an empty name is left out, and an empty or repeated permission isn't added. What you save replaces the dependencies, permissions and range the manifest had. A toast says Manifest saved. A script that's already running keeps what it loaded, and the next start uses the new manifest. For a package with any other name, the card is read-only and says "Published by someone else, so this is read-only". It lists each dependency with its version, or latest when it has none, then the permissions and the range, or "No constraint declared." when there's no range. When Asterobot refuses a manifest When a save fails, the card shows Couldn't save the manifest with the reason under it. Before saving, Asterobot checks each dependency that has no URL: it needs an exact version, and that version has to be in your library. Then it checks the versions of every dependency. Reason What to do dependency "<name>" needs <name>@<version> installed, or a URL to fetch it from Install that version of the package, or correct the name or the version dependency "<name>" needs an exact version Fill in Version invalid package manifest: dependency "<name>": invalid version "<version>": <details> Write an exact version, a range or latest Error messages lists every reason, including those about a version written in the dependency's name. Two fields aren't checked when you save. A compatibleVersion that isn't a valid range is accepted, and only fails when the package loads: see Compatibility. An empty or repeated permission, which the card doesn't let you add but a file can contain, stops the package from starting with empty package permission or duplicate package permission "<permission>". A manifest inside a .zip or a folder goes through the same version checks when the package is installed. The Import a .zip dialog then only says Couldn't import the package, while library install prints the reason. Next, Names and versions.
  21. A package can use other packages of the library, its dependencies, and each of them can declare settings and actions of its own. A script only reads the settings of its own package, and a bot's Settings tab shows each package's settings apart. Dependencies covers declaring and importing them. Each package reads its own settings Every package gets its own copy of asterobot:parameters. Whichever file imports it, values holds the settings declared in the index.js of the package that file belongs to: In your package, values holds your settings, never a dependency's. In a dependency, values holds the dependency's settings, never yours. An onChange() handler registered by a dependency only hears about the dependency's settings. No package can read or change the settings of another one. A dependency declares its settings the way Declare settings describes, in its own index.js. On the bot's page Under Package settings, on the bot's Settings tab, there's one card per package of the bot's script that declares at least one setting or action: the bot's own package and each of its dependencies, down to the dependencies of dependencies. A card's title gives the package's name, its version and a count such as "2 setting(s) · 1 action(s)". A card only appears for a package whose code the script loads. A dependency listed in the manifest that no file imports declares nothing, so it has no card. A package that declares no setting and no action has no card either. The first card is open and the others are closed. The cards don't come in a fixed order, so the bot's own package isn't necessarily the first one. Apply and Reset work card by card: applying one card leaves the others untouched. A dependency's actions are in its own card, and run the dependency's own run functions. The Add a bot dialog shows the same cards, without Apply and without actions, as soon as a library package is picked. Saved under the dependency's name A bot saves a dependency's settings under the dependency's name, the same way it saves yours. So, on one bot: Every package that uses a given dependency reads the same values for it. When the bot moves from one of these packages to another, the dependency's settings come along. The values carry over between versions of the dependency, under the rules in Read settings. Another bot has values of its own. Write a package for others to use A package meant to be a dependency is an ordinary package. What sets it apart is what its index.js exports: functions and values for the packages that import it, and usually no default export. Here is a small one, chat-tools, with one setting and one function: import { values } from "asterobot:parameters"; /** @type {BehaviorParameters} */ export const parameters = { showChannel: { type: "bool", label: "Show the channel", default: true, }, }; export function formatChat(payload) { const line = `${payload.senderName}: ${payload.content}`; // Read at each call, so an applied change counts from the next line. return values.showChannel ? `[${payload.channel}] ${line}` : line; } A package named chat-logger uses it. Its manifest declares the dependency, as Dependencies shows: { "dependencies": { "chat-tools": { "version": "1.0.0" } } } And its index.js imports formatChat() by the name it declared: import { session, botInfo } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; import { formatChat } from "chat-tools"; 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; botInfo(formatChat(traffic.payload)); }); // 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, }); } } A bot set to chat-logger shows a single card, chat-tools, with Show the channel: chat-logger declares nothing itself. Turning the switch off and clicking Apply changes the next chat line the bot's console shows. Some advice for packages like chat-tools: Decide who chooses each value. What the person running the bot decides belongs in your settings. What the package using yours decides belongs in the arguments of the functions you export, since that package can't change your settings. Label your settings clearly. They appear under your package's name on every bot whose package uses yours, and the person reading them may never have heard of your package. Several packages on one bot share your settings. Don't design a setting whose right value depends on which of them uses it. Leave out the default export if the package isn't meant to run on its own. It's still listed in the library and can still be picked for a bot, but its start then fails with an error containing has no unambiguous default export. Say what the package is for in its description when you publish it. Keep the top level of index.js to imports, declarations and function definitions. It runs at every start of every package that uses yours, and each time Asterobot reads their settings. Next, the Packages chapter describes the manifest, starting with every field of asterobot.json.
  22. Smooth a posté un record dans Réglages et actions
    An action is a button your package adds to the bot's page. Clicking it calls a function of the running script, with values the person can fill in first. Actions suit one-off things someone decides while the bot plays: report something, say something, move on to the next step. For a choice that should last, declare a setting instead. Declare an action Actions are declared next to settings, as an object exported as actions from index.js. Each key is the action's name, and each value describes the button and holds the function it runs. Warning On a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), the Say in chat action below sends a real chat message as your character. import { session, botInfo } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; // The last pods the game reported, for the report action. let lastWeight; /** @type {BehaviorActions} */ export const actions = { reportPods: { label: "Report pods", description: "Writes the character's pods to the bot's console.", run() { if (!lastWeight) { botInfo("The game hasn't reported the pods since the script started."); return; } botInfo(`Pods: ${lastWeight.inventoryWeight} / ${lastWeight.weightMax}`); }, }, say: { label: "Say in chat", description: "Sends a chat message as the character.", args: { text: { type: "string", label: "Message", placeholder: "Hello!" }, channel: { type: "string", label: "Channel", choices: [ { value: "GLOBAL", label: "General" }, { value: "GUILD", label: "Guild" }, { value: "PARTY", label: "Party" }, ], }, }, async run({ text, channel }) { // A failed action is reported to the person who clicked, and the script // keeps running, so there's no need to catch errors here. if (text === "") throw new Error("Type a message first"); await send("ChatChannelMessageRequest", { channel, content: text }); }, }, }; export default async function behavior(launch) { on("InventoryWeightEvent", (traffic) => { // A message Asterobot couldn't decode has no payload, and an error // thrown in a handler stops the whole script. if (traffic.payload) lastWeight = traffic.payload; }); // 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, }); } } Actions follow the same rules as settings about where they're declared: only the actions export of index.js counts, the buttons keep the order you write them in, and /** @type {BehaviorActions} */ gives completion and checks on the fields. A package can export parameters, actions or both. Fields Field Required What it does label No The button's text. Without it, the button shows the key, such as reportPods. description No Shown when the pointer rests on the button, and at the top of the dialog that asks for the arguments. Without it, that dialog says "Fill in what this action needs, then run it." args No The values the bot's page asks for before running the action. Each one is declared exactly like a setting. run Yes The function the button calls. Other fields are ignored. An action without run, a run that isn't a function, or args that isn't an object of objects is a mistake in the shape of the export: the script doesn't start at all, and the Problems alert shows a text starting with read declarations of. Declarations lists those texts. run and its arguments run receives a single object holding every argument you declared: The value from the dialog, checked against the argument's declaration with the same rules as a setting: the type, the choices, min and max. The argument's default, for an argument that wasn't sent. An argument declared without a default gets its type's zero value or its first choice, which is why channel above starts at GLOBAL. An action without args receives {}. Arguments are never saved. The dialog opens with the declared defaults every time, whatever was typed the last time. run can be async. Asterobot reports the result once its promise settles, not when the call returns. Whatever run returns is ignored. run runs inside the script like a message handler: it shares the script's variables, can use every function of the built-in modules, and waits for its turn while the script handles other events. The code of run that runs without awaiting has the same 250 ms limit as a handler. Past it, the action fails with JavaScript execution deadline exceeded, and the script keeps running. Asterobot checks the arguments against the declaration of the package the bot is set to play, as it's saved in your library. If that declaration can't be read, the values go to run unchecked. What the person clicking sees The buttons are under Actions, in your package's card on the bot's Settings tab, in the Package settings section. Each one has a lightning bolt. For an action with arguments, a dialog opens, titled with the action's label, with one field per argument, and Cancel and Run buttons. Once the action is handed to the script, a toast says Action started. When run returns, or its promise settles, a second toast gives the action's key followed by finished, or followed by failed with the error. A failure toast stays until someone closes it. The toasts name the action by its key, such as say failed, not by its label. Choose keys a person can recognize. An error in run never stops the script. When the action can't even be handed to the script, Couldn't start the action appears instead, with the reason. The two you'll meet most while writing a package are an argument that doesn't fit, such as count: 0 is below the minimum 1, and an action the running script doesn't have. Error messages lists them all. When actions can be used Only while a script runs on the bot. Otherwise the buttons are disabled, and resting the pointer on one shows The bot has to be playing a behavior before an action can run. A button calls the action of the script that's running, while the card describes the package the bot is set to play, as saved in your library. After you add, rename or change an action in the editor, save and start the script again, with Run for example. Until then, a new action fails with the running behavior declares no action "<action>" on package "<package>", and a changed run still runs the old code. Asterobot finds the actions when the script loads, before it calls your default export. The buttons work while the entry function is still awaiting something, and after it has returned. The actions of a dependency are in the dependency's card, and run the dependency's own functions. See Settings in dependencies. The actions of an inline script can't be used: the Settings tab never shows its declarations. When a setting or an argument breaks a rule, Package settings shows Couldn't read this package's settings instead of the cards, so no button can be clicked until the declaration is fixed. Declare settings explains those errors. Next, Settings in dependencies.
  23. Smooth a posté un record dans Réglages et actions
    Once your package declares settings, its script reads their current values from asterobot:parameters. It can also react the moment someone applies a change. asterobot:parameters is the reference for the module. values import { values } from "asterobot:parameters"; values has one property per setting your package declares, named by its key: values.maxAnswers for a setting declared as maxAnswers. Each value has the type of its setting: a boolean for bool, a string for string, a regular number for int and double, an array for array and an object for map. The values are the ones saved for the bot, laid over the defaults of your declaration. A setting nobody changed reads as its default. values is live. After someone applies new settings, the next read returns the new value, without restarting the script. So read a setting at the moment you need it, in a handler or at each turn of a loop, instead of copying it into a variable when the script starts. When a value has to stay the same for a whole exchange, copy it at the start of that exchange, as the chat script in Add a setting does with its reply. A few more things about values: Object.keys(values) lists the settings, and "delay" in values tells whether one exists. It's read-only. Assigning to one of its properties throws a TypeError, and so does deleting one. asterobot:parameters quotes both texts. It only holds your own package's settings. In a dependency, values holds the dependency's settings: see Settings in dependencies. When Asterobot can't read the declaration, values is empty and every read gives undefined. Declare settings says when that happens. An inline script can declare settings, but they never appear on the bot's Settings tab, so it always reads its defaults. When new values arrive Here is what happens when someone changes settings under Package settings on the bot's Settings tab, then clicks Apply: Asteroboard sends every setting of that package's card: the ones that changed and the ones that didn't. Asterobot checks each value against your declaration. If one doesn't fit, nothing is saved, and Couldn't apply the settings appears with the reason. Asterobot saves the values for the bot. If a script is running, Asterobot hands it the new values. values returns them from then on, then the onChange() handlers run for each setting whose value changed. All the values of one Apply reach the script together, before any handler runs, so a script never sees half an edit. When no script is running, the values are only saved, and the next start reads them. The same thing happens in the rare case where the running script already has 32 operations waiting when someone clicks Apply: see Limits. React to a change onChange() registers a function that runs when an applied value differs from the previous one. Use it when a change should do something right away. When the script only needs the current value the next time it looks, reading values is enough. This script writes a warning on the bot's console when the character's pods go over a limit taken from the settings. When someone changes the limit, onChange() checks the last pods the game reported against the new limit at once, instead of waiting for the game to report them again: import { session, botInfo, botWarn } from "asterobot:bot"; import { values, onChange } from "asterobot:parameters"; import { on, send } from "asterobot:protocol"; /** @type {BehaviorParameters} */ export const parameters = { warnAt: { type: "int", label: "Warn when pods reach", unit: "%", default: 90, min: 1, max: 100, }, }; // Kept so the pods can be checked again when the limit changes. let lastWeight; function checkWeight() { if (!lastWeight || lastWeight.weightMax <= 0) return; const percent = Math.floor((lastWeight.inventoryWeight * 100) / lastWeight.weightMax); if (percent >= values.warnAt) { botWarn(`Pods at ${percent}%, the limit is ${values.warnAt}%`); } } export default async function behavior(launch) { on("InventoryWeightEvent", (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; lastWeight = traffic.payload; checkWeight(); }); onChange(({ name, value }) => { botInfo(`Setting ${name} is now ${value}`); if (name === "warnAt") checkWeight(); }); // 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, }); } } To try it, Run the package on a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play). On the bot's Settings tab, under Package settings, set Warn when pods reach to 1 and click Apply. The bot's Console shows Setting warnAt is now 1, and a warning as soon as the game has reported the character's pods since the script started. The handler receives an object with the setting's name and its new value, of the setting's type. Some details: It runs once for each setting whose value changed, and only for your own package's settings. It doesn't run when the script starts, and an Apply that changes nothing doesn't call it. A list or a map counts as changed only when its content differs. With several handlers, the order they run in isn't fixed. Don't write one that depends on another having run first. A handler that throws stops the whole script, like a message handler. The Problems alert then starts with parameters.onChange handler, or with unhandled Promise rejection: when an async handler's promise rejects. Put anything that can fail in try/catch. onChange() returns a handle. Pass it to offChange() when the script no longer needs to hear about changes: const handle = onChange(({ name }) => { botInfo(`${name} changed`); }); // Once the script doesn't need it any more. offChange(handle); A script can have up to 256 onChange() handlers registered at once, counted apart from its message handlers. See Limits. Saved values Settings are saved per bot, under the package's name, whatever its version. For you as the author, that means: Two bots playing your package each have their own values. A bot moved to another version of your package keeps its values. Asteroboard saves every setting of a package's card at once, the untouched ones included, when someone clicks Apply, and when a bot is added with your package picked in the Add a bot dialog. From then on, the bot has a saved value for each of those settings, and the defaults of your later versions don't change them. A saved value is used only while it fits the declaration of the version the bot plays. When it doesn't fit, the default applies, but the saved value isn't deleted: it comes back if the bot returns to a version where it fits, unless someone applied a new value to that setting in the meantime. Here is what a bot sees after it moves to a new version of your package: In the new version, you A bot with a saved value reads A bot without one reads Add a setting Its default Its default Change a default Its saved value The new default Rename a setting The default, under the new name. The value saved under the old name isn't used. The default Change a setting's type Its saved value if it still fits, such as a whole number saved for a setting now declared double, otherwise the default The default Remove a choice, or narrow min and max Its saved value if it's still allowed, otherwise the default The default Remove a setting Nothing: the setting is gone Nothing So once people use your package, keep the names of its settings. And when a setting should mean something else, give it a new name: an old value that still fits the type would otherwise be read with the new meaning. The settings of a dependency are saved under the dependency's own name. Settings in dependencies explains what follows from that. What a script can't do with settings Change a setting. values is read-only, and the next Apply would send the values shown on the page anyway. Read the settings of another package, whether it's the package that uses it or one of its dependencies. Keep data for its next run. Each start begins again from scratch: see Play, stop and reload. Next, Declare actions.
  24. Smooth a posté un record dans Réglages et actions
    Settings let the people who run your package change what it does without touching its code. Add a setting showed the basics, and Declarations keeps the compact schema with the full list of error texts. Where the declaration goes A package declares its settings in one place: an object exported as parameters from its index.js. Each key is the name of a setting, the name your script reads later in values, and each value describes the setting. /** @type {BehaviorParameters} */ export const parameters = { enabled: { type: "bool", label: "Answer in chat", default: true, }, delay: { type: "double", label: "Wait before answering", unit: "seconds", default: 1.5, min: 0, max: 10, }, maxAnswers: { type: "int", label: "Answers per player", description: "The bot stops answering a player after this many replies.", default: 3, min: 1, }, tone: { type: "string", label: "Tone", choices: [ { value: "polite", label: "Polite" }, { value: "short", label: "Short" }, ], default: "polite", }, ignored: { type: "array", of: "string", label: "Ignored players", description: "Character names the bot never answers.", }, replies: { type: "map", of: "string", label: "Replies", description: "What a player says on the left, what the bot answers on the right.", default: { "!hello": "Hello!", "!help": "Ask me anything." }, }, }; A few rules decide what Asterobot picks up: Only an export of index.js counts. An export const parameters in another file of the package is ignored. Settings appear on the bot's page in the order you write them. The one exception comes from JavaScript itself: keys that look like whole numbers, such as "2", are always listed first. The object is ordinary JavaScript. A default can be a constant from another file of the package, or a value computed when the module runs. Asterobot reads the object by running the top level of your modules, without calling your default export and without a bot or a game session. At that moment values is empty and session holds empty values, so a declaration can't depend on either. The entry function lists what else behaves differently while Asterobot reads it. Each package declares its own settings, dependencies included. See Settings in dependencies. The key is also the name Asterobot saves the value under for each bot. Renaming a setting in a later version loses the values people saved, as Read settings explains, so pick keys you won't want to change. An empty key is refused. Fields Field Used by What it does type Every setting Required. "bool", "string", "int", "double", "array" or "map". of array and map, where it's required The type of the list's items, or of the map's values: "bool", "string", "int" or "double". Refused on every other type. label Every setting The name shown with the input. Without it, the key is shown, such as maxAnswers. description Every setting A line of help shown with the input. placeholder string, int and double Grey text shown while the box is empty. unit int and double A short word shown at the end of the number box, such as seconds or kamas. default Every setting The value until someone applies another one. See Defaults. choices string and int Turns the input into a drop-down list of allowed values. See Choices. min and max int and double The smallest and the largest value allowed. A value equal to a bound is allowed. Asterobot is lenient with what it doesn't understand, which cuts both ways. A field it doesn't know is ignored without an error, so a typo such as lable silently does nothing. A label, description, placeholder or unit that isn't a string is ignored too, and so is a min or max that isn't a number. The JSDoc type described below catches most of these mistakes in the editor. Some combinations have no effect either: min and max don't apply to a setting with choices, nor to the items of an array or a map. placeholder and unit aren't shown on a drop-down list, nor on the items of a list or a map. min greater than max is refused. On the bot's page, min and max also limit the arrows of the number box. Asterobot checks the bounds again when someone clicks Apply, whatever was typed. The six types type The script reads On the bot's page Without a default bool true or false A switch false string A string A text box "" int A whole number, as a regular number A number box whose arrows step by 1 0 double A number A number box 0 array An array of values of the of type One row per item, numbered from 0, each with a remove button, and Add under the rows [] map An object whose keys are strings and whose values have the of type One row per entry with a Key box, the value and a remove button, and Add an entry under the rows {} What each type accepts, for a default or for a value applied on the bot's page: bool takes true or false, nothing else. string takes a string. The number 5 isn't turned into "5". int takes a whole, finite number. 3.0 fits, and 3.5 is refused rather than rounded. The script reads it as a regular number, not as a BigInt like the 64-bit fields of game messages. double takes a finite number, so not NaN and not Infinity. array takes an array whose every item fits of. map takes an object whose every value fits of. Lists and maps only hold the four scalar types: a list of lists can't be declared. On the bot's page, a new item starts at false, "" or 0, depending on of, and a map row whose key is left empty isn't saved. Clearing a number box doesn't store an empty value: the box keeps the last number typed in it. Choices choices turns a string or int setting into a drop-down list. Each choice is either a bare value, which is then its own label, or an object with a value and a label: /** @type {BehaviorParameters} */ export const parameters = { slot: { type: "int", label: "Spell slot", choices: [1, 2, 3], default: 2, }, tone: { type: "string", label: "Tone", choices: [ { value: "polite", label: "Polite" }, { value: "short", label: "Short" }, ], default: "polite", }, }; The script reads the value, never the label: values.tone is "polite", not "Polite". The rules: Only string and int settings can have choices. Every value must fit the setting's type. "2" isn't a valid choice for an int. A choice object without a value is refused. A choice without a label shows its value. The default must be one of the values. Without a default, the setting starts at the first choice. Defaults The default is what a bot reads until someone applies another value. Without a default, or with default: null, a setting starts at its type's value from the last column of the table above, or at its first choice. The default has to fit the declaration exactly like a value typed on the bot's page. Asterobot never converts it: default: "20" on an int is refused, and so is a default below min or above max. A default can be any expression, as long as it doesn't need a setting's value or the session. A default only matters while a bot has no saved value for the setting. Asteroboard saves every setting of a package at once, the untouched ones included, when someone clicks Apply and when a bot is added with the package, so a default you change in a later version doesn't reach those bots. Read settings goes through what happens to saved values. When Asterobot checks the declaration Asterobot reads and checks the declaration each time it needs it: when a bot's Settings tab shows Package settings, when someone picks the package in the Add a bot dialog, and at every start of the script. Saving in the editor checks nothing. After changing a declaration, save, then open Package settings on a bot set to your package and click Refresh. Problems fall into two groups: Problem Examples What happens A setting breaks a rule An unknown type, a default that doesn't fit, choices on a bool, min above max Package settings and the Add a bot dialog show Couldn't read this package's settings with the reason. The script still starts, but with no settings at all: every read from values gives undefined. The export itself has the wrong shape parameters isn't an object, or one of its settings isn't an object The same message on Package settings, and the script doesn't start: the Problems alert shows a text starting with read declarations of. The reason names the package, then the setting, then what's wrong. With default: "friendly" on the tone setting above, Package settings shows: chat-helper@1.0.0: read declarations: package "chat-helper" parameter "tone": parameter "tone" default: friendly is not one of polite, short Declarations lists every text you can get. Typing with JSDoc Put /** @type {BehaviorParameters} */ on the line above export const parameters, as in the examples on this page. The editor then completes the field names, and marks as errors a field that doesn't exist and a type or of that isn't one of the allowed names: mistakes Asterobot would ignore silently, or only report once it reads the declaration. It doesn't check that a default fits its type, so Asterobot's own check still matters. These types need no import: Type What it describes BehaviorParameters The whole parameters object BehaviorParameter One setting, or one argument of an action BehaviorParameterType "bool", "string", "int", "double", "array" or "map" BehaviorScalarType "bool", "string", "int" or "double", the types of accepts BehaviorChoice A choice written as an object, with a value and an optional label Next, Read settings shows how the script uses the values. The arguments of actions are declared with the same fields.
  25. Smooth a posté un record dans Données de jeu
    Some questions need a join, a count or a sort that the lookups can't do. query() runs SQL on the bot's game data and hands back the result. Queries are written in SQLite's dialect of SQL, the same one the game version's SQL tab runs. query() query(sql, params, options) resolves to an object with three properties: Property What it holds columns The name of each column, in the order the query selects them. A name can appear twice, as in SELECT a._id, b._id. rows One array per row, holding the values in the same order as columns truncated true when the query had more rows than the limit let through Option Default What it does limit 200 How many rows to return, 5,000 at most Rows are arrays rather than objects because column names can repeat, so read the values by position: const result = await query( "SELECT language, name FROM _names WHERE record_table = ? AND record_id = ? ORDER BY language", ["monsters", 147], ); for (const [language, name] of result?.rows ?? []) { botInfo(language, name); } This query lists the names of one monster in every language the game data holds, which is also how a script reads a language other than its own. query() resolves to undefined when the bot's game version has no game data, hence result?.rows. Read-only The game data is opened read-only. INSERT, UPDATE, DELETE, CREATE and every other statement that writes fail, and ATTACH is refused too, so a query can't reach any other database file. Every statement in the string runs, and query() returns the result of the last one: SELECT 1; SELECT 2 returns 2. Since nothing can be written, the statements before the last one only cost time. Parameters Write ? wherever a value goes, and pass the values in the params array, in the same order. A parameter keeps its type, so a number is compared as a number, whereas a value written into the SQL between quotes, like '147', is text. params has to be an array: anything else rejects with gamedata.query params must be an array. Pass numbers and strings, and convert BigInt ids with Number() first. Writing the SQL Put text between single quotes, as in 'monsters'. Double quotes only name a table or a column, so "monsters" is never a string, and a mistyped column name gives an error instead of a wrong result. Names go through the _names table, with the columns record_table, record_id, name_id, name, language and ordinal. A record can have several names, and ordinal 0 is its main one. The texts of one language are in the table text_ followed by the language code, such as text_fr, with an id and a text column. In a derived table, whose name contains two underscores, each row points to its parent row with _parent_id. _names_fts is a full-text index of the names, much faster than LIKE '%...%'. The SQL tab's Examples show how to use it. Values come back the way they're stored: numbers as regular numbers, never BigInt, text as strings, and an empty value as null. A column holding nested data comes as an array or an object. A computed column, such as COUNT(*), comes back as the database computed it. Limits and errors Limit Value Past it Rows 200 unless you pass a limit, 5,000 at most The other rows are left out, and truncated is true. Time 10 seconds for the query The query is interrupted and the promise rejects. Length of the SQL 64 KiB The promise rejects with a text such as datacenter: query is 70000 bytes, over the 65536 byte limit. Size of one value the query builds 16 MiB The query fails. Queries at once 4 on one game version, shared by every bot and every open SQL tab Other queries wait for their turn. Calls in progress 32 for a script, counted together with send(), request() and the other game data calls The call rejects with behavior resource limit exceeded: maximum pending operations reached. Problem The promise rejects with sql is empty or isn't a string gamedata.query sql must be a non-empty string sql only holds spaces datacenter: empty query params isn't an array gamedata.query params must be an array A mistake in the SQL A text starting with datacenter: that names the problem, such as no such column: nmae Try it in the SQL tab first The game version's SQL tab runs queries on the same data, with the same limits, and shows the result as a table. It's the quickest way to get a query right before putting it in a script. In Game Manager, open the bot's game version, go to its Game data tab, then to SQL. Type a query, or pick one from Examples. They're grouped by topic: explore, names, monsters, items, harvesting, navigation, crafting, maps, quests and text. Click Run, or press Ctrl+Enter. Next to the buttons, the tab shows the number of rows, stopped at the limit when some were left out, and how long the query took. When the query returns what you want, copy it into your script, and replace the values you typed with ? and params. The tab has no parameters, so write the values into the SQL while you try a query there. It also shows up to 500 rows, where a script gets 200 unless it passes a limit. When a query fails, the tab shows the same text a script would get. SQL describes the tab in full. A complete example This package adds a button that lists the sub-areas where a monster lives, with a join the lookups can't do: import { session, botInfo } from "asterobot:bot"; import { query } from "asterobot:gamedata"; import { send } from "asterobot:protocol"; const SUB_AREAS_OF_MONSTER = ` SELECT DISTINCT area.name FROM _names monster JOIN monsters__subareas link ON link._parent_id = monster.record_id JOIN _names area ON area.record_table = 'sub_areas' AND area.record_id = link.value AND area.language = monster.language WHERE monster.record_table = 'monsters' AND monster.language = ? AND monster.name = ?`; export const actions = { subAreas: { label: "Find where a monster lives", args: { monster: { type: "string", label: "Monster name" }, }, async run({ monster }) { const result = await query(SUB_AREAS_OF_MONSTER, [session.language, monster]); if (result === undefined) throw new Error("No game data for this bot's game version"); const areas = result.rows.map(([name]) => name); botInfo(`${monster}:`, areas.length > 0 ? areas.join(", ") : "no sub-area found"); }, }, }; 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, }); } } The query takes the script's language as its first parameter, so type the monster's French name for now. The button appears under Package settings on the bot's Settings tab once the package runs, as in Lookups.

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.