Files
factorio-expcluster/exp_discord/module/control.lua
T
0c593cd9fe Feature/discord plugin (#54)
* 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>
2026-10-12 14:05:15 +09:00

89 lines
3.1 KiB
Lua

--[[-- ExpDiscord
Links Factorio accounts to Discord accounts, the links are kept on the controller.
A player logs in with Discord on the web interface, which gives them a code.
Typing that code in game links the account they are playing on. Nothing is
stored in the lua state, every call goes to the controller and the answer is
printed to the player once it arrives, if they are still online.
]]
local clusterio_api = require("modules/clusterio/api")
local ExpUtil = require("modules/exp_util")
--- @class ExpDiscord
local ExpDiscord = {}
local error_settings = { color = ExpUtil.color.orange_red, sound_path = "utility/wire_pickup" }
--- Link the player to the Discord account which made the code
--- @param player LuaPlayer
--- @param code string
function ExpDiscord.link(player, code)
clusterio_api.send_json("exp_discord:link", { player_name = player.name, code = code })
end
--- Remove the link between the player and their Discord account
--- @param player LuaPlayer
function ExpDiscord.unlink(player)
clusterio_api.send_json("exp_discord:unlink", { player_name = player.name })
end
--- Print the Discord account the player is linked to
--- @param player LuaPlayer
function ExpDiscord.get_link(player)
clusterio_api.send_json("exp_discord:get", { player_name = player.name })
end
--- A player who can still be printed to, nil once they have left
--- @param name string
--- @return LuaPlayer?
local function get_online_player(name)
local player = game.get_player(name)
if player and player.valid and player.connected then
return player
end
return nil
end
--- The controller linked the player
--- @param payload { player_name: string, discord_name: string }
function ExpDiscord.receive_linked(payload)
local player = get_online_player(payload.player_name)
if not player then return end
player.print{ "exp-discord.linked", payload.discord_name }
end
--- The controller removed the link, discord_name is nil if there was none
--- @param payload { player_name: string, discord_name: string? }
function ExpDiscord.receive_unlinked(payload)
local player = get_online_player(payload.player_name)
if not player then return end
if payload.discord_name then
player.print{ "exp-discord.unlinked", payload.discord_name }
else
player.print({ "exp-discord.not-linked" }, error_settings)
end
end
--- The controller answered which account the player is linked to
--- @param payload { player_name: string, discord_name: string? }
function ExpDiscord.receive_get(payload)
local player = get_online_player(payload.player_name)
if not player then return end
if payload.discord_name then
player.print{ "exp-discord.linked-to", payload.discord_name }
else
player.print{ "exp-discord.not-linked" }
end
end
--- The controller refused a request
--- @param payload { player_name: string, message: string }
function ExpDiscord.receive_error(payload)
local player = get_online_player(payload.player_name)
if not player then return end
player.print(payload.message, error_settings)
end
return ExpDiscord