Let messages through, drop them or change them on their way through the bot with intercept(), how a message waits for your answer, and use it safely on a MITM bot.
Every other function in this chapter observes messages that have already gone through. An interceptor is asked first, and its answer decides whether a message continues, and in what form. On a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), that decides what your Dofus client and the game server receive, so read On a MITM bot before using one there.
intercept()
intercept(selector, handler); // asked about the messages the selector matches
intercept(handler); // asked about every message
intercept() registers handler and returns a handle to pass to off(). The selector is the same as for on(): a message name, "*", or a function. Listening describes them. Interceptors count towards the 256 handlers a script can register with on() and onTraffic(), and registering one throws the same errors, with protocol.intercept handler must be callable when the handler isn't a function.
The handler receives the message's traffic object and returns a decision:
| Return | What happens to the message |
|---|---|
Nothing, undefined, or any other value |
No decision. The next interceptor is asked, and when none decides, the message continues unchanged. |
"drop" |
The message stops here. |
{ payload: { ... } } |
The message continues with these fields instead of its own. See Changing a payload. |
{ raw: bytes } |
The message is replaced by these bytes. See Raw replacements. |
What "continues" covers depends on the bot. On a Full socket bot, the message reaches your script's handlers and waits, and the bot's page in Asteroboard: the Network tool, the console's chat and live inventory. On a MITM bot, it also reaches the other side, your Dofus client or the game server. A dropped message reaches none of them, and keeps its sequence number, which leaves a gap.
What an interceptor is asked about
| Bot | Messages interceptors decide about |
|---|---|
| Full socket | Messages from the game server. |
| MITM | Messages from the game server to your Dofus client, and from your Dofus client to the server. |
Interceptors are never asked about:
- messages a script sends with
send()orrequest(), towards the server or the client, which is why an interceptor can callsend(); - messages someone sends from the Network tool's Send a game message;
- on a MITM bot, the responses to your script's own
request()calls, and the first message of the session, your client'sIdentificationRequest.
Those messages still reach onTraffic() and the Network tool as usual.
The message waits for your answer
While an interceptor is registered, each message it could decide about waits for your script's answer, however long the script takes to get to it. A busy script makes the message later, never undecided: your decision always applies, on a slow PC as on a fast one.
The wait is still bounded. Like any piece of code, an interceptor that runs for more than 5 s is stopped, and the message continues unchanged (see When an interceptor fails). When the script stops, messages continue unchanged.
An interceptor applies to the messages that arrive after it's registered. Register it as your package loads, at the top level of a module or before the first await of the entry function, and it decides every message from Play on: until the package has loaded, every message waits for it.
Three things follow:
- Keep interceptors short. A message waiting for your interceptor also waits for the code running when it arrives, and for the messages before it. Do the quick test in the interceptor, and anything slow somewhere else, such as an
on()handler broken up withawait. - An interceptor must answer synchronously. An
asyncfunction returns a promise, and a promise isn't a decision, so the message always continues unchanged. An error inside anasyncinterceptor also becomes an unhandled rejection, which stops the script. - A message no interceptor names doesn't wait at all. An interceptor registered with a function selector,
"*"or no selector makes every message of the kinds above wait for the script, because the script has to look at each one. Name the messages you decide about, and remove interceptors you no longer need withoff().
The first decisive answer wins
Interceptors are asked in the order they were registered. The first one that returns "drop", { payload } or { raw } decides, and the ones after it aren't asked. An interceptor whose selector doesn't match isn't asked, and doesn't count as an answer.
An interceptor that fails doesn't decide either, and the next one is asked. That covers a handler that throws, a function selector that throws, and a { payload } that can't be built. When an object holds both raw and payload, raw wins.
Changing a payload
Return { payload } to let a message continue with other values. This interceptor defangs links in the chat lines your handlers receive, and on a MITM bot, the lines your Dofus client shows:
intercept("ChatChannelMessageEvent", (traffic) => {
const content = traffic.payload?.content;
if (typeof content !== "string" || !content.includes("http")) return undefined;
// The spread keeps every other field: the replacement is built from this object alone.
return { payload: { ...traffic.payload, content: content.replaceAll("http", "hxxp") } };
});
Rules for { payload }:
- The message is rebuilt from your object alone. A field you leave out is sent unset, or at its zero value, so start from
...traffic.payload. Bytes of the original that Asterobot's definitions don't describe can't be carried over. - The message keeps its type, its
kind, itsuidand its wire name. To send a different message, drop this one and callsend(). - A message without a readable name can't get a new payload.
- Your handlers and the Network tool receive the replacement, not the original.
When the replacement can't be built, the original message continues unchanged, and the bot's console says why, as in build replacement payload for ChatChannelMessageEvent: ChatChannelMessageEvent.contnt: unknown protobuf field. For a message without a readable name, the line starts with cannot replace the payload of followed by its wire name.
Dropping a message
Return "drop" to stop a message. On a MITM bot, this script turns the chat lines you type starting with ! into commands for the script: they never reach the server, so other players don't see them.
import { session, botInfo } from "asterobot:bot";
import { intercept, send } from "asterobot:protocol";
export default async function behavior(launch) {
intercept("ChatChannelMessageRequest", (traffic) => {
const content = traffic.payload?.content;
if (typeof content !== "string" || !content.startsWith("!")) return undefined;
// Writing a line is quick enough to do before answering.
botInfo("Command:", content);
return "drop";
});
// A new game connection starts with this message. MITM bots never launch
// with "initial": the Dofus client has already identified their session.
if (launch.reason === "initial") {
await send("IdentificationRequest", {
ticketKey: session.gameToken,
languageCode: session.language,
});
}
}
On a Full socket bot, this interceptor is never asked anything: every message going to the server there comes from a script, and those aren't intercepted.
On a Full socket bot, dropping a message keeps it from your own handlers, which makes an interceptor a filter. Dropping a response that a request() is waiting for makes that request() time out.
Raw replacements
{ raw } replaces a message with the bytes you give, as a Uint8Array or an ArrayBuffer. Any other value doesn't count as an answer.
The bytes replace the whole message as it travels: the part that states the message's kind, uid and wire name, and the fields inside it. That's more than traffic.rawAny, which only holds the fields, and Asterobot has no function that builds those bytes for you. { raw } is only for a message nothing else can handle, when you know that format yourself.
When Asterobot can't read your bytes as a message, your handlers receive the original message. On a MITM bot, the bytes still go to the other side as they are.
Warning
On a MITM bot, a wrong { raw } sends your Dofus client or the game server bytes they can't read.
When an interceptor fails
Unlike an on() handler, an interceptor that throws doesn't stop the script, unless it's async (see The message waits for your answer). The message continues unchanged, and the bot's console shows an error line from the script:
| The line starts with | Cause |
|---|---|
interceptor failed: |
The handler threw an error, or ran for more than 5 s. |
interceptor selector failed: |
The function selector threw an error. |
build replacement payload for |
A field or value in { payload } doesn't fit the message. |
cannot replace the payload of |
{ payload } was returned for a message without a readable name. |
An interceptor that fails on every message writes one of these lines for each message, so fix it before it fills the console.
An async interceptor isn't reported anywhere: the message continues unchanged.
On a MITM bot
Caution
On a MITM bot, an interceptor acts on the game of the person playing. A dropped message from the server never reaches their Dofus client, a dropped message from the client never reaches the server, and a replacement reaches the other side exactly as you built it. What their client shows and what the server knows can then differ.
While an interceptor is registered, each message it's asked about waits for your script, and the rest of the session waits behind it, since messages keep their order. A script that's slow to answer, or busy with other work when such a message arrives, is felt as lag in the game.
If the script stops or runs into trouble, messages continue unchanged: a failing interceptor can't block a session.
What interceptors are good for
- Keeping noise out of your own handlers on a Full socket bot, with
"drop". - Turning chat lines into commands on a MITM bot, as above.
- Changing what your Dofus client receives on a MITM bot, with
{ payload }. - Replacing a message with a different one: return
"drop"andsend()the new message from the interceptor.
Next, Payloads lists the rules a { payload } follows, and Sessions what else changes on a MITM bot.