added pomodoro function
All checks were successful
release-tag / release-image (push) Successful in 56s

This commit is contained in:
moriese 2025-05-10 00:20:29 +02:00
parent c1f0ae670d
commit 56ec6a1ad8
8 changed files with 139 additions and 26 deletions

View File

@ -24,7 +24,7 @@ export class DynamicVChannelService {
async createVChannel( async createVChannel(
newState: VoiceState, newState: VoiceState,
channel: VoiceBasedChannel, channel: VoiceBasedChannel
): Promise<StageChannel | VoiceChannel> { ): Promise<StageChannel | VoiceChannel> {
//console.log("createChannel()"); //console.log("createChannel()");
const newVChannel = await channel.clone({ const newVChannel = await channel.clone({
@ -41,10 +41,9 @@ export class DynamicVChannelService {
newChannel: StageChannel | VoiceChannel, newChannel: StageChannel | VoiceChannel,
// biome-ignore lint/suspicious/noExplicitAny: <explanation> // biome-ignore lint/suspicious/noExplicitAny: <explanation>
channelListeners: Map<any, any>, channelListeners: Map<any, any>,
channelListener: (oldState: VoiceState, newState: VoiceState) => void, channelListener: (oldState: VoiceState, newState: VoiceState) => void
) { ) {
//console.log("deleteChannel()"); //console.log("deleteChannel()");
if ( if (
oldState.channelId === newChannel.id || oldState.channelId === newChannel.id ||
newState.channelId === newChannel.id newState.channelId === newChannel.id

View File

@ -133,6 +133,7 @@ export class GreetingService {
} }
} }
// unused
async greet() { async greet() {
client.user?.setActivity("guten morgen! :3", { type: 4 }); client.user?.setActivity("guten morgen! :3", { type: 4 });
console.log("set activity: awake"); console.log("set activity: awake");
@ -142,13 +143,14 @@ export class GreetingService {
const channels = client.channels; const channels = client.channels;
const channel = channels.cache.get(config.discord.testChannel); const channel = channels.cache.get(config.discord.channelIdOffTopic);
if (channel?.isTextBased && channel?.isSendable()) { if (channel?.isTextBased && channel?.isSendable()) {
await channel.send({ content: this.getContent(false) }); await channel.send({ content: this.getContent(false) });
} }
} }
// unused
async sleep() { async sleep() {
client.user?.setActivity("zzzzZZ..", { type: 4 }); client.user?.setActivity("zzzzZZ..", { type: 4 });
console.log("set activity: asleep"); console.log("set activity: asleep");
@ -158,7 +160,7 @@ export class GreetingService {
const channels = client.channels; const channels = client.channels;
const channel = channels.cache.get(config.discord.testChannel); const channel = channels.cache.get(config.discord.channelIdOffTopic);
if (channel?.isTextBased && channel?.isSendable()) { if (channel?.isTextBased && channel?.isSendable()) {
await channel.send({ content: this.getContent(true) }); await channel.send({ content: this.getContent(true) });
@ -172,9 +174,10 @@ export class GreetingService {
status: "online", status: "online",
}); });
// unused
/*const channels = client.channels; /*const channels = client.channels;
const channel = channels.cache.get(config.discord.channelId); const channel = channels.cache.get(config.discord.channelIdOffTopic);
if (channel?.isTextBased && channel?.isSendable()) { if (channel?.isTextBased && channel?.isSendable()) {
await channel.send({ content: "frohes neues! @everyone" }); await channel.send({ content: "frohes neues! @everyone" });

View File

@ -0,0 +1,41 @@
import config from "config";
import { Events, NewsChannel, type VoiceState } from "discord.js";
import client from "lib/client";
import EventEmitter from "node:events";
import { PomodoroService } from "actions/pomodoro/pomodoro.service";
export default class PomodoroController extends EventEmitter {
private pomodoroService: PomodoroService;
private activePomodoros = new Set<string>();
private pomodoroChannels = [config.discord.vchannelIdPomodoro25, config.discord.vchannelIdPomodoro50];
constructor() {
super();
this.pomodoroService = new PomodoroService();
client.on(Events.VoiceStateUpdate, async (oldState, newState) => {
const userId = newState.id;
const joinedPomodoroVC = newState.channelId != null && this.pomodoroChannels.includes(newState.channelId) &&
oldState.channelId !== newState.channelId;
const leftPomodoroVC = oldState.channelId != null && this.pomodoroChannels.includes(oldState.channelId) &&
newState.channelId !== config.discord.vchannelIdPomodoro25;
if (joinedPomodoroVC && !this.activePomodoros.has(userId)) {
const member = newState.member;
const vchannel = newState.channel;
if (!member || !vchannel) return;
this.activePomodoros.add(userId);
this.pomodoroService.startPomodoroLoop(member, vchannel);
}
if (leftPomodoroVC && this.activePomodoros.has(userId)) {
this.pomodoroService.stopPomodoro(userId);
this.activePomodoros.delete(userId);
}
});
}
}

View File

@ -0,0 +1,67 @@
import type { GuildMember, VoiceBasedChannel } from "discord.js";
import client from "lib/client";
import config from "config";
import { CustomMessageService } from "actions/customMessage/customMessage.service";
export class PomodoroService {
customMessageService: CustomMessageService;
private activeControllers = new Map<string, AbortController>();
constructor() {
this.customMessageService = new CustomMessageService();
}
public async startPomodoroLoop(member: GuildMember, vchannel: VoiceBasedChannel) {
const userId = member.id;
const controller = new AbortController();
this.activeControllers.set(userId, controller);
const minutesWork = vchannel.id === config.discord.vchannelIdPomodoro25 ? 25 : 50;
const minutesBreak = minutesWork / 5;//vchannel.id === config.discord.vchannelIdPomodoro25 ? 5 : 10;
const signal = controller.signal;
try {
while (!signal.aborted) {
await this.sendMessage(`<@${userId}> 🍅 **pomodoro gestartet!** ${minutesWork} minuten produktivitaet`);
const finishedWork = await this.sleep(minutesWork * 60 * 1000, signal);
if (!finishedWork) break;
await this.sendMessage(`<@${userId}> ☕ **pause!** ${minutesBreak} minuten chillen`);
const finishedBreak = await this.sleep(minutesBreak * 60 * 1000, signal);
if (!finishedBreak) break;
}
} catch (err) {
if ((err as Error).name !== "AbortError") {
console.error("pomodoro fehler:", err);
}
} finally {
this.activeControllers.delete(userId);
}
}
public stopPomodoro(userId: string) {
const controller = this.activeControllers.get(userId);
if (controller) {
controller.abort();
this.activeControllers.delete(userId);
}
}
private async sendMessage(text: string) {
const channel = client.channels.cache.get(config.discord.channelIdPomodoro);
if (channel?.isTextBased() && channel?.isSendable()) {
await channel.send(text);
}
}
private sleep(ms: number, signal: AbortSignal): Promise<boolean> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => resolve(true), ms);
signal.addEventListener("abort", () => {
clearTimeout(timeout);
resolve(false);
});
});
}
}

View File

@ -1,8 +1,6 @@
export default { export default {
discord: { discord: {
// test version: 250508.2150,
testChannel: process.env.DISCORD_TEST_CHANNEL_ID || "",
// avocadi // avocadi
serverID: process.env.DISCORD_SERVER_ID || "", serverID: process.env.DISCORD_SERVER_ID || "",
// texxt channel // texxt channel
@ -15,11 +13,14 @@ export default {
channelIdIntroduction: process.env.DISCORD_CHANNEL_ID_INTRODUCTION || "", channelIdIntroduction: process.env.DISCORD_CHANNEL_ID_INTRODUCTION || "",
channelIdOffTopic: process.env.DISCORD_CHANNEL_ID_OFF_TOPIC || "", channelIdOffTopic: process.env.DISCORD_CHANNEL_ID_OFF_TOPIC || "",
channelIdHelp: process.env.DISCORD_CHANNEL_ID_HELP || "", channelIdHelp: process.env.DISCORD_CHANNEL_ID_HELP || "",
// voice channel# channelIdPomodoro: process.env.DISCORD_CHANNEL_ID_POMODORO || "",
// voice channel
vchannelIdForTwo: process.env.DISCORD_VCHANNEL_ID_FOR_TWO || "", vchannelIdForTwo: process.env.DISCORD_VCHANNEL_ID_FOR_TWO || "",
vchannelIdForThree: process.env.DISCORD_VCHANNEL_ID_FOR_THREE || "", vchannelIdForThree: process.env.DISCORD_VCHANNEL_ID_FOR_THREE || "",
vchannelIdForFour: process.env.DISCORD_VCHANNEL_ID_FOR_FOUR || "", vchannelIdForFour: process.env.DISCORD_VCHANNEL_ID_FOR_FOUR || "",
vchannelIdForGroup: process.env.DISCORD_VCHANNEL_ID_FOR_GROUP || "", vchannelIdForGroup: process.env.DISCORD_VCHANNEL_ID_FOR_GROUP || "",
vchannelIdPomodoro25: process.env.DISCORD_VCHANNEL_ID_POMODORO_25_5 || "",
vchannelIdPomodoro50: process.env.DISCORD_VCHANNEL_ID_POMODORO_50_10 || "",
// roles // roles
roleStudy: process.env.PEOPLE || "", roleStudy: process.env.PEOPLE || "",
roleMod: process.env.MOD || "", roleMod: process.env.MOD || "",

View File

@ -24,23 +24,22 @@ import { DmService } from "actions/dm/dm.service";
import { CustomMessageService } from "actions/customMessage/customMessage.service"; import { CustomMessageService } from "actions/customMessage/customMessage.service";
import { DynamicVChannelService } from "actions/dynamicVChannel/dynamicVChannel.service"; import { DynamicVChannelService } from "actions/dynamicVChannel/dynamicVChannel.service";
import { ReactRolesService } from "actions/reactRole/reactRoles.service"; import { ReactRolesService } from "actions/reactRole/reactRoles.service";
import { PomodoroService } from "actions/pomodoro/pomodoro.service";
import config from "config"; import config from "config";
export default class DiscordController extends EventEmitter { export default class DiscordController extends EventEmitter {
private discordService!: DiscordService; private discordService: DiscordService;
waterMeService: WaterMeService; private waterMeService: WaterMeService;
greetingService: GreetingService; private greetingService: GreetingService;
medicationService: MedicationService; private medicationService: MedicationService;
helpService: HelpService; private helpService: HelpService;
supportService: SupportService; private supportService: SupportService;
activityService: ActivityService; private activityService: ActivityService;
dmService: DmService; private dmService: DmService;
customMessageService: CustomMessageService; private customMessageService: CustomMessageService;
channelListeners = new Map(); private channelListeners = new Map();
dynamicVChannelService: DynamicVChannelService; private dynamicVChannelService: DynamicVChannelService;
reactRolesService: ReactRolesService; private reactRolesService: ReactRolesService;
version = 250507.19;
constructor() { constructor() {
super(); super();
@ -138,7 +137,7 @@ export default class DiscordController extends EventEmitter {
} }
}, },
); );
console.log(`----------------\nversion ${this.version}\n----------------`); console.log(`----------------\nversion ${config.discord.version}\n----------------`);
} }
async setActivity() { async setActivity() {

View File

@ -3,9 +3,12 @@ import "actions/greeting/greeting.task";
import "actions/medication/medication.task"; import "actions/medication/medication.task";
import "actions/drink/drink.task"; import "actions/drink/drink.task";
import DiscordController from "discord.controller"; import DiscordController from "discord.controller";
import PomodoroController from "actions/pomodoro/pomodoro.controller";
import "dotenv/config"; import "dotenv/config";
// bootstrap application // bootstrap application
const discordController = new DiscordController(); const discordController = new DiscordController();
discordController.init(); const pomodoroController = new PomodoroController();
discordController.init();