new games snapshot
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
const {
|
||||
MessageFlags
|
||||
} = require("discord.js");
|
||||
|
||||
const { mClient } = require("../index");
|
||||
|
||||
|
||||
const DATABASE = "neo-db";
|
||||
|
||||
const COOKIE_COLLECTION = "items_cookies";
|
||||
const COOLDOWN_COLLECTION = "cooldowns_cookies";
|
||||
|
||||
|
||||
function getCollections() {
|
||||
const db = mClient.db(DATABASE);
|
||||
|
||||
return {
|
||||
db,
|
||||
cookies: db.collection(COOKIE_COLLECTION),
|
||||
cooldowns: db.collection(COOLDOWN_COLLECTION)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async function getCookieBalance(userId) {
|
||||
const { cookies } = getCollections();
|
||||
|
||||
const result = await cookies.findOne(
|
||||
{
|
||||
userId
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
_id: 0,
|
||||
cookies: 1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return result?.cookies ?? 0;
|
||||
}
|
||||
|
||||
|
||||
async function changeCookies(
|
||||
userId,
|
||||
amount
|
||||
) {
|
||||
const { cookies } = getCollections();
|
||||
|
||||
return cookies.updateOne(
|
||||
{
|
||||
userId
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
cookies: amount
|
||||
},
|
||||
|
||||
$setOnInsert: {
|
||||
userId
|
||||
}
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function transferCookies(
|
||||
fromUserId,
|
||||
toUserId,
|
||||
amount
|
||||
) {
|
||||
if (
|
||||
!Number.isInteger(amount) ||
|
||||
amount <= 0
|
||||
) {
|
||||
throw new Error(
|
||||
"INVALID_COOKIE_AMOUNT"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const { cookies } = getCollections();
|
||||
|
||||
|
||||
const debit =
|
||||
await cookies.updateOne(
|
||||
{
|
||||
userId: fromUserId,
|
||||
|
||||
cookies: {
|
||||
$gte: amount
|
||||
}
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
cookies: -amount
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
debit.modifiedCount !== 1
|
||||
) {
|
||||
throw new Error(
|
||||
"INSUFFICIENT_COOKIES"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await cookies.updateOne(
|
||||
{
|
||||
userId: toUserId
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
cookies: amount
|
||||
},
|
||||
|
||||
$setOnInsert: {
|
||||
userId: toUserId
|
||||
}
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
try {
|
||||
await cookies.updateOne(
|
||||
{
|
||||
userId: fromUserId
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
cookies: amount
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} catch (rollbackError) {
|
||||
console.error(
|
||||
"CRITICAL: Cookie rollback failed:",
|
||||
rollbackError
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function getCooldown(
|
||||
userId,
|
||||
guildId,
|
||||
type
|
||||
) {
|
||||
const { cooldowns } = getCollections();
|
||||
|
||||
const result =
|
||||
await cooldowns.findOne(
|
||||
{
|
||||
userId,
|
||||
guildId
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
_id: 0,
|
||||
[type]: 1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return result?.[type] ?? 0;
|
||||
}
|
||||
|
||||
|
||||
async function setCooldown(
|
||||
userId,
|
||||
guildId,
|
||||
type,
|
||||
duration
|
||||
) {
|
||||
const { cooldowns } = getCollections();
|
||||
|
||||
const now = getUnixTime();
|
||||
const next = now + duration;
|
||||
|
||||
|
||||
await cooldowns.updateOne(
|
||||
{
|
||||
userId,
|
||||
guildId
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
[type]: next
|
||||
},
|
||||
|
||||
$setOnInsert: {
|
||||
userId,
|
||||
guildId
|
||||
}
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Atomically claim a cooldown.
|
||||
*
|
||||
* Useful for button games where a double-click
|
||||
* could otherwise resolve twice.
|
||||
*/
|
||||
|
||||
async function claimCooldown(
|
||||
userId,
|
||||
guildId,
|
||||
type,
|
||||
duration
|
||||
) {
|
||||
const { cooldowns } = getCollections();
|
||||
|
||||
const now = getUnixTime();
|
||||
const next = now + duration;
|
||||
|
||||
|
||||
try {
|
||||
const result =
|
||||
await cooldowns.updateOne(
|
||||
{
|
||||
userId,
|
||||
guildId,
|
||||
|
||||
$or: [
|
||||
{
|
||||
[type]: {
|
||||
$lte: now
|
||||
}
|
||||
},
|
||||
{
|
||||
[type]: {
|
||||
$exists: false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
[type]: next
|
||||
},
|
||||
|
||||
$setOnInsert: {
|
||||
userId,
|
||||
guildId
|
||||
}
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return {
|
||||
claimed:
|
||||
result.modifiedCount === 1 ||
|
||||
result.upsertedCount === 1,
|
||||
|
||||
next
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
/*
|
||||
* A unique {userId, guildId} index may cause
|
||||
* a duplicate-key error if an active cooldown
|
||||
* already exists.
|
||||
*/
|
||||
if (error?.code === 11000) {
|
||||
const active =
|
||||
await getCooldown(
|
||||
userId,
|
||||
guildId,
|
||||
type
|
||||
);
|
||||
|
||||
return {
|
||||
claimed: false,
|
||||
next: active
|
||||
};
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function temporaryEphemeralReply(
|
||||
interaction,
|
||||
options,
|
||||
deleteAfter = 5000
|
||||
) {
|
||||
await interaction.reply({
|
||||
...options,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
|
||||
setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
await interaction.deleteReply();
|
||||
} catch {}
|
||||
},
|
||||
deleteAfter
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function temporaryEphemeralFollowUp(
|
||||
interaction,
|
||||
options,
|
||||
deleteAfter = 8000
|
||||
) {
|
||||
const message =
|
||||
await interaction.followUp({
|
||||
...options,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
|
||||
setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
await message.delete();
|
||||
} catch {}
|
||||
},
|
||||
deleteAfter
|
||||
);
|
||||
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
function getUnixTime() {
|
||||
return Math.floor(
|
||||
Date.now() / 1000
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function randomInteger(
|
||||
min,
|
||||
max
|
||||
) {
|
||||
return Math.floor(
|
||||
Math.random() *
|
||||
(max - min + 1)
|
||||
) + min;
|
||||
}
|
||||
|
||||
|
||||
function randomFloat(
|
||||
min,
|
||||
max
|
||||
) {
|
||||
return (
|
||||
Math.random() *
|
||||
(max - min)
|
||||
) + min;
|
||||
}
|
||||
|
||||
|
||||
function randomItem(items) {
|
||||
return items[
|
||||
Math.floor(
|
||||
Math.random() *
|
||||
items.length
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function formatPercentage(value) {
|
||||
return `${Math.round(value * 100)}%`;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
DATABASE,
|
||||
COOKIE_COLLECTION,
|
||||
COOLDOWN_COLLECTION,
|
||||
|
||||
getCollections,
|
||||
getCookieBalance,
|
||||
changeCookies,
|
||||
transferCookies,
|
||||
|
||||
getCooldown,
|
||||
setCooldown,
|
||||
claimCooldown,
|
||||
|
||||
temporaryEphemeralReply,
|
||||
temporaryEphemeralFollowUp,
|
||||
|
||||
getUnixTime,
|
||||
randomInteger,
|
||||
randomFloat,
|
||||
randomItem,
|
||||
formatPercentage
|
||||
};
|
||||
Reference in New Issue
Block a user