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

536 lines
19 KiB
Lua

local nk = require("nakama")
local utils = require("lua.utils")
local inbox = {}
function inbox.rpc_admin_send_mail(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload or "{}")
local nowStr = os.date("!%Y-%m-%dT%H:%M:%S.000Z") -- approximate ISO8601
local startDate = request.start_date or nowStr
local endDate = request.end_date or ""
-- 30 days from now in seconds for expiry_date fallback if not specified
local expiryDate = os.date("!%Y-%m-%dT%H:%M:%S.000Z", os.time() + 30 * 24 * 60 * 60)
local mailObj = {
id = nk.uuid_v4(),
title = request.title or "Announcement",
content = request.content or "",
sender = "TEKTON DEV TEAM",
date = startDate,
start_date = startDate,
end_date = endDate,
expiry_date = expiryDate,
rewards = request.rewards or {}
}
if request.target_user_id and request.target_user_id ~= "" then
mailObj.type = "personal"
local invObjs = nk.storage_read({{ collection = "inbox", key = "personal", user_id = request.target_user_id }})
local personalMails = {}
if #invObjs > 0 then
personalMails = invObjs[1].value.mails or {}
end
table.insert(personalMails, mailObj)
nk.storage_write({{
collection = "inbox",
key = "personal",
user_id = request.target_user_id,
value = { mails = personalMails },
permission_read = 1,
permission_write = 0
}})
nk.logger_info("Personal mail sent to " .. request.target_user_id)
else
mailObj.type = "global"
local globalObjs = nk.storage_read({{ collection = "config", key = "global_mail", user_id = "00000000-0000-0000-0000-000000000000" }})
local globalMails = {}
if #globalObjs > 0 then
globalMails = globalObjs[1].value.mails or {}
end
table.insert(globalMails, mailObj)
nk.storage_write({{
collection = "config",
key = "global_mail",
user_id = "00000000-0000-0000-0000-000000000000",
value = { mails = globalMails },
permission_read = 2,
permission_write = 0
}})
nk.logger_info("Global mail sent")
end
return nk.json_encode({ success = true, mail = mailObj })
end
function inbox.rpc_get_mail(context, payload)
if not context.user_id then error("Not authenticated") end
local personalObjs = nk.storage_read({{ collection = "inbox", key = "personal", user_id = context.user_id }})
local globalObjs = nk.storage_read({{ collection = "config", key = "global_mail", user_id = "00000000-0000-0000-0000-000000000000" }})
local stateObjs = nk.storage_read({{ collection = "inbox", key = "state", user_id = context.user_id }})
local personalMails = (#personalObjs > 0) and (personalObjs[1].value.mails or {}) or {}
local globalMails = (#globalObjs > 0) and (globalObjs[1].value.mails or {}) or {}
local state = { claimed_ids = {}, deleted_ids = {}, read_ids = {} }
if #stateObjs > 0 then
local val = stateObjs[1].value
state.claimed_ids = val.claimed_ids or {}
state.deleted_ids = val.deleted_ids or {}
state.read_ids = val.read_ids or {}
end
local function array_contains(arr, val)
for _, v in ipairs(arr) do
if v == val then return true end
end
return false
end
local allMails = {}
for _, m in ipairs(personalMails) do table.insert(allMails, m) end
for _, m in ipairs(globalMails) do table.insert(allMails, m) end
local filteredMails = {}
local nowStr = os.date("!%Y-%m-%dT%H:%M:%S.000Z")
for _, mail in ipairs(allMails) do
if not array_contains(state.deleted_ids, mail.id) then
local skip = false
if mail.expiry_date and mail.expiry_date ~= "" and nowStr > mail.expiry_date then
skip = true
end
if not skip and mail.start_date and mail.start_date ~= "" and nowStr < mail.start_date then
skip = true
end
if not skip and mail.type == "global" and mail.end_date and mail.end_date ~= "" and nowStr > mail.end_date then
skip = true
end
if not skip then
table.insert(filteredMails, mail)
end
end
end
return nk.json_encode({ mails = filteredMails, state = state })
end
function inbox.rpc_claim_mail_reward(context, payload)
if not context.user_id then error("Not authenticated") end
local request = nk.json_decode(payload or "{}")
local mailId = request.mail_id
if not mailId then error("mail_id required") end
local personalObjs = nk.storage_read({{ collection = "inbox", key = "personal", user_id = context.user_id }})
local globalObjs = nk.storage_read({{ collection = "config", key = "global_mail", user_id = "00000000-0000-0000-0000-000000000000" }})
local stateObjs = nk.storage_read({{ collection = "inbox", key = "state", user_id = context.user_id }})
local state = { claimed_ids = {}, deleted_ids = {}, read_ids = {} }
if #stateObjs > 0 then
local val = stateObjs[1].value
state.claimed_ids = val.claimed_ids or {}
state.deleted_ids = val.deleted_ids or {}
state.read_ids = val.read_ids or {}
end
local function array_contains(arr, val)
for _, v in ipairs(arr) do
if v == val then return true end
end
return false
end
if array_contains(state.claimed_ids, mailId) then
error("Reward already claimed")
end
local personalMails = (#personalObjs > 0) and (personalObjs[1].value.mails or {}) or {}
local globalMails = (#globalObjs > 0) and (globalObjs[1].value.mails or {}) or {}
local allMails = {}
for _, m in ipairs(personalMails) do table.insert(allMails, m) end
for _, m in ipairs(globalMails) do table.insert(allMails, m) end
local targetMail = nil
for _, mail in ipairs(allMails) do
if mail.id == mailId then
targetMail = mail
break
end
end
if not targetMail then error("Mail not found") end
local rewards = targetMail.rewards or {}
local starTotal = 0
local goldTotal = 0
local fragsToUpdate = {}
local skinsToAdd = {}
if type(rewards) == "table" and not rewards[1] and (rewards.star or rewards.gold) then
-- Handle legacy dictionary format
starTotal = rewards.star or 0
goldTotal = rewards.gold or 0
rewards = {}
end
for _, r in ipairs(rewards) do
local rType = r.type or "star"
local amount = r.amount or 0
if rType == "star" then
starTotal = starTotal + amount
elseif rType == "gold" then
goldTotal = goldTotal + amount
elseif string.sub(rType, 1, 5) == "frag_" or rType == "item" then
local fragId = r.id or rType
fragsToUpdate[fragId] = (fragsToUpdate[fragId] or 0) + amount
elseif rType == "skin" then
if r.id then table.insert(skinsToAdd, r.id) end
end
end
if starTotal > 0 or goldTotal > 0 then
local changes = {}
if starTotal > 0 then changes.star = starTotal end
if goldTotal > 0 then changes.gold = goldTotal end
nk.wallet_update(context.user_id, changes, {}, true)
end
local fragKeysCount = 0
for _ in pairs(fragsToUpdate) do fragKeysCount = fragKeysCount + 1 end
if fragKeysCount > 0 then
local invObjs = nk.storage_read({{ collection = "inventory", key = "fragments", user_id = context.user_id }})
local frags = (#invObjs > 0) and invObjs[1].value or {}
for fId, count in pairs(fragsToUpdate) do
frags[fId] = (frags[fId] or 0) + count
end
nk.storage_write({{
collection = "inventory",
key = "fragments",
user_id = context.user_id,
value = frags,
permission_read = 1,
permission_write = 0
}})
end
if #skinsToAdd > 0 then
local skinWrites = {}
for _, sId in ipairs(skinsToAdd) do
table.insert(skinWrites, {
collection = "inventory",
key = sId,
user_id = context.user_id,
value = { acquired_via = "mail", purchased_at = os.date("!%Y-%m-%dT%H:%M:%S.000Z") },
permission_read = 1,
permission_write = 0
})
end
nk.storage_write(skinWrites)
end
table.insert(state.claimed_ids, mailId)
if not array_contains(state.read_ids, mailId) then
table.insert(state.read_ids, mailId)
end
nk.storage_write({{
collection = "inbox",
key = "state",
user_id = context.user_id,
value = state,
permission_read = 1,
permission_write = 0
}})
return nk.json_encode({ success = true, claimed_ids = state.claimed_ids })
end
function inbox.rpc_delete_mail(context, payload)
if not context.user_id then error("Not authenticated") end
local request = nk.json_decode(payload or "{}")
local mailId = request.mail_id
if not mailId then error("mail_id required") end
local stateObjs = nk.storage_read({{ collection = "inbox", key = "state", user_id = context.user_id }})
local state = { claimed_ids = {}, deleted_ids = {}, read_ids = {} }
if #stateObjs > 0 then
local val = stateObjs[1].value
state.claimed_ids = val.claimed_ids or {}
state.deleted_ids = val.deleted_ids or {}
state.read_ids = val.read_ids or {}
end
local function array_contains(arr, val)
for _, v in ipairs(arr) do if v == val then return true end end
return false
end
if not array_contains(state.deleted_ids, mailId) then table.insert(state.deleted_ids, mailId) end
if not array_contains(state.read_ids, mailId) then table.insert(state.read_ids, mailId) end
nk.storage_write({{
collection = "inbox",
key = "state",
user_id = context.user_id,
value = state,
permission_read = 1,
permission_write = 0
}})
return nk.json_encode({ success = true, deleted_ids = state.deleted_ids })
end
function inbox.rpc_save_mail_state(context, payload)
if not context.user_id then error("Not authenticated") end
local request = nk.json_decode(payload or "{}")
local stateObjs = nk.storage_read({{ collection = "inbox", key = "state", user_id = context.user_id }})
local state = { claimed_ids = {}, deleted_ids = {}, read_ids = {} }
if #stateObjs > 0 then
local val = stateObjs[1].value
state.claimed_ids = val.claimed_ids or {}
state.deleted_ids = val.deleted_ids or {}
state.read_ids = val.read_ids or {}
end
local function array_contains(arr, val)
for _, v in ipairs(arr) do if v == val then return true end end
return false
end
local newReadIds = request.read_ids or {}
for _, rid in ipairs(newReadIds) do
if not array_contains(state.read_ids, rid) then
table.insert(state.read_ids, rid)
end
end
nk.storage_write({{
collection = "inbox",
key = "state",
user_id = context.user_id,
value = state,
permission_read = 1,
permission_write = 0
}})
return nk.json_encode({ success = true })
end
function inbox.rpc_admin_list_mail(context, payload)
utils.require_admin(context)
local globalObjs = nk.storage_read({{ collection = "config", key = "global_mail", user_id = "00000000-0000-0000-0000-000000000000" }})
local globalMails = (#globalObjs > 0) and (globalObjs[1].value.mails or {}) or {}
for _, m in ipairs(globalMails) do m.type = "global" end
local personalMails = {}
local cursor = nil
repeat
local status, listResult = pcall(nk.storage_list, "", "inbox", 100, cursor)
if status and listResult then
local objects = listResult.objects or {}
for _, obj in ipairs(objects) do
if obj.key == "personal" then
local ownerUserId = obj.user_id
local mails = obj.value.mails or {}
for _, m in ipairs(mails) do
m.type = "personal"
m.target_user_id = ownerUserId
table.insert(personalMails, m)
end
end
end
cursor = listResult.cursor
else
cursor = nil
end
until not cursor or cursor == ""
local allMails = {}
for _, m in ipairs(globalMails) do table.insert(allMails, m) end
for _, m in ipairs(personalMails) do table.insert(allMails, m) end
table.sort(allMails, function(a, b)
local d1 = a.date or ""
local d2 = b.date or ""
return d1 > d2
end)
return nk.json_encode({ mails = allMails })
end
function inbox.rpc_admin_update_mail(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload or "{}")
local mailId = request.mail_id
if not mailId then error("mail_id required") end
local isGlobal = request.type ~= "personal"
local targetUserId = request.target_user_id or ""
local newTargetUserId = request.new_target_user_id
local hasNewTarget = (newTargetUserId ~= nil)
local mailObj = nil
if isGlobal then
local globalObjs = nk.storage_read({{ collection = "config", key = "global_mail", user_id = "00000000-0000-0000-0000-000000000000" }})
local globalMails = (#globalObjs > 0) and (globalObjs[1].value.mails or {}) or {}
for i, m in ipairs(globalMails) do
if m.id == mailId then
mailObj = table.remove(globalMails, i)
break
end
end
if not mailObj then error("Mail not found in global") end
nk.storage_write({{
collection = "config",
key = "global_mail",
user_id = "00000000-0000-0000-0000-000000000000",
value = { mails = globalMails },
permission_read = 2,
permission_write = 0
}})
else
if targetUserId == "" then error("target_user_id required for personal mail") end
local pObjs = nk.storage_read({{ collection = "inbox", key = "personal", user_id = targetUserId }})
local personalMails = (#pObjs > 0) and (pObjs[1].value.mails or {}) or {}
for i, m in ipairs(personalMails) do
if m.id == mailId then
mailObj = table.remove(personalMails, i)
break
end
end
if not mailObj then error("Mail not found in personal inbox") end
nk.storage_write({{
collection = "inbox",
key = "personal",
user_id = targetUserId,
value = { mails = personalMails },
permission_read = 1,
permission_write = 0
}})
end
if request.title ~= nil then mailObj.title = request.title end
if request.content ~= nil then mailObj.content = request.content end
if request.end_date ~= nil then mailObj.end_date = request.end_date end
if request.expiry_date ~= nil then mailObj.expiry_date = request.expiry_date end
local destUserId = ""
if hasNewTarget then destUserId = newTargetUserId else
if not isGlobal then destUserId = targetUserId end
end
if destUserId == "" then
mailObj.type = "global"
local gObjs = nk.storage_read({{ collection = "config", key = "global_mail", user_id = "00000000-0000-0000-0000-000000000000" }})
local gMails = (#gObjs > 0) and (gObjs[1].value.mails or {}) or {}
table.insert(gMails, mailObj)
nk.storage_write({{
collection = "config",
key = "global_mail",
user_id = "00000000-0000-0000-0000-000000000000",
value = { mails = gMails },
permission_read = 2,
permission_write = 0
}})
else
mailObj.type = "personal"
local dObjs = nk.storage_read({{ collection = "inbox", key = "personal", user_id = destUserId }})
local dMails = (#dObjs > 0) and (dObjs[1].value.mails or {}) or {}
table.insert(dMails, mailObj)
nk.storage_write({{
collection = "inbox",
key = "personal",
user_id = destUserId,
value = { mails = dMails },
permission_read = 1,
permission_write = 0
}})
end
nk.logger_info("Admin updated mail " .. mailId .. " by " .. context.user_id)
return nk.json_encode({ success = true })
end
function inbox.rpc_admin_delete_mail_server(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload or "{}")
local mailId = request.mail_id
if not mailId then error("mail_id required") end
local isGlobal = request.type ~= "personal"
local targetUserId = request.target_user_id or ""
if isGlobal then
local globalObjs = nk.storage_read({{ collection = "config", key = "global_mail", user_id = "00000000-0000-0000-0000-000000000000" }})
local globalMails = (#globalObjs > 0) and (globalObjs[1].value.mails or {}) or {}
local before = #globalMails
local filtered = {}
for _, m in ipairs(globalMails) do
if m.id ~= mailId then table.insert(filtered, m) end
end
if #filtered == before then error("Mail not found") end
nk.storage_write({{
collection = "config",
key = "global_mail",
user_id = "00000000-0000-0000-0000-000000000000",
value = { mails = filtered },
permission_read = 2,
permission_write = 0
}})
else
if targetUserId == "" then error("target_user_id required for personal mail") end
local pObjs = nk.storage_read({{ collection = "inbox", key = "personal", user_id = targetUserId }})
local personalMails = (#pObjs > 0) and (pObjs[1].value.mails or {}) or {}
local before = #personalMails
local filtered = {}
for _, m in ipairs(personalMails) do
if m.id ~= mailId then table.insert(filtered, m) end
end
if #filtered == before then error("Mail not found") end
nk.storage_write({{
collection = "inbox",
key = "personal",
user_id = targetUserId,
value = { mails = filtered },
permission_read = 1,
permission_write = 0
}})
end
nk.logger_info("Admin deleted mail " .. mailId .. " from server by " .. context.user_id)
return nk.json_encode({ success = true })
end
nk.register_rpc(inbox.rpc_admin_send_mail, "admin_send_mail")
nk.register_rpc(inbox.rpc_get_mail, "get_mail")
nk.register_rpc(inbox.rpc_claim_mail_reward, "claim_mail_reward")
nk.register_rpc(inbox.rpc_delete_mail, "delete_mail")
nk.register_rpc(inbox.rpc_save_mail_state, "save_mail_state")
nk.register_rpc(inbox.rpc_admin_list_mail, "admin_list_mail")
nk.register_rpc(inbox.rpc_admin_update_mail, "admin_update_mail")
nk.register_rpc(inbox.rpc_admin_delete_mail_server, "admin_delete_mail_server")
nk.logger_info("LUA TEST: inbox module loaded")
return inbox