Compare commits
14 Commits
bb90ca792c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 878e8fcdaa | |||
| 57d3ca6929 | |||
| f1335162aa | |||
| 4727434362 | |||
| 6b2fab8b95 | |||
| 8d7e47af3f | |||
| a94562c40b | |||
| f269e15a76 | |||
| 0f5ada6c0f | |||
| 0813beb2c0 | |||
| d749f8f16b | |||
| 346cf6d863 | |||
| 11102fc3a5 | |||
| 7bd44bd653 |
@@ -9,16 +9,16 @@ Arthonor-Neo is a custom Discord bot designed for community interaction and fun.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Core Interactions
|
### Core Interactions
|
||||||
- Nut Economy – Earn and spend nuts in server activities
|
- Nut Economy - Earn and spend nuts in server activities
|
||||||
- Honor System – Show recognition and respect to users
|
- Honor System - Show recognition and respect to users
|
||||||
- Quotes – Save and retrieve memorable quotes
|
- Quotes - Save and retrieve memorable quotes
|
||||||
|
|
||||||
### Community & Events
|
### Community & Events
|
||||||
- Automatic Welcome Messages – Greet new members
|
- Automatic Welcome Messages - Greet new members
|
||||||
- Birthday System – Track and celebrate birthdays
|
- Birthday System - Track and celebrate birthdays
|
||||||
- Automatic Birthday Cards – Personalized greetings on birthdays
|
- Automatic Birthday Cards - Personalized greetings on birthdays
|
||||||
- Wednesday Frog – Midweek server event
|
- Wednesday Frog - Midweek server event
|
||||||
- Friday Reminder (Coming Soon)
|
- WGE Core - With Periodic Reminders and Random Function
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -32,11 +32,11 @@ These have been mostly removed with certain exceptions:
|
|||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Timers
|
- Timers
|
||||||
- WGE
|
|
||||||
- Weather commands
|
- Weather commands
|
||||||
- GIF commands
|
- GIF commands
|
||||||
- Certain developer tools
|
- Certain developer tools
|
||||||
- WOKCommands Core
|
- WOKCommands Core
|
||||||
|
- Friday Reminder
|
||||||
|
|
||||||
### Upgraded to Slash Commands
|
### Upgraded to Slash Commands
|
||||||
- Nuts
|
- Nuts
|
||||||
@@ -46,6 +46,7 @@ These have been mostly removed with certain exceptions:
|
|||||||
- Role Selection
|
- Role Selection
|
||||||
- Channel Selection
|
- Channel Selection
|
||||||
- Meme Commands
|
- Meme Commands
|
||||||
|
- WGE (NEW)
|
||||||
|
|
||||||
### New and Improved
|
### New and Improved
|
||||||
- Interactive Honor Menu
|
- Interactive Honor Menu
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Honors Leaderboard')
|
.setTitle('Honors Leaderboard')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Honors Leaderboard')
|
.setTitle('Honors Leaderboard')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Honors Leaderboard')
|
.setTitle('Honors Leaderboard')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Nuts Leaderboard')
|
.setTitle('Nuts Leaderboard')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Nuts Leaderboard')
|
.setTitle('Nuts Leaderboard')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Nuts Leaderboard')
|
.setTitle('Nuts Leaderboard')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -50,12 +50,15 @@ module.exports = {
|
|||||||
{ $inc: { [`stat.${amount}`]: 1 } },
|
{ $inc: { [`stat.${amount}`]: 1 } },
|
||||||
{ upsert: true }
|
{ upsert: true }
|
||||||
)
|
)
|
||||||
let content = `Du hast ${amount} Nüsse bekommen! :chestnut:`
|
// Helper for singular/plural
|
||||||
if (!amount) {
|
const formatNut = (amount) => `${amount === 1 ? 'eine' : amount} ${amount === 1 ? 'Nuss' : 'Nüsse'}`
|
||||||
content = `Du hast leider keine Nüsse bekommen :(`
|
|
||||||
|
let nutText = `Du hast ${formatNut(amount)} bekommen! :chestnut:`
|
||||||
|
if (amount <= 0) {
|
||||||
|
nutText = `Du hast keine Nuss bekommen! :yoink_help:`
|
||||||
}
|
}
|
||||||
return await message.reply({
|
return await message.reply({
|
||||||
content: content
|
content: nutText
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Nuts Leaderboard')
|
.setTitle('Nuts Leaderboard')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(message.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const { SlashCommandBuilder, EmbedBuilder, MessageFlags } = require('discord.js')
|
const { SlashCommandBuilder, EmbedBuilder, MessageFlags, PermissionFlagsBits } = require('discord.js')
|
||||||
const { mClient } = require('../../../index')
|
const { mClient } = require('../../../index')
|
||||||
const { configDotenv } = require('dotenv')
|
const { configDotenv } = require('dotenv')
|
||||||
configDotenv()
|
configDotenv()
|
||||||
@@ -144,6 +144,7 @@ module.exports = {
|
|||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('birthday')
|
.setName('birthday')
|
||||||
.setDescription('manage birthdays')
|
.setDescription('manage birthdays')
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles)
|
||||||
.addSubcommand(s =>
|
.addSubcommand(s =>
|
||||||
s
|
s
|
||||||
.setName('set')
|
.setName('set')
|
||||||
@@ -185,21 +186,9 @@ module.exports = {
|
|||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
switch (interaction.options._subcommand) {
|
switch (interaction.options._subcommand) {
|
||||||
case 'set':
|
case 'set':
|
||||||
if (!interaction.member.permissions.has("ADMINISTRATOR")) {
|
|
||||||
return await interaction.reply({
|
|
||||||
content: "Unprivileged Access!",
|
|
||||||
flags: MessageFlags.Ephemeral
|
|
||||||
})
|
|
||||||
}
|
|
||||||
birthdaySet(interaction)
|
birthdaySet(interaction)
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
if (!interaction.member.permissions.has("ADMINISTRATOR")) {
|
|
||||||
return await interaction.reply({
|
|
||||||
content: "Unprivileged Access!",
|
|
||||||
flags: MessageFlags.Ephemeral
|
|
||||||
})
|
|
||||||
}
|
|
||||||
birthdayDelete(interaction)
|
birthdayDelete(interaction)
|
||||||
break;
|
break;
|
||||||
case 'get':
|
case 'get':
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const { SlashCommandBuilder, Events, MessageFlags } = require('discord.js')
|
const { SlashCommandBuilder, Events, MessageFlags, PermissionFlagsBits } = require('discord.js')
|
||||||
const { mClient } = require('../../../index')
|
const { mClient } = require('../../../index')
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
|
|
||||||
@@ -80,6 +80,7 @@ module.exports = {
|
|||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('config')
|
.setName('config')
|
||||||
.setDescription('configurations for admins')
|
.setDescription('configurations for admins')
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels)
|
||||||
.addSubcommandGroup(sg =>
|
.addSubcommandGroup(sg =>
|
||||||
sg
|
sg
|
||||||
.setName('channels')
|
.setName('channels')
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
const {
|
||||||
|
SlashCommandBuilder,
|
||||||
|
EmbedBuilder,
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
MessageFlags
|
||||||
|
} = require('discord.js');
|
||||||
|
const { mClient } = require('../../..');
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
const db = mClient.db(process.env.M_DB); // adjust if needed
|
||||||
|
const coll = db.collection('items_daily_numbers');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('ezd')
|
||||||
|
.setDescription('Ersti Zweiti Dritti')
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('show')
|
||||||
|
.setDescription('Zeigt die täglichen Erster/Zweiter/Dritter Ergebnisse')
|
||||||
|
)
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('leaderboard')
|
||||||
|
.setDescription('Zeigt die Rangliste der EZDler')
|
||||||
|
)
|
||||||
|
,
|
||||||
|
async execute(interaction, client) {
|
||||||
|
switch (interaction.options._subcommand) {
|
||||||
|
case 'show':
|
||||||
|
ezdShow(interaction)
|
||||||
|
break;
|
||||||
|
case 'leaderboard':
|
||||||
|
ezdLeaderboard(interaction)
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// helper: shift YYYY-MM-DD safely
|
||||||
|
function shiftDate(dateStr, offset) {
|
||||||
|
const d = new Date(dateStr);
|
||||||
|
d.setDate(d.getDate() + offset);
|
||||||
|
return d.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ezdShow(interaction) {
|
||||||
|
const today = new Date().toLocaleDateString("en-CA", {
|
||||||
|
timeZone: "Europe/Berlin"
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentDate = today;
|
||||||
|
|
||||||
|
const sessionId = `${interaction.user.id}-${Date.now()}`;
|
||||||
|
|
||||||
|
const buildEmbed = (doc, date) => {
|
||||||
|
const p = doc?.positions || {};
|
||||||
|
|
||||||
|
const line = (emoji, label, key) => {
|
||||||
|
const v = p[key];
|
||||||
|
return `${emoji} **${label}:** ${v ? `<@${v.userId}>` : "—"}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const germanDate = new Date(`${date}T00:00:00`).toLocaleDateString("de-DE", {
|
||||||
|
timeZone: "Europe/Berlin",
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric"
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setTitle(`📊 EZD Übersicht – ${germanDate}`)
|
||||||
|
.setColor(0x2b2d31)
|
||||||
|
.setDescription([
|
||||||
|
line("🥇", "Ersti", "Ersti"),
|
||||||
|
line("🥈", "Zweiti", "Zweiti"),
|
||||||
|
line("🥉", "Dritti", "Dritti")
|
||||||
|
].join("\n"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildButtons = (date) => {
|
||||||
|
return new ActionRowBuilder().addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`ezd_prev`)
|
||||||
|
.setLabel("⬅️")
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`ezd_next`)
|
||||||
|
.setLabel("➡️")
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const doc = await coll.findOne({ date: currentDate });
|
||||||
|
|
||||||
|
const msg = await interaction.reply({
|
||||||
|
embeds: [buildEmbed(doc, currentDate)],
|
||||||
|
components: [buildButtons(currentDate)]
|
||||||
|
});
|
||||||
|
|
||||||
|
const collector = msg.createMessageComponentCollector({
|
||||||
|
time: 1000 * 60 * 10 // 10 minutes
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on("collect", async (btn) => {
|
||||||
|
if (btn.user.id !== interaction.user.id) {
|
||||||
|
return btn.reply({
|
||||||
|
content: "❌ Diese Buttons gehören nicht dir.",
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn.customId === `ezd_prev`) {
|
||||||
|
currentDate = shiftDate(currentDate, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn.customId === `ezd_next`) {
|
||||||
|
currentDate = shiftDate(currentDate, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDoc = await coll.findOne({ date: currentDate });
|
||||||
|
|
||||||
|
await btn.update({
|
||||||
|
embeds: [buildEmbed(newDoc, currentDate)],
|
||||||
|
components: [buildButtons(currentDate)]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ezdLeaderboard(interaction) {
|
||||||
|
const docs = await coll.find({}).toArray();
|
||||||
|
|
||||||
|
const leaderboard = new Map();
|
||||||
|
|
||||||
|
const add = (entry, place) => {
|
||||||
|
if (!entry?.userId) return;
|
||||||
|
|
||||||
|
if (!leaderboard.has(entry.userId)) {
|
||||||
|
leaderboard.set(entry.userId, {
|
||||||
|
userId: entry.userId,
|
||||||
|
points: 0,
|
||||||
|
Ersti: 0,
|
||||||
|
Zweiti: 0,
|
||||||
|
Dritti: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = leaderboard.get(entry.userId);
|
||||||
|
|
||||||
|
user[place]++;
|
||||||
|
|
||||||
|
if (place === "Ersti") user.points += 3;
|
||||||
|
if (place === "Zweiti") user.points += 2;
|
||||||
|
if (place === "Dritti") user.points += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const doc of docs) {
|
||||||
|
const p = doc.positions || {};
|
||||||
|
|
||||||
|
add(p.Ersti, "Ersti");
|
||||||
|
add(p.Zweiti, "Zweiti");
|
||||||
|
add(p.Dritti, "Dritti");
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = [...leaderboard.values()].sort((a, b) => {
|
||||||
|
if (b.points !== a.points) return b.points - a.points;
|
||||||
|
if (b.Ersti !== a.Ersti) return b.Ersti - a.Ersti;
|
||||||
|
if (b.Zweiti !== a.Zweiti) return b.Zweiti - a.Zweiti;
|
||||||
|
return b.Dritti - a.Dritti;
|
||||||
|
});
|
||||||
|
|
||||||
|
const PAGE_SIZE = 5;
|
||||||
|
const pageCount = Math.max(1, Math.ceil(users.length / PAGE_SIZE));
|
||||||
|
let page = 0;
|
||||||
|
|
||||||
|
const buildEmbed = () => {
|
||||||
|
const slice = users.slice(
|
||||||
|
page * PAGE_SIZE,
|
||||||
|
page * PAGE_SIZE + PAGE_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
|
const description = slice.length
|
||||||
|
? slice.map((u, i) => {
|
||||||
|
const rank = page * PAGE_SIZE + i + 1;
|
||||||
|
|
||||||
|
return `**#${rank}** <@${u.userId}> — **${u.points}**
|
||||||
|
🥇 ${u.Ersti} · 🥈 ${u.Zweiti} · 🥉 ${u.Dritti}`;
|
||||||
|
}).join("\n\n")
|
||||||
|
: "—";
|
||||||
|
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setTitle("🏆 EZD Rangliste")
|
||||||
|
.setColor(0x2b2d31)
|
||||||
|
.setDescription(description);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildButtons = () =>
|
||||||
|
new ActionRowBuilder().addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId("ezd_lb_prev")
|
||||||
|
.setLabel("⬅️")
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId("ezd_lb_page")
|
||||||
|
.setLabel(`${page + 1}/${pageCount}`)
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(true),
|
||||||
|
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId("ezd_lb_next")
|
||||||
|
.setLabel("➡️")
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
);
|
||||||
|
|
||||||
|
const msg = await interaction.reply({
|
||||||
|
embeds: [buildEmbed()],
|
||||||
|
components: [buildButtons()]
|
||||||
|
});
|
||||||
|
|
||||||
|
const collector = msg.createMessageComponentCollector({
|
||||||
|
time: 1000 * 60 * 10
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on("collect", async btn => {
|
||||||
|
if (btn.user.id !== interaction.user.id) {
|
||||||
|
return btn.reply({
|
||||||
|
content: "❌ Diese Buttons gehören nicht dir.",
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn.customId === "ezd_lb_prev")
|
||||||
|
page = (page - 1 + pageCount) % pageCount;
|
||||||
|
|
||||||
|
if (btn.customId === "ezd_lb_next")
|
||||||
|
page = (page + 1) % pageCount;
|
||||||
|
|
||||||
|
await btn.update({
|
||||||
|
embeds: [buildEmbed()],
|
||||||
|
components: [buildButtons()]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -103,7 +103,7 @@ async function honorLeaderboard(interaction) {
|
|||||||
})
|
})
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('- Honors Leaderboard -')
|
.setTitle('- Honors Leaderboard -')
|
||||||
.setThumbnail(guild.iconURL({ dynamic: true}))
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2') // Discord's blurple color
|
.setColor('#5865F2') // Discord's blurple color
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' });
|
.setFooter({ text: 'Use ◄ ► to navigate' });
|
||||||
|
|||||||
@@ -150,15 +150,14 @@ async function nutsLeaderboard(interaction) {
|
|||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Nuts Leaderboard')
|
.setTitle('Nuts Leaderboard')
|
||||||
.setThumbnail('attachment://nuts_main.png')
|
.setThumbnail(interaction.guild.iconURL({ dynamic: true}))
|
||||||
.setDescription(fields.toString())
|
.setDescription(fields.toString())
|
||||||
.setColor('#5865F2')
|
.setColor('#5865F2')
|
||||||
.setFooter({ text: 'Use ◄ ► to navigate' })
|
.setFooter({ text: 'Use ◄ ► to navigate' })
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [embed],
|
embeds: [embed],
|
||||||
components: [row],
|
components: [row]
|
||||||
files: [`${asset_path}nuts_main.png`]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +280,7 @@ async function nutsNut(interaction, quickMode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper for singular/plural
|
// Helper for singular/plural
|
||||||
const formatNut = (amount) => `${amount} ${amount === 1 ? 'Nuss' : 'Nüsse'}`
|
const formatNut = (amount) => `${amount === 1 ? 'eine' : amount} ${amount === 1 ? 'Nuss' : 'Nüsse'}`
|
||||||
|
|
||||||
// check cooldown
|
// check cooldown
|
||||||
if (cdData?.cooldown > Date.now() / 1000) {
|
if (cdData?.cooldown > Date.now() / 1000) {
|
||||||
@@ -330,9 +329,13 @@ async function nutsNut(interaction, quickMode) {
|
|||||||
{ upsert: true }
|
{ upsert: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let nutText = `Du hast ${formatNut(amount)} bekommen! :chestnut:`
|
||||||
|
if (amount <= 0) {
|
||||||
|
nutText = `Du hast keine Nuss bekommen! :yoink_help:`
|
||||||
|
}
|
||||||
|
|
||||||
if (quickMode) {
|
if (quickMode) {
|
||||||
return interaction.editReply({ content: `Du hast ${formatNut(amount)} bekommen! :chestnut:` })
|
return interaction.editReply({ content: nutText })
|
||||||
}
|
}
|
||||||
|
|
||||||
// pseudo animation
|
// pseudo animation
|
||||||
@@ -355,7 +358,7 @@ async function nutsNut(interaction, quickMode) {
|
|||||||
else if (amount > 4) assetFile = assets.normal
|
else if (amount > 4) assetFile = assets.normal
|
||||||
else assetFile = assets.low
|
else assetFile = assets.low
|
||||||
|
|
||||||
embed.setTitle(`Du hast ${formatNut(amount)} bekommen!`)
|
embed.setTitle(nutText)
|
||||||
embed.setThumbnail(assetFile)
|
embed.setThumbnail(assetFile)
|
||||||
embed.setImage(null)
|
embed.setImage(null)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,576 @@
|
|||||||
|
const { ModalBuilder, UserSelectMenuBuilder, TextInputBuilder, EmbedBuilder, TimestampStyles, time, StringSelectMenuBuilder, messageLink } = require("@discordjs/builders");
|
||||||
|
const { ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } = require('discord.js')
|
||||||
|
const { SlashCommandBuilder, LabelBuilder, TextInputStyle, MessageFlags } = require("discord.js");
|
||||||
|
const { mClient } = require("../../..");
|
||||||
|
const db = mClient.db('neo-db')
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
function getRemaining(timer) {
|
||||||
|
if (timer.state !== 'running') {
|
||||||
|
return timer.timeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(
|
||||||
|
0,
|
||||||
|
timer.timeRemaining - (Date.now() - timer.startedAt)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ordinal(n) {
|
||||||
|
const s = ["th", "st", "nd", "rd"];
|
||||||
|
const v = n % 100;
|
||||||
|
|
||||||
|
return n + (s[(v - 20) % 10] || s[v] || s[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wgeStatus(interaction) {
|
||||||
|
|
||||||
|
const wgeColl = db.collection('timer_wge')
|
||||||
|
const historyColl = db.collection('history_wge');
|
||||||
|
|
||||||
|
const results = await wgeColl.findOne({
|
||||||
|
$and: [
|
||||||
|
{ debug: false },
|
||||||
|
{ state: 'running' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
if (!results) {
|
||||||
|
return interaction.editReply({
|
||||||
|
content: 'No WGE Session currently running!',
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyCount = await historyColl.countDocuments();
|
||||||
|
const iteration = historyCount + 1;
|
||||||
|
|
||||||
|
const createdAt = new Date(results.createdAt)
|
||||||
|
const expiresAt = new Date(createdAt.getTime() + Number(results.timeRemaining))
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(`✨ [${ordinal(iteration)}] World's Greatest Expert ✨`)
|
||||||
|
.setDescription(
|
||||||
|
`<@${results.assignedUser}> is currently the **World's Greatest Expert** in **${results.topic}**.\n\n` +
|
||||||
|
`They must submit their entry <t:${Math.floor(expiresAt.getTime() / 1000)}:R>.\n\n` +
|
||||||
|
`Thank you, <@${results.assigningUser}>, for the nomination!`
|
||||||
|
)
|
||||||
|
|
||||||
|
return interaction.editReply({
|
||||||
|
embeds: [embed]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wgeStart(interaction) {
|
||||||
|
const defaultUsers = []
|
||||||
|
defaultUsers.push(process.env.D_ID)
|
||||||
|
|
||||||
|
|
||||||
|
const participantColl = db.collection('items_wge')
|
||||||
|
const participants = await participantColl.find({
|
||||||
|
status: true
|
||||||
|
}).toArray()
|
||||||
|
|
||||||
|
|
||||||
|
const wgeBaseModal = new ModalBuilder()
|
||||||
|
.setCustomId('wgeBaseModal')
|
||||||
|
.setTitle("World's Greatest Expert")
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: 'RANDOM',
|
||||||
|
value: process.env.D_ID
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const participantOptions = await Promise.all(
|
||||||
|
participants.map(async (participant) => {
|
||||||
|
try {
|
||||||
|
const member = await interaction.guild.members.fetch(participant.userID);
|
||||||
|
return {
|
||||||
|
label: `${member.displayName} (${member.user.username})`,
|
||||||
|
value: participant.userID
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
options.push(
|
||||||
|
...participantOptions.filter(option => option !== null)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Using StringSelectMenuBuilder because UserSelectMenuBuilder cannot be filtered for now
|
||||||
|
const wgeUserCustInput = new StringSelectMenuBuilder()
|
||||||
|
.setCustomId('wgeCustUserInput')
|
||||||
|
.setMaxValues(1)
|
||||||
|
.setMinValues(1)
|
||||||
|
.setRequired(true)
|
||||||
|
.addOptions(options)
|
||||||
|
|
||||||
|
const wgeTopicInput = new TextInputBuilder()
|
||||||
|
.setCustomId('wgeTopicInput')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setRequired(true)
|
||||||
|
|
||||||
|
const wgeTopicLabel = new LabelBuilder()
|
||||||
|
.setLabel("And WHAT are they an expert in?")
|
||||||
|
.setDescription('Tell me!')
|
||||||
|
.setTextInputComponent(wgeTopicInput)
|
||||||
|
|
||||||
|
const wgeCustUserLabel = new LabelBuilder()
|
||||||
|
.setLabel('WHO is your greatest expert?')
|
||||||
|
.setDescription('Select them')
|
||||||
|
.setStringSelectMenuComponent(wgeUserCustInput)
|
||||||
|
|
||||||
|
wgeBaseModal
|
||||||
|
.addLabelComponents(wgeCustUserLabel, wgeTopicLabel)
|
||||||
|
|
||||||
|
await interaction.showModal(wgeBaseModal)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wgeStop(interaction) {
|
||||||
|
;
|
||||||
|
const wgeColl = db.collection('timer_wge');
|
||||||
|
|
||||||
|
const timer = await wgeColl.findOne({
|
||||||
|
debug: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!timer) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: 'Timer not found.',
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// freeze remaining time
|
||||||
|
const remaining = getRemaining(timer);
|
||||||
|
|
||||||
|
await wgeColl.updateOne(
|
||||||
|
{ _id: timer._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
timeRemaining: remaining,
|
||||||
|
startedAt: null,
|
||||||
|
state: 'stopped'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
content: `🛑 Timer stopped for **${timer.topic}**`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wgeResume(interaction) {
|
||||||
|
;
|
||||||
|
const wgeColl = db.collection('timer_wge');
|
||||||
|
|
||||||
|
const timer = await wgeColl.findOne({
|
||||||
|
debug: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!timer) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: 'Timer not found.',
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// freeze remaining time
|
||||||
|
const remaining = getRemaining(timer);
|
||||||
|
|
||||||
|
await wgeColl.updateOne(
|
||||||
|
{ _id: timer._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
timeRemaining: remaining,
|
||||||
|
startedAt: Date.now(),
|
||||||
|
state: 'running'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
content: `🟢 Timer resumed for **${timer.topic}**`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wgeForfeit(interaction) {
|
||||||
|
;
|
||||||
|
const wgeColl = db.collection('timer_wge');
|
||||||
|
const participateColl = db.collection('items_wge');
|
||||||
|
|
||||||
|
// Get active WGE
|
||||||
|
const wge = await wgeColl.findOne({
|
||||||
|
debug: false,
|
||||||
|
state: 'running'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!wge) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: 'No active WGE is currently running.',
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check: only assigned user can forfeit
|
||||||
|
if (wge.assignedUser !== interaction.user.id) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `Only <@${wge.assignedUser}> can forfeit the current WGE.`,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick a new participant, excluding current assignee
|
||||||
|
const [randomParticipant] = await participateColl.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
status: true,
|
||||||
|
userID: { $ne: interaction.user.id }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $sample: { size: 1 } }
|
||||||
|
]).toArray();
|
||||||
|
|
||||||
|
if (!randomParticipant) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: 'No one else is participating!',
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = await interaction.guild.members.fetch(
|
||||||
|
randomParticipant.userID
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextUser = member.user;
|
||||||
|
|
||||||
|
const WGE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
|
|
||||||
|
await wgeColl.deleteOne({ _id: wge._id });
|
||||||
|
|
||||||
|
await wgeColl.insertOne({
|
||||||
|
assignedUser: nextUser.id,
|
||||||
|
assigningUser: wge.assigningUser,
|
||||||
|
|
||||||
|
startedAt: Date.now(),
|
||||||
|
timeRemaining: WGE_DURATION,
|
||||||
|
|
||||||
|
state: 'running',
|
||||||
|
|
||||||
|
notifications: {
|
||||||
|
half: false,
|
||||||
|
day24: false,
|
||||||
|
complete: false
|
||||||
|
},
|
||||||
|
|
||||||
|
topic: wge.topic,
|
||||||
|
debug: false,
|
||||||
|
|
||||||
|
createdAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
return interaction.reply({
|
||||||
|
content: `${interaction.user} has forfeited! <@${nextUser.id}> is the next expert! Their topic is **${wge.topic}**`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wgeParticipate(interaction) {
|
||||||
|
const status = interaction.options.getBoolean('status')
|
||||||
|
const user = interaction.user
|
||||||
|
|
||||||
|
const coll = db.collection('items_wge')
|
||||||
|
|
||||||
|
// Choice was false, ergo not participating
|
||||||
|
await coll.findOneAndUpdate({
|
||||||
|
userID: user.id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
userID: user.id,
|
||||||
|
status: status
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
upsert: true
|
||||||
|
})
|
||||||
|
|
||||||
|
interaction.reply({
|
||||||
|
content: `You are ${status ? '' : 'no longer '}participating!`,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listParticipants(interaction) {
|
||||||
|
;
|
||||||
|
const participantColl = db.collection('items_wge');
|
||||||
|
const participants = await participantColl.find({}).toArray();
|
||||||
|
|
||||||
|
const participating = participants
|
||||||
|
.filter(p => p.status)
|
||||||
|
.map(p => `<@${p.userID}>`);
|
||||||
|
|
||||||
|
const notParticipating = participants
|
||||||
|
.filter(p => !p.status)
|
||||||
|
.map(p => `<@${p.userID}>`);
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('WGE Participation')
|
||||||
|
.setColor(0x5865F2)
|
||||||
|
.addFields(
|
||||||
|
{
|
||||||
|
name: `Participating (${participating.length})`,
|
||||||
|
value: participating.join('\n') || 'No participants found.',
|
||||||
|
inline: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Not Participating (${notParticipating.length})`,
|
||||||
|
value: notParticipating.join('\n') || 'No entries found.',
|
||||||
|
inline: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setFooter({
|
||||||
|
text: `Total entries: ${participants.length}`
|
||||||
|
})
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [embed],
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(ms) {
|
||||||
|
if (ms == null) return "unknown"
|
||||||
|
|
||||||
|
const totalSeconds = Math.floor(ms / 1000)
|
||||||
|
const days = Math.floor(totalSeconds / 86400)
|
||||||
|
const hours = Math.floor((totalSeconds % 86400) / 3600)
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||||
|
const seconds = totalSeconds % 60
|
||||||
|
|
||||||
|
const parts = []
|
||||||
|
|
||||||
|
if (days) parts.push(`${days}d`)
|
||||||
|
if (hours) parts.push(`${hours}h`)
|
||||||
|
if (minutes) parts.push(`${minutes}m`)
|
||||||
|
if (seconds && !days) parts.push(`${seconds}s`) // only show seconds for short durations
|
||||||
|
|
||||||
|
return parts.length ? parts.join(" ") : "0s"
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listHistory(interaction) {
|
||||||
|
const historyColl = db.collection('history_wge')
|
||||||
|
|
||||||
|
const wgeHistory = await historyColl
|
||||||
|
.find({ debug: false })
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.toArray()
|
||||||
|
|
||||||
|
if (!wgeHistory.length) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: "No WGE history found.",
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildEmbed = (entry, index) => {
|
||||||
|
const createdTs = entry.createdAt
|
||||||
|
? Math.floor(new Date(entry.createdAt).getTime() / 1000)
|
||||||
|
: null
|
||||||
|
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setTitle(`WGE History (${index + 1}/${wgeHistory.length})`)
|
||||||
|
.setDescription(
|
||||||
|
`<@${entry.assignedUser}> was the **World's Greatest Expert** of...\n\n` +
|
||||||
|
`**${entry.topic}**\n\n` +
|
||||||
|
|
||||||
|
`Assigned by: <@${entry.assigningUser}>\n` +
|
||||||
|
(createdTs ? `Created: <t:${createdTs}:R>` : "")
|
||||||
|
)
|
||||||
|
.setColor(0x2b2d31)
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = 0
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder().addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('prev')
|
||||||
|
.setLabel('◀')
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
.setDisabled(true),
|
||||||
|
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('next')
|
||||||
|
.setLabel('▶')
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
.setDisabled(wgeHistory.length <= 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
const msg = await interaction.reply({
|
||||||
|
embeds: [buildEmbed(wgeHistory[page], page)],
|
||||||
|
components: [row],
|
||||||
|
fetchReply: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const collector = msg.createMessageComponentCollector({
|
||||||
|
componentType: ComponentType.Button,
|
||||||
|
time: 120000
|
||||||
|
})
|
||||||
|
|
||||||
|
collector.on('collect', async i => {
|
||||||
|
if (i.user.id !== interaction.user.id) {
|
||||||
|
return i.reply({
|
||||||
|
content: "You can't control this pagination.",
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i.customId === 'prev') {
|
||||||
|
page--
|
||||||
|
} else if (i.customId === 'next') {
|
||||||
|
page++
|
||||||
|
}
|
||||||
|
|
||||||
|
page = Math.max(0, Math.min(page, wgeHistory.length - 1))
|
||||||
|
|
||||||
|
row.components[0].setDisabled(page === 0)
|
||||||
|
row.components[1].setDisabled(page === wgeHistory.length - 1)
|
||||||
|
|
||||||
|
await i.update({
|
||||||
|
embeds: [buildEmbed(wgeHistory[page], page)],
|
||||||
|
components: [row]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
collector.on('end', async () => {
|
||||||
|
const disabledRow = new ActionRowBuilder().addComponents(
|
||||||
|
row.components.map(btn => ButtonBuilder.from(btn).setDisabled(true))
|
||||||
|
)
|
||||||
|
|
||||||
|
await msg.edit({ components: [disabledRow] }).catch(() => { })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wgeList(interaction) {
|
||||||
|
const thing = interaction.options.getString('thing');
|
||||||
|
|
||||||
|
switch (thing) {
|
||||||
|
case 'participants': {
|
||||||
|
listParticipants(interaction)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'history':
|
||||||
|
listHistory(interaction)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('wge')
|
||||||
|
.setDescription('welcome to worlds greatest experts')
|
||||||
|
// Show Status of Current Running WGE Session
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('status')
|
||||||
|
.setDescription('display current wge session')
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assign Someone to be the next Expert
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('start')
|
||||||
|
.setDescription('start a new wge session')
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stop the current Session Timer
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('stop')
|
||||||
|
.setDescription('stop the current wge session')
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resume the current Session Timer
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('resume')
|
||||||
|
.setDescription('resume the current wge session timer')
|
||||||
|
)
|
||||||
|
|
||||||
|
// Forfeit your current expert topic and assign someone else
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('forfeit')
|
||||||
|
.setDescription('forfeit your current topic')
|
||||||
|
)
|
||||||
|
|
||||||
|
// List the previous WGE Sessions
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('history')
|
||||||
|
.setDescription('display the previous wge sessions')
|
||||||
|
)
|
||||||
|
|
||||||
|
// List the previous WGE Sessions
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('participate')
|
||||||
|
.setDescription('do you want to play a game?')
|
||||||
|
.addBooleanOption(o =>
|
||||||
|
o.setName('status')
|
||||||
|
.setDescription('choose')
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// List the previous WGE Sessions
|
||||||
|
.addSubcommand(s =>
|
||||||
|
s
|
||||||
|
.setName('list')
|
||||||
|
.setDescription('list things')
|
||||||
|
.addStringOption(o =>
|
||||||
|
o.setName('thing')
|
||||||
|
.setDescription('what would you like to be listed')
|
||||||
|
.addChoices(
|
||||||
|
{ name: 'participants', value: 'participants' },
|
||||||
|
{ name: 'history', value: 'history' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
,
|
||||||
|
async execute(interaction) {
|
||||||
|
switch (interaction.options._subcommand) {
|
||||||
|
case 'status':
|
||||||
|
await interaction.deferReply()
|
||||||
|
wgeStatus(interaction)
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
wgeStart(interaction)
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
wgeStop(interaction)
|
||||||
|
break;
|
||||||
|
case 'pause':
|
||||||
|
wgePause(interaction)
|
||||||
|
break;
|
||||||
|
case 'resume':
|
||||||
|
wgeResume(interaction)
|
||||||
|
break;
|
||||||
|
case 'forfeit':
|
||||||
|
wgeForfeit(interaction)
|
||||||
|
break;
|
||||||
|
case 'history':
|
||||||
|
wgeHistory(interaction)
|
||||||
|
break;
|
||||||
|
case 'participate':
|
||||||
|
wgeParticipate(interaction)
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
wgeList(interaction)
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,5 +101,18 @@ module.exports = {
|
|||||||
description: 'a birthday banner'
|
description: 'a birthday banner'
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// mark as notified
|
||||||
|
|
||||||
|
const bdayColl = db.collection('items_birthdays')
|
||||||
|
await bdayColl.findOneAndUpdate({
|
||||||
|
userID: member.user.id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
notified: true
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
upsert: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+39
-5
@@ -48,25 +48,59 @@ module.exports = {
|
|||||||
for (const member of members.values()) {
|
for (const member of members.values()) {
|
||||||
const dbEntry = bdayMap.get(member.id);
|
const dbEntry = bdayMap.get(member.id);
|
||||||
|
|
||||||
if (dbEntry && isToday(dbEntry.day, dbEntry.month)) {
|
if (!dbEntry) continue;
|
||||||
// Today is their birthday
|
|
||||||
|
// Reset notification once the birthday has passed
|
||||||
|
if (!isToday(dbEntry.day, dbEntry.month) && dbEntry.notified) {
|
||||||
|
await bdayColl.updateOne(
|
||||||
|
{ userID: member.id },
|
||||||
|
{ $set: { notified: false } }
|
||||||
|
);
|
||||||
|
|
||||||
|
dbEntry.notified = false; // keep local copy in sync
|
||||||
|
console.log(`Removed notified flag for ${member.user.tag}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if already handled today
|
||||||
|
if (dbEntry.notified && isToday(dbEntry.day, dbEntry.month)) {
|
||||||
|
console.log(`${member.user.username} was already notified today, skipping...`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isToday(dbEntry.day, dbEntry.month)) {
|
||||||
await member.roles.add(birthdayRoleID).catch(err =>
|
await member.roles.add(birthdayRoleID).catch(err =>
|
||||||
console.error(`Failed to add birthday role to ${member.user.tag}:`, err)
|
console.error(`Failed to add birthday role to ${member.user.tag}:`, err)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Added birthday role to ${member.user.tag}`);
|
console.log(`Added birthday role to ${member.user.tag}`);
|
||||||
//client.emit('Birthday', member);
|
client.emit('Birthday', member);
|
||||||
|
|
||||||
|
// mark as notified
|
||||||
|
await bdayColl.updateOne(
|
||||||
|
{ userID: member.id },
|
||||||
|
{ $set: { notified: true } }
|
||||||
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Default fallback: remove birthday role if present
|
|
||||||
if (member.roles.cache.has(birthdayRoleID)) {
|
if (member.roles.cache.has(birthdayRoleID)) {
|
||||||
await member.roles.remove(birthdayRoleID).catch(err =>
|
await member.roles.remove(birthdayRoleID).catch(err =>
|
||||||
console.error(`Failed to remove birthday role from ${member.user.tag}:`, err)
|
console.error(`Failed to remove birthday role from ${member.user.tag}:`, err)
|
||||||
);
|
);
|
||||||
console.log(`Removed birthday role from ${member.user.tag} (not today or not in DB)`);
|
|
||||||
|
console.log(`Removed birthday role from ${member.user.tag}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await handleBirthdays();
|
await handleBirthdays();
|
||||||
|
|
||||||
|
// First check after restart
|
||||||
|
client.emit('timerCheck');
|
||||||
|
|
||||||
|
// Check every minute
|
||||||
|
setInterval(() => {
|
||||||
|
client.emit('timerCheck');
|
||||||
|
}, 60_000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
+103
-4
@@ -1,13 +1,16 @@
|
|||||||
const { Events, MessageFlags } = require('discord.js')
|
const { Events, MessageFlags, Message } = require('discord.js')
|
||||||
|
const { mClient } = require('../index')
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: Events.InteractionCreate,
|
name: Events.InteractionCreate,
|
||||||
once: false,
|
once: false,
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
if (interaction.isChatInputCommand()) { commandHandler(interaction) };
|
if (interaction.isChatInputCommand()) { commandHandler(interaction) }
|
||||||
if (interaction.isButton()) { buttonHandler(interaction) }
|
if (interaction.isButton()) { buttonHandler(interaction) }
|
||||||
if (interaction.isMentionableSelectMenu()) { selectMenuHandler(interaction)}
|
if (interaction.isMentionableSelectMenu()) { selectMenuHandler(interaction) }
|
||||||
if (interaction.isUserSelectMenu()) { selectMenuHandler(interaction) }
|
if (interaction.isUserSelectMenu()) { selectMenuHandler(interaction) }
|
||||||
|
if (interaction.isModalSubmit()) { modalSubmitHandler(interaction) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function commandHandler(interaction) {
|
async function commandHandler(interaction) {
|
||||||
@@ -30,7 +33,7 @@ async function commandHandler(interaction) {
|
|||||||
}
|
}
|
||||||
async function buttonHandler(interaction) {
|
async function buttonHandler(interaction) {
|
||||||
const button = interaction.client.buttons.get(interaction.customId)
|
const button = interaction.client.buttons.get(interaction.customId)
|
||||||
if (!button) { return console.error(`No button registered matching ${interaction.customId}.`) }
|
if (!button) { return }// console.error(`No button registered matching ${interaction.customId}.`) }
|
||||||
try {
|
try {
|
||||||
await button.execute(interaction);
|
await button.execute(interaction);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -56,3 +59,99 @@ async function selectMenuHandler(interaction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function modalSubmitHandler(interaction) {
|
||||||
|
if (interaction.customId === 'wgeBaseModal') {
|
||||||
|
const db = mClient.db('neo-db')
|
||||||
|
const wgeColl = db.collection('timer_wge')
|
||||||
|
const participateColl = db.collection('items_wge')
|
||||||
|
|
||||||
|
// Get the data entered by the user
|
||||||
|
|
||||||
|
var wgeAssignedUser = {}
|
||||||
|
wgeAssignedUser.id = interaction.components[0].component.values[0]
|
||||||
|
|
||||||
|
const wgeAssigningUser = interaction.user
|
||||||
|
if (wgeAssignedUser.id === process.env.D_ID) {
|
||||||
|
// Randomizer Function
|
||||||
|
const randomParticipant = await participateColl.aggregate([
|
||||||
|
{ $match: { status: true } },
|
||||||
|
{ $sample: { size: 1 } }
|
||||||
|
]).toArray()
|
||||||
|
|
||||||
|
if (!randomParticipant) { return interaction.followUp({ content: 'No User is participating!' }) }
|
||||||
|
|
||||||
|
random = await interaction.guild.members.fetch(randomParticipant[0].userID)
|
||||||
|
wgeAssignedUser = random.user
|
||||||
|
} else {
|
||||||
|
// validate non-random user
|
||||||
|
const foundParticipant = await participateColl.findOne({
|
||||||
|
$and: [
|
||||||
|
{ assignedUser: wgeAssignedUser.id },
|
||||||
|
{ status: true}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
if(!foundParticipant || foundParticipant == null){ return interaction.reply({
|
||||||
|
content: `<@${wgeAssignedUser.id}> is not participating!`,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
})}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const prevWge = await wgeColl.findOne({
|
||||||
|
$and: [
|
||||||
|
{ debug: false },
|
||||||
|
{ state: 'running' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (prevWge != null) {
|
||||||
|
// Save previous run in history_wge
|
||||||
|
const historyWgeColl = db.collection('history_wge')
|
||||||
|
await historyWgeColl.insertOne({
|
||||||
|
assignedUser: prevWge.assignedUser,
|
||||||
|
assigningUser: prevWge.assigningUser,
|
||||||
|
timeRemaining: prevWge.timeRemaining,
|
||||||
|
topic: prevWge.topic,
|
||||||
|
debug: prevWge.debug,
|
||||||
|
createdAt: prevWge.createdAt
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
message: 'saved previous wge in history_wge',
|
||||||
|
data: prevWge
|
||||||
|
})
|
||||||
|
|
||||||
|
await wgeColl.deleteOne({
|
||||||
|
_id: prevWge._id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const wgeAssignedTopic = interaction.fields.getTextInputValue('wgeTopicInput');
|
||||||
|
|
||||||
|
const wgeDefaultAssignedTime = 7 * 24 * 60 * 60 * 1000; // 7 days in ms
|
||||||
|
|
||||||
|
await wgeColl.insertOne({
|
||||||
|
assignedUser: wgeAssignedUser.id,
|
||||||
|
assigningUser: wgeAssigningUser.id,
|
||||||
|
|
||||||
|
startedAt: Date.now(),
|
||||||
|
timeRemaining: wgeDefaultAssignedTime,
|
||||||
|
|
||||||
|
state: 'running',
|
||||||
|
|
||||||
|
notifications: {
|
||||||
|
half: false,
|
||||||
|
day24: false,
|
||||||
|
complete: false
|
||||||
|
},
|
||||||
|
|
||||||
|
topic: wgeAssignedTopic,
|
||||||
|
debug: false,
|
||||||
|
|
||||||
|
createdAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
return interaction.reply({ content: `<@${wgeAssignedUser.id}> is the next expert! Their topic is **${wgeAssignedTopic}**` })
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
-5
@@ -1,19 +1,98 @@
|
|||||||
const { Events } = require('discord.js')
|
const { Events, MessageFlags } = require('discord.js')
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
const { client } = require('../index')
|
const { client, mClient } = require('../index')
|
||||||
const prefix = process.env.D_Prefix
|
const prefix = process.env.D_Prefix
|
||||||
|
const db = mClient.db(process.env.M_DB)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: Events.MessageCreate,
|
name: Events.MessageCreate,
|
||||||
once: false,
|
once: false,
|
||||||
async execute(message) {
|
async execute(message) {
|
||||||
if (!message.content.startsWith(prefix) || message.author.bot) return;
|
if (message.author.bot) { return }
|
||||||
|
|
||||||
|
// Erster/Zweiter/Dritter Handler
|
||||||
|
if (message.channelId === '353902391021535244' || message.channelId === '471058991481487361') {
|
||||||
|
const numbered = {
|
||||||
|
Erst: "Ersti",
|
||||||
|
Zweit: "Zweiti",
|
||||||
|
Dritt: "Dritti"
|
||||||
|
};
|
||||||
|
|
||||||
|
const found = Object.entries(numbered).find(([stem]) =>
|
||||||
|
new RegExp(`\\b\\w*${stem}\\w*\\b`, "i").test(message.content)
|
||||||
|
)?.[1];
|
||||||
|
|
||||||
|
if (!found) return;
|
||||||
|
|
||||||
|
const numberedColl = db.collection('items_daily_numbers');
|
||||||
|
const today = new Date().toLocaleDateString("en-CA", {
|
||||||
|
timeZone: "Europe/Berlin"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tages-Dokument sicherstellen
|
||||||
|
await numberedColl.findOneAndUpdate(
|
||||||
|
{ date: today },
|
||||||
|
{
|
||||||
|
$setOnInsert: {
|
||||||
|
date: today,
|
||||||
|
positions: {
|
||||||
|
Ersti: null,
|
||||||
|
Zweiti: null,
|
||||||
|
Dritti: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 🔒 Atomarer Claim:
|
||||||
|
// - Position muss frei sein
|
||||||
|
// - User darf noch keine Position heute haben
|
||||||
|
const result = await numberedColl.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
date: today,
|
||||||
|
[`positions.${found}`]: null,
|
||||||
|
|
||||||
|
"positions.Ersti.userId": { $ne: message.author.id },
|
||||||
|
"positions.Zweiti.userId": { $ne: message.author.id },
|
||||||
|
"positions.Dritti.userId": { $ne: message.author.id }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
[`positions.${found}`]: {
|
||||||
|
userId: message.author.id,
|
||||||
|
timestamp: new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ returnDocument: "after" }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ❌ bereits vergeben oder schon eine Position
|
||||||
|
if (!result) {
|
||||||
|
return message.reply({
|
||||||
|
content: `❌ Du kannst pro Tag nur eine Position beanspruchen, und sie muss noch frei sein.`,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Erfolg
|
||||||
|
return message.reply({
|
||||||
|
content: `✅ Du bist ${found}! :point_right: :point_left: <@${message.author.id}>`,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command Handler
|
||||||
const args = message.content.slice(prefix.length).trim().split(/ +/);
|
const args = message.content.slice(prefix.length).trim().split(/ +/);
|
||||||
|
if (!message.content.startsWith(prefix)) return;
|
||||||
|
|
||||||
const cmd = args.shift().toLowerCase();
|
const cmd = args.shift().toLowerCase();
|
||||||
let command = client.legacyCommands.get(cmd)
|
let command = client.legacyCommands.get(cmd)
|
||||||
if(!command) command = client.legacyCommands.get(client.aliases.get(cmd))
|
|
||||||
if(!command) return
|
|
||||||
|
if (!command) command = client.legacyCommands.get(client.aliases.get(cmd))
|
||||||
|
if (!command) return
|
||||||
try {
|
try {
|
||||||
client.legacyCommands.get(command.name).execute(message, args)
|
client.legacyCommands.get(command.name).execute(message, args)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
async function getMember(client, userId) {
|
||||||
|
const guild = client.guilds.cache.get(process.env.D_GuildID);
|
||||||
|
if (!guild) return null;
|
||||||
|
|
||||||
|
// cache first (fast path)
|
||||||
|
let member = guild.members.cache.get(userId);
|
||||||
|
|
||||||
|
// fallback (safe path)
|
||||||
|
if (!member) {
|
||||||
|
member = await guild.members.fetch(userId).catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'timer24h',
|
||||||
|
|
||||||
|
async execute(timer, client) {
|
||||||
|
const member = await getMember(client, timer.assignedUser);
|
||||||
|
if (!member) return;
|
||||||
|
|
||||||
|
await member.send(
|
||||||
|
`⚠️ Less than 24 hours remaining for: **${timer.topic}**`
|
||||||
|
).catch(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
const { mClient } = require('../index');
|
||||||
|
function getRemaining(timer) {
|
||||||
|
if (timer.state !== 'running') {
|
||||||
|
return timer.timeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(
|
||||||
|
0,
|
||||||
|
timer.timeRemaining - (Date.now() - timer.startedAt)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'timerCheck',
|
||||||
|
|
||||||
|
async execute(client) {
|
||||||
|
const db = mClient.db('neo-db');
|
||||||
|
const wgeColl = db.collection('timer_wge');
|
||||||
|
|
||||||
|
const timers = await wgeColl.find({
|
||||||
|
state: { $ne: 'stopped' }
|
||||||
|
}).toArray();
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
for (const timer of timers) {
|
||||||
|
|
||||||
|
const remaining = getRemaining(timer);
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// HALF TIME (3.5 days left)
|
||||||
|
// -------------------------
|
||||||
|
if (
|
||||||
|
!timer.notifications?.half &&
|
||||||
|
remaining <= 3.5 * 24 * 60 * 60 * 1000
|
||||||
|
) {
|
||||||
|
await wgeColl.updateOne(
|
||||||
|
{ _id: timer._id },
|
||||||
|
{ $set: { "notifications.half": true } }
|
||||||
|
);
|
||||||
|
|
||||||
|
client.emit('timerHalf', timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// 24H WARNING
|
||||||
|
// -------------------------
|
||||||
|
if (
|
||||||
|
!timer.notifications?.day24 &&
|
||||||
|
remaining <= 24 * 60 * 60 * 1000
|
||||||
|
) {
|
||||||
|
await wgeColl.updateOne(
|
||||||
|
{ _id: timer._id },
|
||||||
|
{ $set: { "notifications.day24": true } }
|
||||||
|
);
|
||||||
|
|
||||||
|
client.emit('timer24h', timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// COMPLETION
|
||||||
|
// -------------------------
|
||||||
|
if (
|
||||||
|
!timer.notifications?.complete &&
|
||||||
|
remaining <= 0
|
||||||
|
) {
|
||||||
|
await wgeColl.updateOne(
|
||||||
|
{ _id: timer._id },
|
||||||
|
{ $set: { "notifications.complete": true } }
|
||||||
|
);
|
||||||
|
|
||||||
|
client.emit('timerComplete', timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
const { mClient } = require("..");
|
||||||
|
|
||||||
|
async function getMember(client, userId) {
|
||||||
|
const guild = client.guilds.cache.get(process.env.D_GuildID);
|
||||||
|
if (!guild) return null;
|
||||||
|
|
||||||
|
// cache first (fast path)
|
||||||
|
let member = guild.members.cache.get(userId);
|
||||||
|
|
||||||
|
// fallback (safe path)
|
||||||
|
if (!member) {
|
||||||
|
member = await guild.members.fetch(userId).catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'timerComplete',
|
||||||
|
|
||||||
|
async execute(timer, client) {
|
||||||
|
const member = await getMember(client, timer.assignedUser);
|
||||||
|
if (!member) return;
|
||||||
|
|
||||||
|
await member.send(
|
||||||
|
`❌ Timer ran out: **${timer.topic}** ...Re-assigning randomly...`
|
||||||
|
).catch(() => { });
|
||||||
|
|
||||||
|
// Reset Timer, Reassign Randomly
|
||||||
|
const db = mClient.db('neo-db')
|
||||||
|
const wgeColl = db.collection('timer_wge')
|
||||||
|
|
||||||
|
// Randomizer Function
|
||||||
|
const participateColl = db.collection('items_wge')
|
||||||
|
const randomParticipant = await participateColl.aggregate([
|
||||||
|
{ $match: { status: true } },
|
||||||
|
{ $sample: { size: 1 } }
|
||||||
|
]).toArray()
|
||||||
|
|
||||||
|
if (!randomParticipant.length) { return interaction.reply({ content: 'No one else is participating!' }) }
|
||||||
|
|
||||||
|
const wge = await wgeColl.findOne({
|
||||||
|
$and: [
|
||||||
|
{ debug: false },
|
||||||
|
{ state: 'running' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const channelColl = db.collection('config_channels')
|
||||||
|
const guild = await client.guilds.cache.get(process.env.D_GuildID);
|
||||||
|
const wgeChannel = await channelColl.findOne({
|
||||||
|
$and: [
|
||||||
|
{ purpose: 'wge' },
|
||||||
|
{ guildID: process.env.D_GuildID }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const channel = await client.channels.cache.get(wgeChannel.channelID)
|
||||||
|
|
||||||
|
random = await guild.members.fetch(randomParticipant[0].userID)
|
||||||
|
wgeAssignedUser = random.user
|
||||||
|
|
||||||
|
await wgeColl.deleteOne({
|
||||||
|
_id: wge._id
|
||||||
|
})
|
||||||
|
|
||||||
|
const wgeDefaultAssignedTime = 7 * 24 * 60 * 60 * 1000; // 7 days in ms
|
||||||
|
|
||||||
|
channel.send({
|
||||||
|
content: `The Timer for <@${wge.assignedUser}> has run out! Assigning randomly... \r\n<@${wgeAssignedUser.id}> is the next expert! Their topic still is **${wge.topic}**`
|
||||||
|
})
|
||||||
|
|
||||||
|
await wgeColl.insertOne({
|
||||||
|
assignedUser: wgeAssignedUser.id,
|
||||||
|
assigningUser: wge.assigningUser,
|
||||||
|
|
||||||
|
startedAt: Date.now(),
|
||||||
|
timeRemaining: wgeDefaultAssignedTime,
|
||||||
|
|
||||||
|
state: 'running',
|
||||||
|
|
||||||
|
notifications: {
|
||||||
|
half: false,
|
||||||
|
day24: false,
|
||||||
|
complete: false
|
||||||
|
},
|
||||||
|
|
||||||
|
topic: wge.topic,
|
||||||
|
debug: false,
|
||||||
|
|
||||||
|
createdAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
async function getMember(client, userId) {
|
||||||
|
const guild = client.guilds.cache.get(process.env.D_GuildID);
|
||||||
|
if (!guild) return null;
|
||||||
|
|
||||||
|
// cache first (fast path)
|
||||||
|
let member = guild.members.cache.get(userId);
|
||||||
|
|
||||||
|
// fallback (safe path)
|
||||||
|
if (!member) {
|
||||||
|
member = await guild.members.fetch(userId).catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'timerHalf',
|
||||||
|
|
||||||
|
async execute(timer, client) {
|
||||||
|
const member = await getMember(client, timer.assignedUser);
|
||||||
|
if (!member) return;
|
||||||
|
|
||||||
|
await member.send(
|
||||||
|
`⏳ Half time reached for: **${timer.topic}**`
|
||||||
|
).catch(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user