mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-13 08:04:53 +09:00
* Add exp_discord plugin for Discord login and account linking Players log in to the web interface with Discord through OAuth2 with the identify scope, so no bot token is needed. After logging in they get a code from the login page and type /link-discord <code> in game to link that Factorio account. One Discord account can hold up to exp_discord.max_accounts Factorio accounts (default 3), and linking a name moves it off any other Discord account. Sessions and link codes live in memory on the controller plugin, the links are stored in database/exp_discord/accounts.json. The user page shows the linked Discord account and lets the user, or anyone with exp_discord.link.delete, unlink it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Link Discord from the user page and document the setup The user page gets a Link Discord button. It asks the controller for a one-time ticket bound to the logged in user, then goes through the authorize route with it. The callback links the Discord account to that user directly, without the in-game code, and goes back to the user page. The link flow always shows the Discord consent screen. The README covers registering the redirect URL and the config. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: bbassie <17990055+bbassie@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
import type { Instance, InstancePluginContext } from "@clusterio/host";
|
|
import type * as lib from "@clusterio/lib";
|
|
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 class InstancePlugin {
|
|
private constructor(
|
|
public instance: Instance,
|
|
public logger: lib.Logger,
|
|
public name: string,
|
|
) {}
|
|
|
|
static async fromContext(context: InstancePluginContext) {
|
|
const instance = context.instance;
|
|
const plugin = new InstancePlugin(instance, context.logger, context.plugin.name);
|
|
|
|
instance.server.handle("exp_discord:link", plugin.handleLinkIPC.bind(plugin));
|
|
instance.server.handle("exp_discord:unlink", plugin.handleUnlinkIPC.bind(plugin));
|
|
instance.server.handle("exp_discord:get", plugin.handleGetIPC.bind(plugin));
|
|
return plugin;
|
|
}
|
|
|
|
async handleLinkIPC(event: IpcLink) {
|
|
try {
|
|
const discord = await this.instance.sendTo("controller", new messages.DiscordLinkRequest(
|
|
event.player_name, event.code,
|
|
));
|
|
await this.luaSend("receive_linked", { player_name: event.player_name, discord_name: discord!.name });
|
|
} catch (err: any) {
|
|
await this.luaSend("receive_error", { player_name: event.player_name, message: err.message });
|
|
}
|
|
}
|
|
|
|
async handleUnlinkIPC(event: IpcPlayer) {
|
|
try {
|
|
const discord = await this.instance.sendTo("controller", new messages.DiscordUnlinkRequest(event.player_name));
|
|
await this.luaSend("receive_unlinked", { player_name: event.player_name, discord_name: discord?.name });
|
|
} catch (err: any) {
|
|
await this.luaSend("receive_error", { player_name: event.player_name, message: err.message });
|
|
}
|
|
}
|
|
|
|
async handleGetIPC(event: IpcPlayer) {
|
|
try {
|
|
const discord = await this.instance.sendTo("controller", new messages.DiscordLinkGetRequest(event.player_name));
|
|
await this.luaSend("receive_get", { player_name: event.player_name, discord_name: discord?.name });
|
|
} catch (err: any) {
|
|
await this.luaSend("receive_error", { player_name: event.player_name, message: err.message });
|
|
}
|
|
}
|
|
|
|
/** Hand an answer to lua, unless the instance stopped while it was being fetched. */
|
|
async luaSend(receiver: string, json: any) {
|
|
if (this.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 this.instance.sendRcon(
|
|
`/sc exp_discord.${receiver}(helpers.json_to_table[${level}[${text}]${level}])`, true
|
|
);
|
|
}
|
|
}
|
|
|
|
export default async function (context: InstancePluginContext) {
|
|
await InstancePlugin.fromContext(context);
|
|
}
|