diff --git a/src/actions/dynamicVChannel/dynamicVChannel.service.ts b/src/actions/dynamicVChannel/dynamicVChannel.service.ts index c0ceb7b..559442d 100644 --- a/src/actions/dynamicVChannel/dynamicVChannel.service.ts +++ b/src/actions/dynamicVChannel/dynamicVChannel.service.ts @@ -24,7 +24,7 @@ export class DynamicVChannelService { async createVChannel( newState: VoiceState, - channel: VoiceBasedChannel, + channel: VoiceBasedChannel ): Promise { //console.log("createChannel()"); const newVChannel = await channel.clone({ @@ -41,10 +41,9 @@ export class DynamicVChannelService { newChannel: StageChannel | VoiceChannel, // biome-ignore lint/suspicious/noExplicitAny: channelListeners: Map, - channelListener: (oldState: VoiceState, newState: VoiceState) => void, + channelListener: (oldState: VoiceState, newState: VoiceState) => void ) { //console.log("deleteChannel()"); - if ( oldState.channelId === newChannel.id || newState.channelId === newChannel.id diff --git a/src/actions/greeting/greeting.service.ts b/src/actions/greeting/greeting.service.ts index a22bd72..e2b1719 100644 --- a/src/actions/greeting/greeting.service.ts +++ b/src/actions/greeting/greeting.service.ts @@ -133,6 +133,7 @@ export class GreetingService { } } + // unused async greet() { client.user?.setActivity("guten morgen! :3", { type: 4 }); console.log("set activity: awake"); @@ -142,13 +143,14 @@ export class GreetingService { 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()) { await channel.send({ content: this.getContent(false) }); } } + // unused async sleep() { client.user?.setActivity("zzzzZZ..", { type: 4 }); console.log("set activity: asleep"); @@ -158,7 +160,7 @@ export class GreetingService { 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()) { await channel.send({ content: this.getContent(true) }); @@ -172,9 +174,10 @@ export class GreetingService { status: "online", }); + // unused /*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()) { await channel.send({ content: "frohes neues! @everyone" }); diff --git a/src/actions/pomodoro/pomodoro.components.ts b/src/actions/pomodoro/pomodoro.components.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/actions/pomodoro/pomodoro.controller.ts b/src/actions/pomodoro/pomodoro.controller.ts new file mode 100644 index 0000000..3eb36d4 --- /dev/null +++ b/src/actions/pomodoro/pomodoro.controller.ts @@ -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(); + 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); + } + }); + } + +} diff --git a/src/actions/pomodoro/pomodoro.service.ts b/src/actions/pomodoro/pomodoro.service.ts new file mode 100644 index 0000000..8d423bf --- /dev/null +++ b/src/actions/pomodoro/pomodoro.service.ts @@ -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(); + + 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 { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => resolve(true), ms); + signal.addEventListener("abort", () => { + clearTimeout(timeout); + resolve(false); + }); + }); + } +} diff --git a/src/config.ts b/src/config.ts index 9e9335c..05f4e3d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,6 @@ export default { discord: { - // test - testChannel: process.env.DISCORD_TEST_CHANNEL_ID || "", - + version: 250508.2150, // avocadi serverID: process.env.DISCORD_SERVER_ID || "", // texxt channel @@ -15,11 +13,14 @@ export default { channelIdIntroduction: process.env.DISCORD_CHANNEL_ID_INTRODUCTION || "", channelIdOffTopic: process.env.DISCORD_CHANNEL_ID_OFF_TOPIC || "", 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 || "", vchannelIdForThree: process.env.DISCORD_VCHANNEL_ID_FOR_THREE || "", vchannelIdForFour: process.env.DISCORD_VCHANNEL_ID_FOR_FOUR || "", 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 roleStudy: process.env.PEOPLE || "", roleMod: process.env.MOD || "", diff --git a/src/discord.controller.ts b/src/discord.controller.ts index 2b5f065..f0f15d9 100644 --- a/src/discord.controller.ts +++ b/src/discord.controller.ts @@ -24,23 +24,22 @@ import { DmService } from "actions/dm/dm.service"; import { CustomMessageService } from "actions/customMessage/customMessage.service"; import { DynamicVChannelService } from "actions/dynamicVChannel/dynamicVChannel.service"; import { ReactRolesService } from "actions/reactRole/reactRoles.service"; -import { PomodoroService } from "actions/pomodoro/pomodoro.service"; import config from "config"; export default class DiscordController extends EventEmitter { - private discordService!: DiscordService; - waterMeService: WaterMeService; - greetingService: GreetingService; - medicationService: MedicationService; - helpService: HelpService; - supportService: SupportService; - activityService: ActivityService; - dmService: DmService; - customMessageService: CustomMessageService; - channelListeners = new Map(); - dynamicVChannelService: DynamicVChannelService; - reactRolesService: ReactRolesService; - version = 250507.19; + private discordService: DiscordService; + private waterMeService: WaterMeService; + private greetingService: GreetingService; + private medicationService: MedicationService; + private helpService: HelpService; + private supportService: SupportService; + private activityService: ActivityService; + private dmService: DmService; + private customMessageService: CustomMessageService; + private channelListeners = new Map(); + private dynamicVChannelService: DynamicVChannelService; + private reactRolesService: ReactRolesService; + constructor() { 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() { diff --git a/src/index.ts b/src/index.ts index 658d11c..071d84d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,12 @@ import "actions/greeting/greeting.task"; import "actions/medication/medication.task"; import "actions/drink/drink.task"; import DiscordController from "discord.controller"; +import PomodoroController from "actions/pomodoro/pomodoro.controller"; import "dotenv/config"; // bootstrap application const discordController = new DiscordController(); -discordController.init(); +const pomodoroController = new PomodoroController(); + +discordController.init(); \ No newline at end of file