Files
tekton/server/nakama/lua/admin.lua
T
2026-05-19 17:30:29 +08:00

356 lines
12 KiB
Lua

local nk = require("nakama")
local utils = require("lua.utils")
local admin = {}
function admin.rpc_admin_kick_player(context, payload)
local request = nk.json_decode(payload)
utils.require_admin_or_host(context, request.match_id)
if request.user_id == context.user_id then
error("Cannot kick yourself")
end
local status, err = pcall(nk.match_signal, request.match_id, nk.json_encode({
action = "kick",
user_id = request.user_id,
reason = request.reason or "Kicked by admin"
}))
if not status then
nk.logger_error("Failed to kick player: " .. tostring(err))
error("Failed to kick player")
end
nk.logger_info("Player " .. request.user_id .. " kicked from match " .. request.match_id .. " by " .. context.user_id)
return nk.json_encode({ success = true })
end
function admin.rpc_admin_ban_player(context, payload)
local request = nk.json_decode(payload)
utils.require_admin(context)
if request.user_id == context.user_id then
error("Cannot ban yourself")
end
local status, targetAccount = pcall(nk.account_get_id, request.user_id)
if not status then error("Target account not found") end
local metadata = {}
if targetAccount.user.metadata then
status, metadata = pcall(nk.json_decode, targetAccount.user.metadata)
if not status then metadata = {} end
end
if utils.ADMIN_ROLES[metadata.role or ""] then
error("Cannot ban an admin")
end
local banExpires = nil
if request.duration_hours and request.duration_hours > 0 then
-- Unix time in seconds for lua, Nakama might expect ISO string or unix
banExpires = os.time() + (request.duration_hours * 60 * 60)
end
metadata.banned = true
metadata.ban_reason = request.reason or "Banned by admin"
if banExpires then metadata.ban_expires = banExpires end
nk.account_update_id(request.user_id, nil, nil, nil, nil, nil, nil, nk.json_encode(metadata))
if request.match_id then
pcall(nk.match_signal, request.match_id, nk.json_encode({
action = "kick",
user_id = request.user_id,
reason = "Banned: " .. (request.reason or "")
}))
end
local banRecord = {
user_id = request.user_id,
username = targetAccount.user.username,
banned_by = context.user_id,
banned_at = os.time(),
reason = request.reason,
expires = banExpires
}
nk.storage_write({{
collection = "bans",
key = request.user_id,
user_id = "00000000-0000-0000-0000-000000000000",
value = banRecord,
permission_read = 2,
permission_write = 0
}})
nk.logger_warn("Player " .. request.user_id .. " banned by " .. context.user_id)
return nk.json_encode({ success = true, ban = banRecord })
end
function admin.rpc_admin_unban_player(context, payload)
local request = nk.json_decode(payload)
utils.require_admin(context)
local status, targetAccount = pcall(nk.account_get_id, request.user_id)
if not status then error("Target account not found") end
local metadata = {}
if targetAccount.user.metadata then
status, metadata = pcall(nk.json_decode, targetAccount.user.metadata)
if not status then metadata = {} end
end
metadata.banned = nil
metadata.ban_reason = nil
metadata.ban_expires = nil
nk.account_update_id(request.user_id, nil, nil, nil, nil, nil, nil, nk.json_encode(metadata))
nk.storage_delete({{
collection = "bans",
key = request.user_id,
user_id = "00000000-0000-0000-0000-000000000000"
}})
nk.logger_info("Player " .. request.user_id .. " unbanned by " .. context.user_id)
return nk.json_encode({ success = true })
end
function admin.rpc_admin_get_ban_list(context, payload)
utils.require_admin(context)
local status, result = pcall(nk.storage_list, "00000000-0000-0000-0000-000000000000", "bans", 100)
local bans = {}
if status and result and result.objects then
for _, obj in ipairs(result.objects) do
table.insert(bans, obj.value)
end
end
return nk.json_encode({ bans = bans })
end
function admin.rpc_admin_get_server_stats(context, payload)
local request = nk.json_decode(payload or "{}")
if request.match_id then
utils.require_admin_or_host(context, request.match_id)
else
utils.require_admin(context)
end
local matches = nk.match_list(100, true)
local activeMatchCount = matches and #matches or 0
local totalPlayers = 0
if matches then
for _, match in ipairs(matches) do
totalPlayers = totalPlayers + (match.size or 0)
end
end
local stats = {
active_matches = activeMatchCount,
total_players = totalPlayers,
server_time = os.time()
}
if request.match_id then
local status, match = pcall(nk.match_get, request.match_id)
if status and match then
stats.match = {
id = match.match_id,
size = match.size,
tick_rate = match.tick_rate,
authoritative = match.authoritative
}
end
end
return nk.json_encode(stats)
end
function admin.rpc_admin_end_match(context, payload)
local request = nk.json_decode(payload)
utils.require_admin_or_host(context, request.match_id)
nk.match_signal(request.match_id, nk.json_encode({
action = "end_match",
reason = request.reason or "Ended by admin"
}))
nk.logger_info("Match " .. request.match_id .. " ended by " .. context.user_id)
return nk.json_encode({ success = true })
end
function admin.rpc_admin_set_user_role(context, payload)
local request = nk.json_decode(payload)
local callerAccount = nk.account_get_id(context.user_id)
local callerMetadata = nk.json_decode(callerAccount.user.metadata or "{}")
if callerMetadata.role ~= "owner" then
error("Only owners can modify user roles")
end
local validRoles = { player = true, moderator = true, admin = true }
if not validRoles[request.role] then
error("Invalid role")
end
local targetAccount = nk.account_get_id(request.user_id)
local metadata = nk.json_decode(targetAccount.user.metadata or "{}")
metadata.role = request.role
nk.account_update_id(request.user_id, nil, nil, nil, nil, nil, nil, nk.json_encode(metadata))
nk.logger_info("User " .. request.user_id .. " role set to " .. request.role .. " by " .. context.user_id)
return nk.json_encode({ success = true, role = request.role })
end
function admin.rpc_admin_topup_gold(context, payload)
utils.require_admin(context)
nk.wallet_update(context.user_id, { gold = 999999 })
nk.logger_info("Admin gold top-up applied for user " .. context.user_id)
return nk.json_encode({ success = true, gold_added = 999999 })
end
function admin.rpc_admin_clear_global_chat(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload or "{}")
local channelId = request.channel_id or ""
if channelId == "" then
error("channel_id is required. Pass the channel ID from the client.")
end
local deleted = 0
local cursor = ""
repeat
local status, result = pcall(nk.channel_messages_list, channelId, 100, false, cursor)
if not status then break end
local messages = result.messages or {}
for _, msg in ipairs(messages) do
pcall(nk.channel_message_remove, channelId, msg.message_id)
deleted = deleted + 1
end
cursor = result.next_cursor or ""
until cursor == ""
nk.logger_info("[AdminClearGlobalChat] Deleted " .. deleted .. " messages by " .. context.user_id)
return nk.json_encode({ success = true, deleted = deleted })
end
function admin.rpc_admin_list_users(context, payload)
utils.require_admin(context)
local users = {}
local sql = "SELECT id, username, display_name, metadata, create_time FROM users WHERE id != '00000000-0000-0000-0000-000000000000' ORDER BY create_time DESC LIMIT 500"
local status, rows = pcall(nk.sql_query, sql, {})
if status and rows then
for _, row in ipairs(rows) do
local metadata = {}
if row.metadata then
local s, m = pcall(nk.json_decode, row.metadata)
if s then metadata = m end
end
table.insert(users, {
user_id = row.id,
username = row.username or "",
display_name = row.display_name or row.username or "",
create_time = row.create_time,
role = metadata.role or "player",
banned = metadata.banned or false,
ban_reason = metadata.ban_reason or ""
})
end
end
return nk.json_encode({ users = users, count = #users })
end
function admin.rpc_admin_delete_users(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload)
local userIds = request.user_ids or {}
if #userIds == 0 then error("No user IDs provided") end
for _, uid in ipairs(userIds) do
if uid == context.user_id then
error("Cannot delete your own account")
end
end
local deleted = {}
local failed = {}
for _, uid in ipairs(userIds) do
local status, err = pcall(function()
local account = nk.account_get_id(uid)
local meta = {}
if account.user.metadata then
local s, m = pcall(nk.json_decode, account.user.metadata)
if s then meta = m end
end
if meta.role == "admin" or meta.role == "moderator" or meta.role == "owner" then
error("Cannot delete admin account")
end
nk.account_delete_id(uid, false)
table.insert(deleted, uid)
nk.logger_warn("User " .. uid .. " deleted by " .. context.user_id)
end)
if not status then
table.insert(failed, { user_id = uid, reason = tostring(err) })
end
end
return nk.json_encode({ success = true, deleted = deleted, failed = failed })
end
function admin.rpc_admin_get_player_list(context, payload)
local request = nk.json_decode(payload)
utils.require_admin_or_host(context, request.match_id)
local status, match = pcall(nk.match_get, request.match_id)
if not status or not match then
error("Match not found")
end
-- Get player details
-- Note: In actual implementation, you'd need to track presences
-- This is a simplified version - adjust based on your match handler
local players = {}
return nk.json_encode({ players = players })
end
-- Register RPCs
nk.register_rpc(admin.rpc_admin_kick_player, "admin_kick_player")
nk.register_rpc(admin.rpc_admin_ban_player, "admin_ban_player")
nk.register_rpc(admin.rpc_admin_unban_player, "admin_unban_player")
nk.register_rpc(admin.rpc_admin_get_ban_list, "admin_get_ban_list")
nk.register_rpc(admin.rpc_admin_get_server_stats, "admin_get_server_stats")
nk.register_rpc(admin.rpc_admin_get_player_list, "admin_get_player_list")
nk.register_rpc(admin.rpc_admin_end_match, "admin_end_match")
nk.register_rpc(admin.rpc_admin_set_user_role, "admin_set_user_role")
nk.register_rpc(admin.rpc_admin_topup_gold, "admin_topup_gold")
nk.register_rpc(admin.rpc_admin_clear_global_chat, "admin_clear_global_chat")
nk.register_rpc(admin.rpc_admin_list_users, "admin_list_users")
nk.register_rpc(admin.rpc_admin_delete_users, "admin_delete_users")
nk.logger_info("LUA TEST: admin module loaded successfully")
return admin