Compare commits

...

2 Commits

Author SHA1 Message Date
admin_rb 4727434362 changed from ISO to LocaleDateString, Made Regex work with Any Prefix 2026-07-04 04:00:12 +02:00
admin_rb 6b2fab8b95 EZD Logik 2026-07-02 18:37:22 +02:00
5 changed files with 372 additions and 64 deletions
+109
View File
@@ -0,0 +1,109 @@
const {
SlashCommandBuilder,
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
MessageFlags
} = require('discord.js');
const { mClient } = require('../../..');
require('dotenv').config()
module.exports = {
data: new SlashCommandBuilder()
.setName('ezd')
.setDescription('Zeigt die täglichen Erster/Zweiter/Dritter Ergebnisse'),
async execute(interaction, client) {
const db = mClient.db(process.env.M_DB); // adjust if needed
const coll = db.collection('items_daily_numbers');
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)]
});
});
}
};
// 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);
}
+163 -48
View File
@@ -1,6 +1,8 @@
const { ModalBuilder, UserSelectMenuBuilder, TextInputBuilder, EmbedBuilder, TimestampStyles, time, StringSelectMenuBuilder } = require("@discordjs/builders"); 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 { SlashCommandBuilder, LabelBuilder, TextInputStyle, MessageFlags } = require("discord.js");
const { mClient } = require("../../.."); const { mClient } = require("../../..");
const db = mClient.db('neo-db')
require('dotenv').config() require('dotenv').config()
function getRemaining(timer) { function getRemaining(timer) {
@@ -22,7 +24,7 @@ function ordinal(n) {
} }
async function wgeStatus(interaction) { async function wgeStatus(interaction) {
const db = mClient.db('neo-db')
const wgeColl = db.collection('timer_wge') const wgeColl = db.collection('timer_wge')
const historyColl = db.collection('history_wge'); const historyColl = db.collection('history_wge');
@@ -62,7 +64,7 @@ async function wgeStart(interaction) {
const defaultUsers = [] const defaultUsers = []
defaultUsers.push(process.env.D_ID) defaultUsers.push(process.env.D_ID)
const db = mClient.db('neo-db')
const participantColl = db.collection('items_wge') const participantColl = db.collection('items_wge')
const participants = await participantColl.find({ const participants = await participantColl.find({
status: true status: true
@@ -128,7 +130,7 @@ async function wgeStart(interaction) {
} }
async function wgeStop(interaction) { async function wgeStop(interaction) {
const db = mClient.db('neo-db'); ;
const wgeColl = db.collection('timer_wge'); const wgeColl = db.collection('timer_wge');
const timer = await wgeColl.findOne({ const timer = await wgeColl.findOne({
@@ -162,7 +164,7 @@ async function wgeStop(interaction) {
} }
async function wgeResume(interaction) { async function wgeResume(interaction) {
const db = mClient.db('neo-db'); ;
const wgeColl = db.collection('timer_wge'); const wgeColl = db.collection('timer_wge');
const timer = await wgeColl.findOne({ const timer = await wgeColl.findOne({
@@ -196,7 +198,7 @@ async function wgeResume(interaction) {
} }
async function wgeForfeit(interaction) { async function wgeForfeit(interaction) {
const db = mClient.db('neo-db'); ;
const wgeColl = db.collection('timer_wge'); const wgeColl = db.collection('timer_wge');
const participateColl = db.collection('items_wge'); const participateColl = db.collection('items_wge');
@@ -275,14 +277,10 @@ async function wgeForfeit(interaction) {
}); });
} }
async function wgeHistory(interaction) {
//FIXME!
}
async function wgeParticipate(interaction) { async function wgeParticipate(interaction) {
const status = interaction.options.getBoolean('status') const status = interaction.options.getBoolean('status')
const user = interaction.user const user = interaction.user
const db = mClient.db('neo-db')
const coll = db.collection('items_wge') const coll = db.collection('items_wge')
// Choice was false, ergo not participating // Choice was false, ergo not participating
@@ -303,50 +301,167 @@ async function wgeParticipate(interaction) {
}) })
} }
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) { async function wgeList(interaction) {
const thing = interaction.options.getString('thing'); const thing = interaction.options.getString('thing');
switch (thing) { switch (thing) {
case 'participants': { case 'participants': {
const db = mClient.db('neo-db'); 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
});
break; break;
} }
case 'history':
listHistory(interaction)
break;
default: default:
break; break;
} }
+13 -8
View File
@@ -50,8 +50,19 @@ module.exports = {
if (!dbEntry) continue; if (!dbEntry) continue;
// skip if already notified // Reset notification once the birthday has passed
if (dbEntry.notified) { 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...`); console.log(`${member.user.username} was already notified today, skipping...`);
continue; continue;
} }
@@ -78,12 +89,6 @@ module.exports = {
console.log(`Removed birthday role from ${member.user.tag}`); console.log(`Removed birthday role from ${member.user.tag}`);
} }
// reset notification flag if not birthday anymore
await bdayColl.updateOne(
{ userID: member.id },
{ $set: { notified: false } }
);
} }
} }
}; };
+1 -1
View File
@@ -33,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) {
+84 -5
View File
@@ -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${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) {