feat: 2.3.1
This commit is contained in:
@@ -19,6 +19,7 @@ function InitModule(ctx, logger, nk, initializer) {
|
||||
initializer.registerRpc("admin_list_users", rpcAdminListUsers);
|
||||
initializer.registerRpc("admin_delete_users", rpcAdminDeleteUsers);
|
||||
initializer.registerRpc("admin_topup_gold", rpcAdminTopupGold);
|
||||
initializer.registerRpc("admin_clear_global_chat", rpcAdminClearGlobalChat);
|
||||
|
||||
// User management RPCs
|
||||
initializer.registerRpc("get_user_profile", rpcGetUserProfile);
|
||||
@@ -49,6 +50,12 @@ function InitModule(ctx, logger, nk, initializer) {
|
||||
initializer.registerRpc("set_daily_reward_config", rpcSetDailyRewardConfig);
|
||||
initializer.registerRpc("get_daily_reward_config_admin", rpcGetDailyRewardConfigAdmin);
|
||||
|
||||
// Inbox System RPCs
|
||||
initializer.registerRpc("admin_send_mail", rpcAdminSendMail);
|
||||
initializer.registerRpc("get_mail", rpcGetMail);
|
||||
initializer.registerRpc("claim_mail_reward", rpcClaimMailReward);
|
||||
initializer.registerRpc("delete_mail", rpcDeleteMail);
|
||||
|
||||
// Steam auth hooks
|
||||
initializer.registerAfterAuthenticateSteam(afterAuthenticateSteam);
|
||||
|
||||
@@ -531,6 +538,53 @@ function rpcAdminTopupGold(ctx, logger, nk, payload) {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Admin Clear Global Chat RPC
|
||||
// =============================================================================
|
||||
|
||||
function rpcAdminClearGlobalChat(ctx, logger, nk, payload) {
|
||||
requireAdmin(ctx, nk);
|
||||
|
||||
// Nakama's channel message list uses the channel ID, not the room name.
|
||||
// We need to find the channel ID for "social_global" Room type.
|
||||
// Room channel IDs are deterministic: we can list messages by cursor.
|
||||
var req = JSON.parse(payload || "{}");
|
||||
var channelId = req.channel_id || "";
|
||||
|
||||
if (!channelId) {
|
||||
throw new Error("channel_id is required. Pass the channel ID from the client.");
|
||||
}
|
||||
|
||||
var deleted = 0;
|
||||
var cursor = "";
|
||||
|
||||
try {
|
||||
// Paginate through all messages and remove each one
|
||||
do {
|
||||
var result = nk.channelMessagesList(channelId, 100, false, cursor);
|
||||
var messages = result.messages || [];
|
||||
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
try {
|
||||
nk.channelMessageRemove(channelId, messages[i].messageId);
|
||||
deleted++;
|
||||
} catch (e2) {
|
||||
logger.warn("Failed to remove message " + messages[i].messageId + ": " + e2);
|
||||
}
|
||||
}
|
||||
|
||||
cursor = result.nextCursor || "";
|
||||
} while (cursor !== "");
|
||||
|
||||
logger.info("[AdminClearGlobalChat] Deleted " + deleted + " messages by " + ctx.userId);
|
||||
return JSON.stringify({ success: true, deleted: deleted });
|
||||
} catch (e) {
|
||||
logger.error("admin_clear_global_chat failed: " + e);
|
||||
throw new Error("Failed to clear global chat: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function rpcBuyCurrency(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
|
||||
@@ -1384,3 +1438,277 @@ function rpcSendLobbyInvite(ctx, logger, nk, payload) {
|
||||
logger.info("Lobby invite sent from " + ctx.userId + " to " + toUserId + " for match " + matchId);
|
||||
return JSON.stringify({ success: true });
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Inbox System RPCs
|
||||
// =============================================================================
|
||||
|
||||
function rpcAdminSendMail(ctx, logger, nk, payload) {
|
||||
requireAdmin(ctx, nk);
|
||||
var request = JSON.parse(payload || "{}");
|
||||
|
||||
var nowStr = new Date().toISOString();
|
||||
var startDate = request.start_date || nowStr;
|
||||
var endDate = request.end_date || "";
|
||||
|
||||
// Auto-delete / expire after 30 days from start
|
||||
var startObj = new Date(startDate);
|
||||
startObj.setDate(startObj.getDate() + 30);
|
||||
var expiryDate = startObj.toISOString();
|
||||
|
||||
var mailObj = {
|
||||
id: nk.uuidv4(),
|
||||
title: request.title || "Announcement",
|
||||
content: request.content || "",
|
||||
sender: "TEKTON DEV TEAM",
|
||||
date: startDate,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
expiry_date: expiryDate,
|
||||
rewards: request.rewards || []
|
||||
};
|
||||
|
||||
if (request.target_user_id) {
|
||||
mailObj.type = "personal";
|
||||
var invObjs = nk.storageRead([{ collection: "inbox", key: "personal", userId: request.target_user_id }]);
|
||||
var personalMails = [];
|
||||
if (invObjs && invObjs.length > 0) {
|
||||
personalMails = invObjs[0].value.mails || [];
|
||||
}
|
||||
personalMails.push(mailObj);
|
||||
nk.storageWrite([{
|
||||
collection: "inbox",
|
||||
key: "personal",
|
||||
userId: request.target_user_id,
|
||||
value: { mails: personalMails },
|
||||
permissionRead: 1,
|
||||
permissionWrite: 0
|
||||
}]);
|
||||
logger.info("Personal mail sent to " + request.target_user_id);
|
||||
} else {
|
||||
mailObj.type = "global";
|
||||
var globalObjs = nk.storageRead([{ collection: "config", key: "global_mail", userId: "00000000-0000-0000-0000-000000000000" }]);
|
||||
var globalMails = [];
|
||||
if (globalObjs && globalObjs.length > 0) {
|
||||
globalMails = globalObjs[0].value.mails || [];
|
||||
}
|
||||
globalMails.push(mailObj);
|
||||
nk.storageWrite([{
|
||||
collection: "config",
|
||||
key: "global_mail",
|
||||
userId: "00000000-0000-0000-0000-000000000000",
|
||||
value: { mails: globalMails },
|
||||
permissionRead: 2,
|
||||
permissionWrite: 0
|
||||
}]);
|
||||
logger.info("Global mail sent");
|
||||
}
|
||||
|
||||
return JSON.stringify({ success: true, mail: mailObj });
|
||||
}
|
||||
|
||||
function rpcGetMail(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
|
||||
var personalObjs = nk.storageRead([{ collection: "inbox", key: "personal", userId: ctx.userId }]);
|
||||
var globalObjs = nk.storageRead([{ collection: "config", key: "global_mail", userId: "00000000-0000-0000-0000-000000000000" }]);
|
||||
var stateObjs = nk.storageRead([{ collection: "inbox", key: "state", userId: ctx.userId }]);
|
||||
|
||||
var personalMails = (personalObjs && personalObjs.length > 0) ? (personalObjs[0].value.mails || []) : [];
|
||||
var globalMails = (globalObjs && globalObjs.length > 0) ? (globalObjs[0].value.mails || []) : [];
|
||||
|
||||
var state = { claimed_ids: [], deleted_ids: [], read_ids: [] };
|
||||
if (stateObjs && stateObjs.length > 0) {
|
||||
var val = stateObjs[0].value;
|
||||
state.claimed_ids = val.claimed_ids || [];
|
||||
state.deleted_ids = val.deleted_ids || [];
|
||||
state.read_ids = val.read_ids || [];
|
||||
}
|
||||
|
||||
var allMails = personalMails.concat(globalMails);
|
||||
var filteredMails = [];
|
||||
var nowStr = new Date().toISOString();
|
||||
|
||||
for (var i = 0; i < allMails.length; i++) {
|
||||
var mail = allMails[i];
|
||||
if (state.deleted_ids.indexOf(mail.id) !== -1) continue;
|
||||
|
||||
// Expiry check
|
||||
if (mail.expiry_date && nowStr > mail.expiry_date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scheduled start
|
||||
if (mail.start_date && nowStr < mail.start_date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scheduled end
|
||||
if (mail.type === "global" && mail.end_date && nowStr > mail.end_date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredMails.push(mail);
|
||||
}
|
||||
|
||||
return JSON.stringify({ mails: filteredMails, state: state });
|
||||
}
|
||||
|
||||
function rpcClaimMailReward(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
var request = JSON.parse(payload || "{}");
|
||||
var mailId = request.mail_id;
|
||||
if (!mailId) throw new Error("mail_id required");
|
||||
|
||||
// fetch all mails to find it
|
||||
var personalObjs = nk.storageRead([{ collection: "inbox", key: "personal", userId: ctx.userId }]);
|
||||
var globalObjs = nk.storageRead([{ collection: "config", key: "global_mail", userId: "00000000-0000-0000-0000-000000000000" }]);
|
||||
var stateObjs = nk.storageRead([{ collection: "inbox", key: "state", userId: ctx.userId }]);
|
||||
|
||||
var state = { claimed_ids: [], deleted_ids: [], read_ids: [] };
|
||||
if (stateObjs && stateObjs.length > 0) {
|
||||
var val = stateObjs[0].value;
|
||||
state.claimed_ids = val.claimed_ids || [];
|
||||
state.deleted_ids = val.deleted_ids || [];
|
||||
state.read_ids = val.read_ids || [];
|
||||
}
|
||||
|
||||
if (state.claimed_ids.indexOf(mailId) !== -1) {
|
||||
throw new Error("Reward already claimed");
|
||||
}
|
||||
|
||||
var personalMails = (personalObjs && personalObjs.length > 0) ? (personalObjs[0].value.mails || []) : [];
|
||||
var globalMails = (globalObjs && globalObjs.length > 0) ? (globalObjs[0].value.mails || []) : [];
|
||||
var allMails = personalMails.concat(globalMails);
|
||||
|
||||
var targetMail = null;
|
||||
for (var i = 0; i < allMails.length; i++) {
|
||||
if (allMails[i].id === mailId) {
|
||||
targetMail = allMails[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetMail) throw new Error("Mail not found");
|
||||
|
||||
var rewards = targetMail.rewards || [];
|
||||
var starTotal = 0;
|
||||
var goldTotal = 0;
|
||||
|
||||
// Support legacy dictionary if it exists
|
||||
if (!Array.isArray(rewards)) {
|
||||
starTotal = rewards.star || 0;
|
||||
goldTotal = rewards.gold || 0;
|
||||
rewards = []; // prevent array loop
|
||||
}
|
||||
|
||||
var fragsToUpdate = {};
|
||||
var skinsToAdd = [];
|
||||
|
||||
for (var j = 0; j < rewards.length; j++) {
|
||||
var r = rewards[j];
|
||||
var type = r.type || "star";
|
||||
var amount = r.amount || 0;
|
||||
|
||||
if (type === "star") starTotal += amount;
|
||||
else if (type === "gold") goldTotal += amount;
|
||||
else if (type.startsWith("frag_") || type === "item") {
|
||||
var fragId = r.id || type;
|
||||
fragsToUpdate[fragId] = (fragsToUpdate[fragId] || 0) + amount;
|
||||
}
|
||||
else if (type === "skin") {
|
||||
if (r.id) skinsToAdd.push(r.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (starTotal > 0 || goldTotal > 0) {
|
||||
var changes = {};
|
||||
if (starTotal > 0) changes["star"] = starTotal;
|
||||
if (goldTotal > 0) changes["gold"] = goldTotal;
|
||||
nk.walletUpdate(ctx.userId, changes, {}, true);
|
||||
}
|
||||
|
||||
if (Object.keys(fragsToUpdate).length > 0) {
|
||||
var invObjs = nk.storageRead([{ collection: "inventory", key: "fragments", userId: ctx.userId }]);
|
||||
var frags = {};
|
||||
if (invObjs && invObjs.length > 0) {
|
||||
frags = invObjs[0].value;
|
||||
}
|
||||
for (var fId in fragsToUpdate) {
|
||||
frags[fId] = (frags[fId] || 0) + fragsToUpdate[fId];
|
||||
}
|
||||
nk.storageWrite([{
|
||||
collection: "inventory",
|
||||
key: "fragments",
|
||||
userId: ctx.userId,
|
||||
value: frags,
|
||||
permissionRead: 1,
|
||||
permissionWrite: 0
|
||||
}]);
|
||||
}
|
||||
|
||||
if (skinsToAdd.length > 0) {
|
||||
var skinWrites = [];
|
||||
for (var s = 0; s < skinsToAdd.length; s++) {
|
||||
skinWrites.push({
|
||||
collection: "inventory",
|
||||
key: skinsToAdd[s],
|
||||
userId: ctx.userId,
|
||||
value: { acquired_via: "mail", purchased_at: new Date().toISOString() },
|
||||
permissionRead: 1,
|
||||
permissionWrite: 0
|
||||
});
|
||||
}
|
||||
nk.storageWrite(skinWrites);
|
||||
}
|
||||
|
||||
state.claimed_ids.push(mailId);
|
||||
if (state.read_ids.indexOf(mailId) === -1) {
|
||||
state.read_ids.push(mailId);
|
||||
}
|
||||
|
||||
nk.storageWrite([{
|
||||
collection: "inbox",
|
||||
key: "state",
|
||||
userId: ctx.userId,
|
||||
value: state,
|
||||
permissionRead: 1,
|
||||
permissionWrite: 0
|
||||
}]);
|
||||
|
||||
return JSON.stringify({ success: true, claimed_ids: state.claimed_ids });
|
||||
}
|
||||
|
||||
function rpcDeleteMail(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
var request = JSON.parse(payload || "{}");
|
||||
var mailId = request.mail_id;
|
||||
if (!mailId) throw new Error("mail_id required");
|
||||
|
||||
var stateObjs = nk.storageRead([{ collection: "inbox", key: "state", userId: ctx.userId }]);
|
||||
var state = { claimed_ids: [], deleted_ids: [], read_ids: [] };
|
||||
if (stateObjs && stateObjs.length > 0) {
|
||||
var val = stateObjs[0].value;
|
||||
state.claimed_ids = val.claimed_ids || [];
|
||||
state.deleted_ids = val.deleted_ids || [];
|
||||
state.read_ids = val.read_ids || [];
|
||||
}
|
||||
|
||||
if (state.deleted_ids.indexOf(mailId) === -1) {
|
||||
state.deleted_ids.push(mailId);
|
||||
}
|
||||
if (state.read_ids.indexOf(mailId) === -1) {
|
||||
state.read_ids.push(mailId);
|
||||
}
|
||||
|
||||
nk.storageWrite([{
|
||||
collection: "inbox",
|
||||
key: "state",
|
||||
userId: ctx.userId,
|
||||
value: state,
|
||||
permissionRead: 1,
|
||||
permissionWrite: 0
|
||||
}]);
|
||||
|
||||
return JSON.stringify({ success: true, deleted_ids: state.deleted_ids });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user