Files
bbassieandClaude Opus 5.5 1c713ba0aa Address review of the Discord plugin
The plugin is now exp_discord_auth, so later Discord plugins can sit
next to it. Config keys, permissions, routes and the Lua module moved
with it, and the Discord redirect is now api/exp_discord_auth/callback.

The controller registers its handlers and routes in the constructor and
uses the express types, the instance plugin is a closure, and the Lua
commands file returns its commands. The get and unlink responses carry
the controller url so the in game messages can name the website.

The login form asks the new status route whether Discord is configured
and shows the error straight away instead of a button that fails. The
button is full width like the link button, and a "Not you?" link next
to the Discord name replaces the separate switch account button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:38:20 +00:00

68 lines
2.4 KiB
TypeScript

import type { InstancePluginContext } from "@clusterio/host";
import * as messages from "./messages.js";
/** Sent by the lua side when a player uses a link code. */
export type IpcLink = {
player_name: string,
code: string,
};
/** Sent by the lua side when a player unlinks their Discord account, or asks which one they are linked to. */
export type IpcPlayer = {
player_name: string,
};
/** Bridges the lua side and the controller, the answer is printed to the player once it arrives. */
export default async function (context: InstancePluginContext) {
const { instance } = context;
/** Hand an answer to lua, unless the instance stopped while it was being fetched. */
async function luaSend(receiver: string, json: any) {
if (instance.status !== "running") {
return;
}
// Discord names can contain anything, so pick a long bracket level the json does not close
const text = JSON.stringify(json);
let level = "=";
while (text.includes(`]${level}]`)) {
level += "=";
}
await instance.sendRcon(
`/sc exp_discord_auth.${receiver}(helpers.json_to_table[${level}[${text}]${level}])`, true
);
}
instance.server.handle("exp_discord_auth:link", async (event: IpcLink) => {
try {
const discord = await instance.sendTo("controller", new messages.DiscordLinkRequest(
event.player_name, event.code,
));
await luaSend("receive_linked", { player_name: event.player_name, discord_name: discord!.name });
} catch (err: any) {
await luaSend("receive_error", { player_name: event.player_name, message: err.message });
}
});
instance.server.handle("exp_discord_auth:unlink", async (event: IpcPlayer) => {
try {
const status = await instance.sendTo("controller", new messages.DiscordUnlinkRequest(event.player_name));
await luaSend("receive_unlinked", {
player_name: event.player_name, discord_name: status.link?.name, url: status.url,
});
} catch (err: any) {
await luaSend("receive_error", { player_name: event.player_name, message: err.message });
}
});
instance.server.handle("exp_discord_auth:get", async (event: IpcPlayer) => {
try {
const status = await instance.sendTo("controller", new messages.DiscordLinkGetRequest(event.player_name));
await luaSend("receive_get", {
player_name: event.player_name, discord_name: status.link?.name, url: status.url,
});
} catch (err: any) {
await luaSend("receive_error", { player_name: event.player_name, message: err.message });
}
});
}