restructure
move to bun workspaces bump zod begin adding fluxer
This commit is contained in:
0
core/src/actions/activity/activity.components.ts
Normal file
0
core/src/actions/activity/activity.components.ts
Normal file
13
core/src/actions/activity/activity.service.ts
Normal file
13
core/src/actions/activity/activity.service.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { CacheType, Client, Interaction } from "discord.js";
|
||||
import client from "lib/client";
|
||||
|
||||
export class ActivityService {
|
||||
|
||||
async setActivity(client: Client<boolean>, description: string, activity: string) {
|
||||
client.user?.setActivity(":3", { type: 4 });
|
||||
console.log("set activity");
|
||||
client.user?.setPresence({
|
||||
status: "online",
|
||||
});
|
||||
}
|
||||
}
|
||||
30
core/src/actions/customMessage/customMessage.components.ts
Normal file
30
core/src/actions/customMessage/customMessage.components.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import config from "config";
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
export const customContent = `hey <@&${config.discord.roleStudy}>! meine [eigene website](https://avocadi.unom.io) ist endlich on :3\ngebe mir gerne rueckmeldung unter <#${config.discord.channelIdFeedback}>! <3`;
|
||||
|
||||
export function createEmbed(title: string, description: string, timestamp?: boolean) {
|
||||
// ({ embeds: [exampleEmbed] })
|
||||
console.log("createEmbed()");
|
||||
const customEmbed = (timestamp === true) ? new EmbedBuilder()
|
||||
.setColor(0x004400)
|
||||
.setAuthor({
|
||||
name: title,
|
||||
iconURL:
|
||||
"https://media.discordapp.net/attachments/1321933410188656693/1323447010380222474/mo_Avocadi_Avatar_Closeup_2.png?ex=67748b93&is=67733a13&hm=f48efb3523bca5f50e79144c7b41a127c94670e693e3da3dc2e6ffe62ad8a769&=&format=webp&quality=lossless&width=1524&height=1524",
|
||||
url: "https://avocadi.unom.io",
|
||||
})
|
||||
.setDescription(description)
|
||||
.setTimestamp() :
|
||||
new EmbedBuilder()
|
||||
.setColor(0x004400)
|
||||
.setAuthor({
|
||||
name: title,
|
||||
iconURL:
|
||||
"https://media.discordapp.net/attachments/1321933410188656693/1323447010380222474/mo_Avocadi_Avatar_Closeup_2.png?ex=67748b93&is=67733a13&hm=f48efb3523bca5f50e79144c7b41a127c94670e693e3da3dc2e6ffe62ad8a769&=&format=webp&quality=lossless&width=1524&height=1524",
|
||||
url: "https://avocadi.unom.io",
|
||||
})
|
||||
.setDescription(description);
|
||||
//.setFooter({ text: 'Some footer text here', iconURL: 'https://i.imgur.com/AfFp7pu.png' });
|
||||
|
||||
return customEmbed;
|
||||
}
|
||||
133
core/src/actions/customMessage/customMessage.service.ts
Normal file
133
core/src/actions/customMessage/customMessage.service.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
import { getRandomInt } from "lib/utils";
|
||||
import { customContent, createEmbed } from "./customMessage.components.ts";
|
||||
import {
|
||||
Client,
|
||||
EmbedBuilder,
|
||||
type Message,
|
||||
type CacheType,
|
||||
type GuildMember,
|
||||
type Interaction,
|
||||
type OmitPartialGroupDMChannel,
|
||||
type ChatInputCommandInteraction,
|
||||
ChannelType,
|
||||
} from "discord.js";
|
||||
import { type CommandsType, Commands } from "commands/index.ts";
|
||||
import { time } from "drizzle-orm/mysql-core";
|
||||
|
||||
export class CustomMessageService {
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await this.handleChatInputCommand(interaction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async handleChatInputCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
) {
|
||||
console.log("accept");
|
||||
const commandName = interaction.commandName as CommandsType;
|
||||
switch (commandName) {
|
||||
case Commands.Enum.embed:
|
||||
await this.customEmbed(interaction);
|
||||
return;
|
||||
case Commands.Enum.message:
|
||||
await this.customMessage(interaction);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async checkPermission(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
const userIdCommand = interaction.user.id;
|
||||
if (userIdCommand !== config.discord.myId) {
|
||||
await interaction.reply({
|
||||
content: "you have no permission for that command",
|
||||
ephemeral: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if command done in server
|
||||
async checkIfServer(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
const guild = interaction.guild;
|
||||
if (!guild) {
|
||||
await interaction.reply({
|
||||
content: "command can only be used on a server",
|
||||
ephemeral: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async customEmbed(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
const title = interaction.options.getString("title") || " ";
|
||||
const description = interaction.options.getString("description") || " ";
|
||||
const timestamp = interaction.options.getBoolean("timestamp") || false;
|
||||
// return the value
|
||||
console.log(title, description, timestamp);
|
||||
|
||||
// permission check
|
||||
// permission check
|
||||
if (await this.checkPermission(interaction) && await this.checkIfServer(interaction))
|
||||
try {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(interaction.channelId);
|
||||
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send({
|
||||
embeds: [createEmbed(title, description, timestamp)],
|
||||
});
|
||||
}
|
||||
await interaction.reply({
|
||||
content: "successfully created embed",
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error while creating embed:", error);
|
||||
await interaction.reply({
|
||||
content:
|
||||
"error while creating embed",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async customMessage(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
const input: string = interaction.options.getString("input") || "";
|
||||
const result = input.replaceAll(";", "\n");
|
||||
|
||||
// return the value
|
||||
console.log(input);
|
||||
|
||||
// permission check && server check
|
||||
if (await this.checkPermission(interaction) && await this.checkIfServer(interaction))
|
||||
try {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(interaction.channelId);
|
||||
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send({
|
||||
content: result,
|
||||
});
|
||||
}
|
||||
await interaction.reply({
|
||||
content: "successfully created message",
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error while creating message:", error);
|
||||
await interaction.reply({
|
||||
content:
|
||||
"error while creating message",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
48
core/src/actions/debug/debug.service.ts
Normal file
48
core/src/actions/debug/debug.service.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { type CommandsType, Commands } from "commands";
|
||||
import config from "config";
|
||||
import type { CacheType, ChatInputCommandInteraction, Interaction } from "discord.js";
|
||||
import { checkPermission } from "permissions";
|
||||
|
||||
export class DebugService {
|
||||
|
||||
async handleInteraction(
|
||||
interaction: Interaction<CacheType>
|
||||
) {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await this.handleChatInputCommand(interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
async handleChatInputCommand(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
const commandName = interaction.commandName as CommandsType;
|
||||
switch (commandName) {
|
||||
case Commands.Enum.version:
|
||||
await this.version(interaction);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async version(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
try {
|
||||
console.log("version command");
|
||||
if (await checkPermission(interaction.member) !== true) {
|
||||
await interaction.reply({
|
||||
content: "du hast keine rechte fuer diesen befehl",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await interaction.reply({
|
||||
content: "version: " + config.discord.version,
|
||||
});
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.error("error while sending version msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
5
core/src/actions/dm/dm.components.ts
Normal file
5
core/src/actions/dm/dm.components.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import config from "config";
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
|
||||
export const dmWelcomeContent = `hey! ich bin avocadi von [avocadi-study](<https://discord.gg/kkryyeXu3S>)!\n\num auf den rest des servers zugreifen zu koennen, musst du dich noch vorstellen (unter <#${config.discord.channelIdIntroduction}>)!\n\n---\nname und alter:\npronomen:\nklasse/studiengang/beruf:\nhobby:\nueber mich:\n---\n\nsobald wir deine nachricht ueberprueft haben, bekommst du die rolle **lernende:r** :)`;
|
||||
export const dmAcceptedContent = `huhu! du wurdest als lernende:r akzeptiert :3\nsag gerne hallo: <#${config.discord.channelIdOffTopic}> <:avocadi_cute:1321893797138923602>`;
|
||||
104
core/src/actions/dm/dm.service.ts
Normal file
104
core/src/actions/dm/dm.service.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
import { getRandomInt } from "lib/utils";
|
||||
import { dmWelcomeContent, dmAcceptedContent } from "./dm.components.ts";
|
||||
import {
|
||||
Client,
|
||||
EmbedBuilder,
|
||||
type Message,
|
||||
type CacheType,
|
||||
type GuildMember,
|
||||
type Interaction,
|
||||
type OmitPartialGroupDMChannel,
|
||||
} from "discord.js";
|
||||
|
||||
export class DmService {
|
||||
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
// todo
|
||||
}
|
||||
|
||||
async reminderPrivate(member: GuildMember) {
|
||||
console.log("reminder");
|
||||
try {
|
||||
const content = `hey, kleine erinnerung :)\nbitte stelle dich auf <#${config.discord.channelIdIntroduction}> vor, damit du alle kanaele ansehen und nutzen kannst! <3`
|
||||
await client.users.send(member, content);
|
||||
} catch (error) {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(config.discord.channelIdLog);
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send(`konnte keine private nachricht an <@${member.user.id}> senden`);
|
||||
}
|
||||
console.error("error while sending a welcome msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async welcomePrivate(member: GuildMember) {
|
||||
console.log("welcome private");
|
||||
try {
|
||||
await client.users.send(member, dmWelcomeContent);
|
||||
} catch (error) {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(config.discord.channelIdLog);
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send(`konnte keine private nachricht an <@${member.user.id}> senden`);
|
||||
}
|
||||
console.error("error while sending a welcome msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async forward(message: OmitPartialGroupDMChannel<Message<boolean>>) {
|
||||
if (message.channel.isDMBased()) {
|
||||
const author = message.author.id;
|
||||
const recipient = message.channel.recipient?.id;
|
||||
console.log("forward message");
|
||||
let context = "";
|
||||
|
||||
if (message.author.bot) {
|
||||
context = `<@${author}> hat an <@${recipient}> geschrieben:\n"${message.content}"`;
|
||||
}
|
||||
else {
|
||||
context = `<@${author}> hat geschrieben:\n"${message.content}"`;
|
||||
}
|
||||
|
||||
try {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(config.discord.channelIdLog);
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send(context);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("error while forwarding a msg:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async acceptDm(member: GuildMember) {
|
||||
console.log("accept dm");
|
||||
try {
|
||||
await client.users.send(member, dmAcceptedContent);
|
||||
} catch (error) {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(config.discord.channelIdLog);
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send(`konnte keine private nachricht an <@${member.user.id}> senden`);
|
||||
}
|
||||
console.error("error while sending a accept msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async roleMentionDm(member: GuildMember, add: boolean) {
|
||||
console.log("rolementionadd dm");
|
||||
try {
|
||||
const contentRoleMentionDm = `du hast die rolle **streber:in** erfolgreich ** *${(add ? "zugeteilt" : "entfernt")}* ** bekommen :3 <#${config.discord.channelIdOffTopic}> <:avocadi_cute:1321893797138923602>`;
|
||||
client.users.send(member, contentRoleMentionDm);
|
||||
} catch (error) {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(config.discord.channelIdLog);
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send(`konnte keine private nachricht an <@${member.user.id}> senden`);
|
||||
}
|
||||
console.error("error while sending a accept msg:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
core/src/actions/drink/drink.service.ts
Normal file
14
core/src/actions/drink/drink.service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
|
||||
export class GreetingService {
|
||||
async greet() {
|
||||
const channels = client.channels;
|
||||
|
||||
const channel = channels.cache.get(config.discord.channelIdBot);
|
||||
|
||||
if (channel?.isTextBased && channel?.isSendable()) {
|
||||
await channel.send({ content: "HALLOOOO" });
|
||||
}
|
||||
}
|
||||
}
|
||||
0
core/src/actions/drink/drink.task.ts
Normal file
0
core/src/actions/drink/drink.task.ts
Normal file
60
core/src/actions/dynamicVChannel/dynamicVChannel.service.ts
Normal file
60
core/src/actions/dynamicVChannel/dynamicVChannel.service.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
import { getRandomInt } from "lib/utils";
|
||||
import { } from "./dynamicVChannel.components.ts";
|
||||
import {
|
||||
Client,
|
||||
EmbedBuilder,
|
||||
type Message,
|
||||
type CacheType,
|
||||
type GuildMember,
|
||||
type Interaction,
|
||||
type OmitPartialGroupDMChannel,
|
||||
type VoiceState,
|
||||
type VoiceChannel,
|
||||
type StageChannel,
|
||||
Events,
|
||||
type VoiceBasedChannel,
|
||||
} from "discord.js";
|
||||
|
||||
export class DynamicVChannelService {
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
// todo
|
||||
}
|
||||
|
||||
async createVChannel(
|
||||
newState: VoiceState,
|
||||
channel: VoiceBasedChannel
|
||||
): Promise<StageChannel | VoiceChannel> {
|
||||
//console.log("createChannel()");
|
||||
const newVChannel = await channel.clone({
|
||||
name: `${channel.name.substring(2)}; ${newState.member?.displayName}`,
|
||||
position: 100,
|
||||
});
|
||||
|
||||
return newVChannel;
|
||||
}
|
||||
|
||||
async deleteVChannel(
|
||||
oldState: VoiceState,
|
||||
newState: VoiceState,
|
||||
newChannel: StageChannel | VoiceChannel,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
channelListeners: Map<any, any>,
|
||||
channelListener: (oldState: VoiceState, newState: VoiceState) => void
|
||||
) {
|
||||
//console.log("deleteChannel()");
|
||||
if (
|
||||
oldState.channelId === newChannel.id ||
|
||||
newState.channelId === newChannel.id
|
||||
) {
|
||||
if (newChannel.members.size === 0) {
|
||||
newChannel.delete().catch(console.error);
|
||||
|
||||
client.removeListener(Events.VoiceStateUpdate, channelListener);
|
||||
channelListeners.delete(newChannel.id);
|
||||
}
|
||||
}
|
||||
return channelListeners;
|
||||
}
|
||||
}
|
||||
38
core/src/actions/greeting/greeting.components.ts
Normal file
38
core/src/actions/greeting/greeting.components.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import config from "config";
|
||||
import type { GuildMember } from "discord.js";
|
||||
|
||||
export const greetContent = ["HALLOOOO", "guten morgen! ich hoffe es geht euch gut <3"];
|
||||
export const sleepContent = ["gute nacht! ich muss jetzt schlafen gehen :c", "zzzzZZ..", "*schnarch*"];
|
||||
export const dmWelcomeContent = `hey! ich bin avocadi von [avocadi-study](<https://discord.gg/kkryyeXu3S>)!\n\num auf den rest des servers zugreifen zu koennen, musst du dich noch vorstellen (unter <#${config.discord.channelIdIntroduction}>)!\n\n---\nname und alter:\npronomen:\nklasse/studiengang/beruf:\nhobby:\nueber mich:\n---\n\nsobald wir deine nachricht ueberprueft haben, bekommst du die rolle **lernende:r** :)`;
|
||||
export const dmAcceptedContent = `huhu! du wurdest als lernende:r akzeptiert :3\nsag gerne hallo: <#${config.discord.channelIdOffTopic}> <:avocadi_cute:1321893797138923602>`;
|
||||
|
||||
export function getWelcomeContent(member: GuildMember) {
|
||||
const welcomeContents = [
|
||||
`willkommen auf dem server, ${member}! fuehl dich wie zuhause💕`,
|
||||
`hey ${member}! schoen, dass du hier bist! 😊`,
|
||||
`hi ${member}, willkommen! viel spass hier! 💖`,
|
||||
`willkommen, ${member}! schoen, dass du da bist! :3`,
|
||||
`moin ${member}! viel spass im server! c:`,
|
||||
`hey ${member}, herzlich willkommen! fuehl dich wie zu hause! <3`,
|
||||
`hi ${member}! cool, dass du da bist! <3`,
|
||||
`willkommen, ${member}! wir freuen uns, dass du hier bist! 💕`,
|
||||
`hey ${member}! schoen, dass du bei uns bist! :3`,
|
||||
`willkommen auf dem server, ${member}! viel spass hier! ✨`,
|
||||
`hi ${member}, super, dass du dabei bist! :3`,
|
||||
`hey ${member}, willkommen bei uns! 💖`,
|
||||
`moin ${member}! schoen, dass du dabei bist! :)`,
|
||||
`hi ${member}, willkommen in unserer kleinen community! ✨`,
|
||||
`willkommen, ${member}! fuehl dich wie zu hause! 💕`,
|
||||
`hey ${member}, schoen, dass du uns gefunden hast! <333`,
|
||||
`hi ${member}, willkommen in unserer runde! c:`,
|
||||
`willkommen, ${member}! schoen, dass du hier bist! 💖`,
|
||||
`moin ${member}! lass uns zusammen spass haben! ✨`,
|
||||
`hey ${member}, herzlich willkommen bei uns! 😊`,
|
||||
`hi ${member}! schoen, dass du dabei bist! 💕`,
|
||||
`willkommen auf dem server, ${member}! wir freuen uns auf dich! <3`,
|
||||
`hey ${member}, schoen, dass du da bist! ✨`,
|
||||
`hi ${member}, willkommen! fuehl dich wie zu hause! 💖`,
|
||||
`willkommen, ${member}! lass uns gemeinsam eine tolle zeit haben! :3`,
|
||||
];
|
||||
return welcomeContents[Math.floor(Math.random() * welcomeContents.length)];
|
||||
}
|
||||
339
core/src/actions/greeting/greeting.service.ts
Normal file
339
core/src/actions/greeting/greeting.service.ts
Normal file
@@ -0,0 +1,339 @@
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
import { getRandomInt } from "lib/utils";
|
||||
import {
|
||||
getWelcomeContent,
|
||||
greetContent,
|
||||
sleepContent,
|
||||
} from "./greeting.components.ts";
|
||||
import {
|
||||
type ChatInputCommandInteraction,
|
||||
Client,
|
||||
EmbedBuilder,
|
||||
type CacheType,
|
||||
type GuildMember,
|
||||
type Interaction,
|
||||
GuildMemberRoleManager,
|
||||
type APIInteractionGuildMember,
|
||||
} from "discord.js";
|
||||
import { DmService } from "actions/dm/dm.service.ts";
|
||||
import { Commands, type CommandsType } from "commands/index.ts";
|
||||
import { checkPermission } from "permissions/index.ts";
|
||||
|
||||
export class GreetingService {
|
||||
dmService: DmService;
|
||||
|
||||
constructor() {
|
||||
this.dmService = new DmService();
|
||||
}
|
||||
|
||||
async handleInteraction(
|
||||
interaction: Interaction<CacheType>
|
||||
) {
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await this.handleChatInputCommand(interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
async handleChatInputCommand(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
const commandName = interaction.commandName as CommandsType;
|
||||
switch (commandName) {
|
||||
case Commands.Enum.accept:
|
||||
await this.acceptUser(interaction);
|
||||
return;
|
||||
case Commands.Enum.welcome:
|
||||
await this.welcomeCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.reminder:
|
||||
await this.reminderCommand(interaction);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async welcome(member: GuildMember) {
|
||||
console.log("welcome msg");
|
||||
const welcomeContent = getWelcomeContent(member);
|
||||
|
||||
try {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(config.discord.channelIdWelcome);
|
||||
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send(welcomeContent);
|
||||
}
|
||||
|
||||
await this.dmService.welcomePrivate(member);
|
||||
} catch (error) {
|
||||
console.error("error while sending a welcome msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async acceptUser(
|
||||
interaction: ChatInputCommandInteraction<CacheType>
|
||||
) {
|
||||
console.log("accept user");
|
||||
|
||||
// get the string option
|
||||
const input = interaction.options.getString("input") || "";
|
||||
// return the value
|
||||
//console.log(input);
|
||||
|
||||
// permission check
|
||||
if (await checkPermission(interaction.member) !== true) {
|
||||
await interaction.reply({
|
||||
content: "du hast keine rechte fuer diesen befehl",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// get user id from mentioning
|
||||
const userId = input.replace(/[<@!>]/g, "");
|
||||
console.log(userId.toString());
|
||||
const guild = interaction.guild;
|
||||
if (!guild) {
|
||||
await interaction.reply({
|
||||
content: "command can only be used on a server",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const member = await guild.members.fetch(userId);
|
||||
const username = member.user.username;
|
||||
console.log(username);
|
||||
|
||||
if (await this.checkRole(member) === true) {
|
||||
await interaction.reply({
|
||||
content: `${member.user.username} hat die rolle *lernende:r* schon!`,
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await member.roles.add(config.discord.roleStudy);
|
||||
|
||||
await interaction.reply({
|
||||
content: `die rolle *lernende:r* wurde erfolgreich an ${member.user.username} vergeben`,
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
this.dmService.acceptDm(member);
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Hinzufügen der Rolle:", error);
|
||||
await interaction.reply({
|
||||
content:
|
||||
"Es gab einen Fehler beim Hinzufügen der Rolle. Stelle sicher, dass du einen gültigen User erwähnt hast.",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// unused
|
||||
async greet() {
|
||||
client.user?.setActivity("guten morgen! :3", { type: 4 });
|
||||
console.log("set activity: awake");
|
||||
client.user?.setPresence({
|
||||
status: "online",
|
||||
});
|
||||
|
||||
const channels = client.channels;
|
||||
|
||||
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");
|
||||
client.user?.setPresence({
|
||||
status: "dnd",
|
||||
});
|
||||
|
||||
const channels = client.channels;
|
||||
|
||||
const channel = channels.cache.get(config.discord.channelIdOffTopic);
|
||||
|
||||
if (channel?.isTextBased && channel?.isSendable()) {
|
||||
await channel.send({ content: this.getContent(true) });
|
||||
}
|
||||
}
|
||||
|
||||
async newYear() {
|
||||
client.user?.setActivity("frohes neues! :)", { type: 4 });
|
||||
console.log("set activity: happy new year");
|
||||
client.user?.setPresence({
|
||||
status: "online",
|
||||
});
|
||||
|
||||
// unused
|
||||
/*const channels = client.channels;
|
||||
|
||||
const channel = channels.cache.get(config.discord.channelIdOffTopic);
|
||||
|
||||
if (channel?.isTextBased && channel?.isSendable()) {
|
||||
await channel.send({ content: "frohes neues! @everyone" });
|
||||
}*/
|
||||
}
|
||||
|
||||
getContent(asleep: boolean) {
|
||||
if (asleep) {
|
||||
return sleepContent[getRandomInt(0, sleepContent.length - 1)];
|
||||
}
|
||||
return greetContent[getRandomInt(0, greetContent.length - 1)];
|
||||
}
|
||||
|
||||
async reminderCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
console.log("remind user");
|
||||
|
||||
// get the string option
|
||||
const input = interaction.options.getString("input") || "";
|
||||
// return the value
|
||||
//console.log(input);
|
||||
|
||||
try {
|
||||
// permission check
|
||||
if (await checkPermission(interaction.member) !== true) {
|
||||
await interaction.reply({
|
||||
content: "du hast keine rechte fuer diesen befehl",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// get user id from mentioning
|
||||
const userId = input.replace(/[<@!>]/g, "");
|
||||
console.log(userId.toString());
|
||||
const guild = interaction.guild;
|
||||
if (!guild) {
|
||||
await interaction.reply({
|
||||
content: "command can only be used on a server",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const member = await guild.members.fetch(userId);
|
||||
const username = member.user.username;
|
||||
console.log(username);
|
||||
|
||||
if (await this.checkRole(member) === true) {
|
||||
await interaction.reply({
|
||||
content: `${member.user.username} hat die rolle *lernende:r* schon!`,
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.dmService.reminderPrivate(member);
|
||||
//await member.roles.add(config.discord.roleStudy);
|
||||
|
||||
await interaction.reply({
|
||||
content: `${member.user.username} wurde erfolgrich remindet`,
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error while reminding:", error);
|
||||
await interaction.reply({
|
||||
content:
|
||||
"Es gab einen Fehler beim reminden. Stelle sicher, dass du einen gültigen User erwähnt hast.",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async welcomeCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>
|
||||
) {
|
||||
console.log("welcome user");
|
||||
|
||||
// get the string option
|
||||
const input = interaction.options.getString("input") || "";
|
||||
// return the value
|
||||
//console.log(input);
|
||||
|
||||
try {
|
||||
// get user id from mentioning
|
||||
const userId = input.replace(/[<@!>]/g, "");
|
||||
console.log(userId.toString());
|
||||
const guild = interaction.guild;
|
||||
if (!guild) {
|
||||
await interaction.reply({
|
||||
content: "command can only be used on a server",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (await checkPermission(interaction.member) !== true) {
|
||||
await interaction.reply({
|
||||
content: "du hast keine rechte fuer diesen befehl",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// get member from id
|
||||
const member = await guild.members.fetch(userId);
|
||||
const username = member.user.username;
|
||||
console.log(username);
|
||||
|
||||
const welcomeContent = getWelcomeContent(member);
|
||||
|
||||
if (await this.checkRole(member) === true) {
|
||||
await interaction.reply({
|
||||
content: `${member.user.username} wurde schon begruesst!`,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const channels = client.channels;
|
||||
const channel = channels.cache.get(config.discord.channelIdWelcome);
|
||||
|
||||
if (channel?.isTextBased() && channel?.isSendable()) {
|
||||
await channel.send(welcomeContent);
|
||||
}
|
||||
|
||||
await this.dmService.welcomePrivate(member);
|
||||
|
||||
} catch (error) {
|
||||
console.error("error while sending a welcome command msg:", error);
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
content: `erfolgreich welcome command: ${member.user.username}`,
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("fehler bei welcome command", error);
|
||||
await interaction.reply({
|
||||
content:
|
||||
"fehler bei welcome command",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async checkRole(member: GuildMember) {
|
||||
let hasRole = false;
|
||||
if (member?.roles instanceof GuildMemberRoleManager) {
|
||||
if (member.roles.cache.has(config.discord.roleAdmin) || member.roles.cache.has(config.discord.roleMod)) {
|
||||
console.log("user has permission");
|
||||
hasRole = true;
|
||||
}
|
||||
}
|
||||
return hasRole;
|
||||
}
|
||||
}
|
||||
38
core/src/actions/greeting/greeting.task.ts
Normal file
38
core/src/actions/greeting/greeting.task.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { CronJob } from "cron";
|
||||
import { GreetingService } from "actions/greeting/greeting.service";
|
||||
|
||||
const greetingService = new GreetingService();
|
||||
/*
|
||||
new CronJob(
|
||||
"0 30 6 * * *", // cronTime
|
||||
async () => {
|
||||
console.log("good morning");
|
||||
await greetingService.greet();
|
||||
}, // onTick
|
||||
null, // onComplete
|
||||
true, // start
|
||||
"Europe/Berlin", // timeZone
|
||||
);
|
||||
// job.start() is optional here because of the fourth parameter set to true.
|
||||
|
||||
new CronJob(
|
||||
"0 30 22 * * *",
|
||||
async () => {
|
||||
console.log("good night");
|
||||
await greetingService.sleep();
|
||||
},
|
||||
null,
|
||||
true,
|
||||
"Europe/Berlin",
|
||||
);*/
|
||||
|
||||
new CronJob(
|
||||
"0 0 0 1 1 *",
|
||||
async () => {
|
||||
console.log("happy new year");
|
||||
await greetingService.newYear();
|
||||
},
|
||||
null,
|
||||
true,
|
||||
"Europe/Berlin",
|
||||
);
|
||||
26
core/src/actions/help/help.components.ts
Normal file
26
core/src/actions/help/help.components.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Commands, CommandsMeta } from 'commands';
|
||||
import { AttachmentBuilder, EmbedBuilder } from 'discord.js';
|
||||
|
||||
export default function createEmbed() { // ({ embeds: [exampleEmbed] })
|
||||
console.log("createHelpEmbed()");
|
||||
|
||||
const exampleEmbed = new EmbedBuilder()
|
||||
.setColor(0x004400)
|
||||
//.setTitle("/hilfe")
|
||||
//.setURL("")
|
||||
.setAuthor({ name: "avocadi - befehle", iconURL: "https://media.discordapp.net/attachments/1321933410188656693/1323447010380222474/mo_Avocadi_Avatar_Closeup_2.png?ex=67748b93&is=67733a13&hm=f48efb3523bca5f50e79144c7b41a127c94670e693e3da3dc2e6ffe62ad8a769&=&format=webp&quality=lossless&width=1524&height=1524", url: 'https://git.unom.io/mo/avocadi-bot' })
|
||||
.setDescription(" ")
|
||||
.addFields(
|
||||
{ name: `/${Commands.Enum.giessen}`, value: CommandsMeta.giessen.description },
|
||||
{ name: `/${Commands.Enum.medikamente}`, value: CommandsMeta.medikamente.description },
|
||||
{ name: `/${Commands.Enum.hilfe}`, value: CommandsMeta.hilfe.description },
|
||||
{ name: `/${Commands.Enum.support}`, value: CommandsMeta.support.description },
|
||||
{ name: `/${Commands.Enum.kofi}`, value: CommandsMeta.kofi.description },
|
||||
{ name: `/${Commands.Enum.disboard}`, value: CommandsMeta.disboard.description },
|
||||
{ name: `/${Commands.Enum.discadia}`, value: CommandsMeta.discadia.description },
|
||||
)
|
||||
.setTimestamp()
|
||||
//.setFooter({ text: 'Some footer text here', iconURL: 'https://i.imgur.com/AfFp7pu.png' });
|
||||
;
|
||||
return exampleEmbed;
|
||||
}
|
||||
22
core/src/actions/help/help.service.ts
Normal file
22
core/src/actions/help/help.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { CacheType, Interaction } from "discord.js";
|
||||
import createEmbed from "./help.components";
|
||||
|
||||
export class HelpService {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
exampleEmbed: any;
|
||||
|
||||
constructor() {
|
||||
this.exampleEmbed = createEmbed();
|
||||
}
|
||||
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
console.log("help");
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await interaction.reply({
|
||||
embeds: [this.exampleEmbed],
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
10
core/src/actions/medication/medication.components.ts
Normal file
10
core/src/actions/medication/medication.components.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ButtonBuilder, ButtonStyle } from "discord.js";
|
||||
|
||||
export const yesButton = new ButtonBuilder()
|
||||
.setCustomId("yesMedication")
|
||||
.setLabel("ja")
|
||||
.setStyle(ButtonStyle.Primary);
|
||||
export const noButton = new ButtonBuilder()
|
||||
.setCustomId("noMedication")
|
||||
.setLabel("noch nicht")
|
||||
.setStyle(ButtonStyle.Secondary);
|
||||
250
core/src/actions/medication/medication.service.ts
Normal file
250
core/src/actions/medication/medication.service.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { CronJob } from "cron";
|
||||
import { getRandomInt } from "lib/utils";
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
type ButtonInteraction,
|
||||
ButtonStyle,
|
||||
type ChatInputCommandInteraction,
|
||||
type ModalSubmitInteraction,
|
||||
type CacheType,
|
||||
type Interaction,
|
||||
} from "discord.js";
|
||||
import { yesButton, noButton } from "./medication.components";
|
||||
import { db } from "db";
|
||||
import { usersTable } from "db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export class MedicationService {
|
||||
medication: string;
|
||||
tookMedication: boolean;
|
||||
|
||||
constructor() {
|
||||
this.medication = "";
|
||||
this.tookMedication = false;
|
||||
}
|
||||
|
||||
async askMedication() {
|
||||
const channels = client.channels;
|
||||
|
||||
const channel = channels.cache.get(config.discord.channelIdBot);
|
||||
|
||||
// funkt noch nicht, beides
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(yesButton);
|
||||
row.addComponents(noButton);
|
||||
|
||||
if (
|
||||
channel?.isTextBased &&
|
||||
channel?.isSendable() &&
|
||||
this.tookMedication === false
|
||||
) {
|
||||
await channel.send({
|
||||
content: "hast du schon deine medis genommen? :)",
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
components: [row as any],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getMedication() {
|
||||
// todo
|
||||
}
|
||||
|
||||
setMedication() {
|
||||
const reply = this.getReply();
|
||||
console.log("medication");
|
||||
|
||||
// this.medication = input von user:in hier rein;
|
||||
return {
|
||||
reply,
|
||||
};
|
||||
}
|
||||
|
||||
getReply() {
|
||||
return "medication reminder";
|
||||
}
|
||||
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
if (interaction.isModalSubmit()) {
|
||||
await this.handleModalSubmit(interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await this.handleChatInputCommand(interaction);
|
||||
return;
|
||||
}
|
||||
if (interaction.isButton()) {
|
||||
await this.handleButton(interaction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async handleModalSubmit(interaction: ModalSubmitInteraction<CacheType>) {
|
||||
switch (interaction.customId) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async handleButton(interaction: ButtonInteraction<CacheType>) {
|
||||
console.log("button interaction");
|
||||
|
||||
const discordId = Number.parseInt(interaction.user.id);
|
||||
const name = interaction.user.displayName;
|
||||
|
||||
console.log(`userid: ${discordId}`);
|
||||
|
||||
try {
|
||||
const userId = await this.ensureUserExists(discordId, name);
|
||||
console.log(`userid: ${userId}`);
|
||||
|
||||
const tookMedication = interaction.customId === "yesMedication";
|
||||
|
||||
await interaction.reply({
|
||||
content: tookMedication
|
||||
? "das hast du toll gemacht <3 mach weiter so :3"
|
||||
: "das passiert mal... aber versuch sie heute noch zu nehmen, oki? :)",
|
||||
});
|
||||
await this.logMedication(userId, tookMedication);
|
||||
} catch (error) {
|
||||
console.error("error ensuring user exists:", error);
|
||||
await interaction.reply({
|
||||
content:
|
||||
"es gab einen fehler beim verarbeiten deiner anfrage :( versuch es bitte spaeter nochmal, oki? c:",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async handleChatInputCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
) {
|
||||
const result = this.setMedication();
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(yesButton);
|
||||
row.addComponents(noButton);
|
||||
await interaction.reply({
|
||||
content: result.reply,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
components: [row as any],
|
||||
});
|
||||
}
|
||||
|
||||
/**, um die Benutzerdaten in die Datenbank zu schreiben.
|
||||
* @param discordId unique user id
|
||||
* @param name name how the user wants to get called by avocadi
|
||||
* @param tookMedication if user took medication
|
||||
*/
|
||||
async logMedication(id: number, tookMedication: boolean) {
|
||||
try {
|
||||
await db
|
||||
.update(usersTable)
|
||||
.set({
|
||||
took_medication_today: Number(tookMedication),
|
||||
})
|
||||
.where(eq(usersTable.id, id));
|
||||
|
||||
/* await db.insert(usersTable).values({
|
||||
name: name,
|
||||
discord_id: discordId,
|
||||
took_medication_today: Number(tookMedication),
|
||||
}); */
|
||||
|
||||
console.log(`user with id ${id} saved`);
|
||||
} catch (error) {
|
||||
console.error("error while saving in db:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async getNameByDiscordId(discordId: number): Promise<string | null> {
|
||||
const result = await db
|
||||
.select({
|
||||
name: usersTable.name,
|
||||
})
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.discord_id, discordId))
|
||||
.limit(1);
|
||||
|
||||
if (result.length > 0) {
|
||||
console.log("user found");
|
||||
return result[0].name;
|
||||
}
|
||||
console.log("name not found");
|
||||
return "";
|
||||
}
|
||||
|
||||
async findUserIdByDiscordId(discordId: number): Promise<number | null> {
|
||||
try {
|
||||
const result = await db
|
||||
.select({
|
||||
id: usersTable.id,
|
||||
})
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.discord_id, discordId))
|
||||
.limit(1);
|
||||
|
||||
if (result.length > 0) {
|
||||
console.log(`ID für Discord-ID ${discordId} gefunden: ${result[0].id}`);
|
||||
return result[0].id;
|
||||
}
|
||||
console.log(`no id for discordId ${discordId} found`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`error while calling id for discordId ${discordId}:`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async ensureUserExists(discordId: number, name: string): Promise<number> {
|
||||
try {
|
||||
const userId = await this.findUserIdByDiscordId(discordId);
|
||||
|
||||
if (userId !== null) {
|
||||
console.log(`entry for discordID ${discordId} already exists`);
|
||||
return userId;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`found no entry for discordID ${discordId}. creating new entry`,
|
||||
);
|
||||
|
||||
const result = await db
|
||||
.insert(usersTable)
|
||||
.values({
|
||||
name: name,
|
||||
discord_id: discordId,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.returning({
|
||||
id: usersTable.id,
|
||||
});
|
||||
|
||||
if (result.length > 0) {
|
||||
const newUserId = result[0].id;
|
||||
console.log(
|
||||
`new user with discordId ${discordId} and name ${name} created. id: ${newUserId}`,
|
||||
);
|
||||
return newUserId;
|
||||
}
|
||||
|
||||
// check again if user is now existing
|
||||
const newUserId = await this.findUserIdByDiscordId(discordId);
|
||||
if (newUserId !== null) {
|
||||
console.log(`user created in parallel. fetched id: ${newUserId}`);
|
||||
return newUserId;
|
||||
}
|
||||
throw new Error(`creating a new user for discordId ${discordId} failed`);
|
||||
} catch (error) {
|
||||
console.error("error while creating or calling the user:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
core/src/actions/medication/medication.task.ts
Normal file
15
core/src/actions/medication/medication.task.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { CronJob } from "cron";
|
||||
import { MedicationService } from "actions/medication/medication.service";
|
||||
|
||||
const medicationService = new MedicationService();
|
||||
|
||||
/*new CronJob(
|
||||
"0 0 19 * * *", // cronTime
|
||||
async () => {
|
||||
console.log("askMedication()");
|
||||
await medicationService.askMedication();
|
||||
}, // onTick
|
||||
null, // onComplete
|
||||
true, // start
|
||||
"Europe/Berlin", // timeZone
|
||||
);*/
|
||||
0
core/src/actions/pomodoro/pomodoro.components.ts
Normal file
0
core/src/actions/pomodoro/pomodoro.components.ts
Normal file
42
core/src/actions/pomodoro/pomodoro.controller.ts
Normal file
42
core/src/actions/pomodoro/pomodoro.controller.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
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 !== oldState.channelId;
|
||||
|
||||
if (leftPomodoroVC && this.activePomodoros.has(userId)) {
|
||||
console.log("pomodoro left");
|
||||
this.pomodoroService.stopPomodoro(userId);
|
||||
this.activePomodoros.delete(userId);
|
||||
}
|
||||
|
||||
if (joinedPomodoroVC && !this.activePomodoros.has(userId)) {
|
||||
console.log("pomodoro join");
|
||||
const member = newState.member;
|
||||
const vchannel = newState.channel;
|
||||
if (!member || !vchannel) return;
|
||||
|
||||
this.activePomodoros.add(userId);
|
||||
this.pomodoroService.startPomodoroLoop(member, vchannel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
67
core/src/actions/pomodoro/pomodoro.service.ts
Normal file
67
core/src/actions/pomodoro/pomodoro.service.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
0
core/src/actions/reactRole/reactRoles.components.ts
Normal file
0
core/src/actions/reactRole/reactRoles.components.ts
Normal file
69
core/src/actions/reactRole/reactRoles.service.ts
Normal file
69
core/src/actions/reactRole/reactRoles.service.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { DmService } from "actions/dm/dm.service";
|
||||
import config from "config";
|
||||
import type { CacheType, ChatInputCommandInteraction, Guild, GuildMember, MessageReaction, PartialMessageReaction, PartialUser, User } from "discord.js";
|
||||
|
||||
export class ReactRolesService {
|
||||
dmService: DmService;
|
||||
|
||||
constructor() {
|
||||
this.dmService = new DmService();
|
||||
}
|
||||
|
||||
async roleMention(reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser, add: boolean) {
|
||||
if (!await this.validMsg(reaction.message.id)) return;
|
||||
try {
|
||||
await reaction.fetch();
|
||||
const guild = reaction.message.guild;
|
||||
if (!guild) return;
|
||||
const member = await this.getUser(guild, user);
|
||||
if (!member) return;
|
||||
add ? await this.giveRoleMention(member, guild, user) : await this.removeRoleMention(member, guild, user);
|
||||
await this.dmService.roleMentionDm(member, add);
|
||||
} catch (error) {
|
||||
console.error('smt went wring while roleMention():', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async giveRoleMention(member: GuildMember, guild: Guild, user: User | PartialUser) {
|
||||
this.updateRoleMention(member, guild, user, true);
|
||||
}
|
||||
async removeRoleMention(member: GuildMember, guild: Guild, user: User | PartialUser) {
|
||||
this.updateRoleMention(member, guild, user, false);
|
||||
}
|
||||
|
||||
async updateRoleMention(member: GuildMember, guild: Guild, user: User | PartialUser, add: boolean) {
|
||||
try {
|
||||
|
||||
const role = guild.roles.cache.get(config.discord.roleMention);
|
||||
if (!role) {
|
||||
console.error("role ot found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (add === member.roles.cache.has(role.id)) {
|
||||
console.log(`${member.user.tag} hat die Rolle *streber* bereits.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await (add ? member.roles.add(role) : member.roles.remove(role));
|
||||
console.log(`role *streber* successfully ${add ? "added to" : "removed from"} ${member.user.tag}.`);
|
||||
} catch (error) {
|
||||
console.error(`error while ${add ? "added to" : "removed from"} RoleMention:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async validMsg(id: string) {
|
||||
return id === config.discord.rolesMsg;
|
||||
}
|
||||
|
||||
async getUser(guild: Guild, user: User | PartialUser) {
|
||||
try {
|
||||
return await guild.members.fetch(user.id);
|
||||
} catch (error) {
|
||||
console.error("error fetching user:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
core/src/actions/support/support.components.ts
Normal file
18
core/src/actions/support/support.components.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { createEmbed } from "actions/customMessage/customMessage.components";
|
||||
|
||||
const kofiTitle = "ko-fi";
|
||||
const kofiLink = "[ko-fi.com/avocadi](https://ko-fi.com/avocadi)";
|
||||
const kofiDesc = "gerne kannst du uns eine kleine spende ueber ko-fi zukommen lassen!";
|
||||
|
||||
const disboardTitle = "disboard";
|
||||
const disboardLink = "[disboard.org/de/server/1316153371899592774](https://disboard.org/de/server/1316153371899592774)";
|
||||
const disboardDesc = "hier kannst du eine bewertung schreiben <3";
|
||||
|
||||
const discadiaTitle = "discadia";
|
||||
const discadiaLink = "[discadia.com/server/avocadi](https://discadia.com/server/avocadi/)";
|
||||
const discadiaDesc = "du moechtest fuer uns voten? dann klicke auf den link :)";
|
||||
|
||||
export const supportContent = createEmbed("support", (`${kofiTitle}: ${kofiLink}\n${disboardTitle}: ${disboardLink}\n${discadiaTitle}: ${discadiaLink}`));
|
||||
export const kofiContent = createEmbed(kofiTitle, (`${kofiDesc}\n${kofiLink}`));
|
||||
export const disboardContent = createEmbed(disboardTitle, (`${disboardDesc}\n${disboardLink}`));
|
||||
export const discadiaContent = createEmbed(discadiaTitle, (`${discadiaDesc}\n${discadiaLink}`));
|
||||
93
core/src/actions/support/support.service.ts
Normal file
93
core/src/actions/support/support.service.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
import { getRandomInt } from "lib/utils";
|
||||
import {
|
||||
kofiContent,
|
||||
supportContent,
|
||||
discadiaContent,
|
||||
disboardContent,
|
||||
} from "./support.components.ts";
|
||||
import type {
|
||||
CacheType,
|
||||
Interaction,
|
||||
ChatInputCommandInteraction,
|
||||
} from "discord.js";
|
||||
import { Commands, type CommandsType } from "commands/index.ts";
|
||||
|
||||
export class SupportService {
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await this.handleChatInputCommand(interaction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async handleChatInputCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
) {
|
||||
const commandName = interaction.commandName as CommandsType;
|
||||
switch (commandName) {
|
||||
case Commands.Enum.support:
|
||||
await this.supportCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.kofi:
|
||||
await this.kofiCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.disboard:
|
||||
await this.disboardCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.discadia:
|
||||
await this.discadiaCommand(interaction);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
async supportCommand(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
console.log("supportCommand");
|
||||
try {
|
||||
await interaction.reply({
|
||||
embeds: [supportContent],
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error while sending supportCommand msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async kofiCommand(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
console.log("kofiCommand");
|
||||
try {
|
||||
await interaction.reply({
|
||||
embeds: [kofiContent],
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error while sending kofiCommand msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async disboardCommand(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
console.log("disboardCommand");
|
||||
try {
|
||||
await interaction.reply({
|
||||
embeds: [disboardContent],
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error while sending disboardCommand msg:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async discadiaCommand(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||
console.log("discadiaCommand");
|
||||
try {
|
||||
await interaction.reply({
|
||||
embeds: [discadiaContent],
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error while sending discadiaCommand msg:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
99
core/src/actions/waterMe/waterMe.service.ts
Normal file
99
core/src/actions/waterMe/waterMe.service.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { CronJob } from "cron";
|
||||
import { getRandomInt } from "lib/utils";
|
||||
import config from "config";
|
||||
import client from "lib/client";
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
type CacheType,
|
||||
type Interaction,
|
||||
} from "discord.js";
|
||||
|
||||
export class WaterMeService {
|
||||
waterLevel: number;
|
||||
|
||||
private thirsty = 3 as const;
|
||||
private enough = 10 as const;
|
||||
|
||||
constructor() {
|
||||
this.waterLevel = 0;
|
||||
}
|
||||
|
||||
getReply() {
|
||||
const thirstyReplies = [
|
||||
"... wow das wars schon??? ich brauche noch mehr wasser :(",
|
||||
"dankeeeee!!!! ich waer fast verdurstet :(((",
|
||||
"*roelpssssss*",
|
||||
];
|
||||
|
||||
const fullReplies = [
|
||||
"langsam reicht es :o",
|
||||
"poah, das hat gut getan",
|
||||
"das ist krass :3",
|
||||
];
|
||||
|
||||
const tooMuchReplies = [
|
||||
"ES REICHT!!!!",
|
||||
"bitte hoer auf, ich platze gleich :(",
|
||||
];
|
||||
|
||||
if (this.waterLevel <= this.thirsty) {
|
||||
return thirstyReplies[getRandomInt(0, thirstyReplies.length - 1)];
|
||||
}
|
||||
if (this.waterLevel > this.thirsty && this.waterLevel <= this.enough) {
|
||||
return fullReplies[getRandomInt(0, fullReplies.length - 1)];
|
||||
}
|
||||
if (this.waterLevel > this.enough) {
|
||||
return tooMuchReplies[getRandomInt(0, tooMuchReplies.length - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
async isThirsty() {
|
||||
const channels = client.channels;
|
||||
|
||||
const channel = channels.cache.get(config.discord.channelIdBot);
|
||||
|
||||
if (
|
||||
channel?.isTextBased &&
|
||||
channel?.isSendable() &&
|
||||
this.waterLevel <= this.thirsty
|
||||
) {
|
||||
await channel.send({ content: "ich brauche wasser :(" });
|
||||
}
|
||||
}
|
||||
|
||||
waterMe() {
|
||||
const reply = this.getReply();
|
||||
|
||||
this.waterLevel++;
|
||||
console.log(this.waterLevel);
|
||||
|
||||
return {
|
||||
reply,
|
||||
};
|
||||
}
|
||||
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
const result = this.waterMe();
|
||||
|
||||
const moreButton = new ButtonBuilder()
|
||||
.setCustomId("moreWater")
|
||||
.setLabel("mehr")
|
||||
.setStyle(ButtonStyle.Secondary);
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(moreButton);
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await interaction.reply({
|
||||
content: result.reply,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
components: [row as any],
|
||||
});
|
||||
} else if (interaction.isButton()) {
|
||||
await interaction.reply({
|
||||
content: result.reply,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
16
core/src/actions/waterMe/waterMe.task.ts
Normal file
16
core/src/actions/waterMe/waterMe.task.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { CronJob } from "cron";
|
||||
import { WaterMeService } from "actions/waterMe/waterMe.service";
|
||||
|
||||
const waterMeService = new WaterMeService();
|
||||
/*
|
||||
new CronJob(
|
||||
"0 0 20 * * *", // cronTime
|
||||
async () => {
|
||||
console.log("isThirsty()");
|
||||
await waterMeService.isThirsty();
|
||||
}, // onTick
|
||||
null, // onComplete
|
||||
true, // start
|
||||
"Europe/Berlin", // timeZone
|
||||
);*/
|
||||
// job.start() is optional here because of the fourth parameter set to true.
|
||||
123
core/src/commands/index.ts
Normal file
123
core/src/commands/index.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { SlashCommandBuilder, userMention } from "discord.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export const Commands = z.enum(["giessen", "medikamente", "hilfe", "support", "kofi", "disboard", "discadia", "accept", "welcome", "embed", "message", "reminder", "version"]);
|
||||
|
||||
export const CommandsMeta: Record<z.output<typeof Commands>, { description: string }> = {
|
||||
giessen: {
|
||||
description: "giess mich mit etwas wasser :3"
|
||||
},
|
||||
medikamente: {
|
||||
description: "ich erinnere dich gerne daran, deine medikamente zu nehmen! :)"
|
||||
},
|
||||
hilfe: {
|
||||
description: "ich schreibe dir auf, was du alles mit mir machen kannst :)"
|
||||
},
|
||||
support: {
|
||||
description: "unterstuetze uns! link zu unserem ko-fi, disboard und discardia c:"
|
||||
},
|
||||
kofi: {
|
||||
description: "link zu unserem ko-fi (spendenportal):"
|
||||
},
|
||||
disboard: {
|
||||
description: "link zu disboard, hier kannst du uns bewerten!"
|
||||
},
|
||||
discadia: {
|
||||
description: "link zu discadia, hier kannst du fuer uns voten!"
|
||||
},
|
||||
accept: {
|
||||
description: "admin use only"
|
||||
},
|
||||
welcome: {
|
||||
description: "admin use only"
|
||||
},
|
||||
embed: {
|
||||
description: "admin use only"
|
||||
},
|
||||
message: {
|
||||
description: "admin use only"
|
||||
},
|
||||
reminder: {
|
||||
description: "admin use only"
|
||||
},
|
||||
version: {
|
||||
description: "admin use only"
|
||||
}
|
||||
}
|
||||
|
||||
export type CommandsType = z.output<typeof Commands>;
|
||||
|
||||
export default function getCommands() {
|
||||
const commands = [
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.giessen)
|
||||
.setDescription(CommandsMeta.giessen.description),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.medikamente)
|
||||
.setDescription(CommandsMeta.medikamente.description),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.hilfe)
|
||||
.setDescription(CommandsMeta.hilfe.description),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.support)
|
||||
.setDescription(CommandsMeta.support.description),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.kofi)
|
||||
.setDescription(CommandsMeta.kofi.description),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.disboard)
|
||||
.setDescription(CommandsMeta.disboard.description),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.discadia)
|
||||
.setDescription(CommandsMeta.discadia.description),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.accept)
|
||||
.setDescription(CommandsMeta.accept.description)
|
||||
.addStringOption(option =>
|
||||
option.setName('input')
|
||||
.setDescription('input for bot')
|
||||
.setRequired(true)),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.welcome)
|
||||
.setDescription(CommandsMeta.welcome.description)
|
||||
.addStringOption(option =>
|
||||
option.setName('input')
|
||||
.setDescription('input for bot')
|
||||
.setRequired(true)),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.embed)
|
||||
.setDescription(CommandsMeta.embed.description)
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('title')
|
||||
.setRequired(true))
|
||||
.addStringOption(option =>
|
||||
option.setName('description')
|
||||
.setDescription('description')
|
||||
.setRequired(true))
|
||||
.addBooleanOption(option =>
|
||||
option.setName('timestamp')
|
||||
.setDescription('timestamp bool')
|
||||
.setRequired(false)),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.message)
|
||||
.setDescription(CommandsMeta.message.description)
|
||||
.addStringOption(option =>
|
||||
option.setName('input')
|
||||
.setDescription('input for bot')
|
||||
.setRequired(true)),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.reminder)
|
||||
.setDescription(CommandsMeta.reminder.description)
|
||||
.addStringOption(option =>
|
||||
option.setName('input')
|
||||
.setDescription('input for bot')
|
||||
.setRequired(true)),
|
||||
new SlashCommandBuilder()
|
||||
.setName(Commands.Enum.version)
|
||||
.setDescription(CommandsMeta.version.description),
|
||||
|
||||
].map((command) => command.toJSON());
|
||||
|
||||
return commands;
|
||||
}
|
||||
24
core/src/components/index.ts
Normal file
24
core/src/components/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
import { Commands, CommandsMeta } from "commands";
|
||||
|
||||
export default function getEmbed() { // channel.send({ embeds: [exampleEmbed] });
|
||||
const exampleEmbed = new EmbedBuilder()
|
||||
.setColor(0x0099FF)
|
||||
.setTitle('Some title')
|
||||
.setURL('https://discord.js.org/')
|
||||
.setAuthor({ name: 'Some name', iconURL: 'https://i.imgur.com/AfFp7pu.png', url: 'https://discord.js.org' })
|
||||
.setDescription('Some description here')
|
||||
.setThumbnail('https://i.imgur.com/AfFp7pu.png')
|
||||
.addFields(
|
||||
{ name: Commands.Enum.giessen, value: CommandsMeta.giessen.description },
|
||||
{ name: Commands.Enum.medikamente, value: CommandsMeta.medikamente.description },
|
||||
{ name: Commands.Enum.hilfe, value: CommandsMeta.hilfe.description },
|
||||
{ name: '\u200B', value: '\u200B' },
|
||||
{ name: 'Inline field title', value: 'Some value here', inline: true },
|
||||
)
|
||||
.addFields({ name: 'Inline field title', value: 'Some value here', inline: true })
|
||||
.setImage('https://i.imgur.com/AfFp7pu.png')
|
||||
.setTimestamp()
|
||||
.setFooter({ text: 'Some footer text here', iconURL: 'https://i.imgur.com/AfFp7pu.png' });
|
||||
return exampleEmbed;
|
||||
}
|
||||
42
core/src/config.ts
Normal file
42
core/src/config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export default {
|
||||
discord: {
|
||||
version: 251218,
|
||||
// avocadi
|
||||
serverID: process.env.DISCORD_SERVER_ID || "",
|
||||
// text channel
|
||||
channelIdBump: process.env.DISCORD_CHANNEL_ID_BUMP || "",
|
||||
channelIdBot: process.env.DISCORD_CHANNEL_ID_BOT || "",
|
||||
channelIdFeedback: process.env.DISCORD_CHANNEL_ID_FEEDBACK || "",
|
||||
channelIdLog: process.env.DISCORD_CHANNEL_ID_NOTIFICATION || "",
|
||||
channelIdWelcome: process.env.DISCORD_CHANNEL_ID_WELCOME || "",
|
||||
channelIdRules: process.env.DISCORD_CHANNEL_ID_RULE || "",
|
||||
channelIdNews: process.env.DISCORD_CHANNEL_ID_NEWS || "",
|
||||
channelIdIntroduction: process.env.DISCORD_CHANNEL_ID_INTRODUCTION || "",
|
||||
channelIdOffTopic: process.env.DISCORD_CHANNEL_ID_OFF_TOPIC || "",
|
||||
channelIdHelp: process.env.DISCORD_CHANNEL_ID_HELP || "",
|
||||
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
|
||||
roleBot: process.env.BOT || "",
|
||||
roleStudy: process.env.PEOPLE || "",
|
||||
roleMod: process.env.MOD || "",
|
||||
roleAdmin: process.env.ADMIN || "",
|
||||
roleMention: process.env.MENTION || "",
|
||||
|
||||
rolesMsg: process.env.ROLES_NOTIFICATION_MSG || "",
|
||||
rolesMsgTest: process.env.ROLES_MSG_TEST || "",
|
||||
// other
|
||||
applicationId: process.env.DISCORD_APPLICATION_ID || "",
|
||||
token: process.env.DISCORD_TOKEN || "",
|
||||
|
||||
botId: process.env.BOT_ID || "",
|
||||
myId: process.env.MY_ID || "",
|
||||
enricoId: process.env.ENRICO_ID || "",
|
||||
},
|
||||
};
|
||||
36
core/src/db/index.ts
Normal file
36
core/src/db/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||
import { Database } from 'bun:sqlite';
|
||||
import { usersTable } from './schema'; // Importiere die Tabelle hier
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
const dbPath = process.env.DB_FILE_NAME!;
|
||||
const dbDir = path.dirname(dbPath);
|
||||
|
||||
// Erstelle das Verzeichnis, wenn es noch nicht existiert
|
||||
if (!fs.existsSync(dbDir)) {
|
||||
fs.mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Wenn die Datenbankdatei nicht existiert, dann erstelle sie
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
fs.writeFileSync(dbPath, ''); // Leere Datei erstellen
|
||||
}
|
||||
|
||||
// Datenbankverbindung herstellen
|
||||
const client = new Database(dbPath);
|
||||
|
||||
// Stelle sicher, dass die Tabelle existiert
|
||||
client.run(`
|
||||
CREATE TABLE IF NOT EXISTS users_table (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
discord_id INTEGER NOT NULL UNIQUE,
|
||||
join_streak INTEGER,
|
||||
last_joined_at INTEGER,
|
||||
took_medication_today INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
`);
|
||||
|
||||
export const db = drizzle(client);
|
||||
10
core/src/db/schema.ts
Normal file
10
core/src/db/schema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const usersTable = sqliteTable("users_table", {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
name: text().notNull(),
|
||||
discord_id: integer().notNull().unique(),
|
||||
join_streak: integer(),
|
||||
last_joined_at: integer({ mode: "timestamp" }),
|
||||
took_medication_today: integer().notNull().default(0),
|
||||
});
|
||||
301
core/src/discord.controller.ts
Normal file
301
core/src/discord.controller.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
import { Commands, type CommandsType } from "commands";
|
||||
import {
|
||||
ChannelType,
|
||||
Client,
|
||||
Events,
|
||||
IntentsBitField,
|
||||
type VoiceState,
|
||||
type ButtonInteraction,
|
||||
type CacheType,
|
||||
type ChatInputCommandInteraction,
|
||||
type Interaction,
|
||||
type ModalSubmitInteraction,
|
||||
ActivityType,
|
||||
type Channel,
|
||||
} from "discord.js";
|
||||
import client from "lib/client";
|
||||
import EventEmitter from "node:events";
|
||||
import DiscordService from "discord.service";
|
||||
import { WaterMeService } from "actions/waterMe/waterMe.service";
|
||||
import { MedicationService } from "actions/medication/medication.service";
|
||||
import { HelpService } from "actions/help/help.service";
|
||||
import { SupportService } from "actions/support/support.service";
|
||||
import { GreetingService } from "actions/greeting/greeting.service";
|
||||
import { ActivityService } from "actions/activity/activity.service";
|
||||
import { DmService } from "actions/dm/dm.service";
|
||||
import { CustomMessageService } from "actions/customMessage/customMessage.service";
|
||||
import { DynamicVChannelService } from "actions/dynamicVChannel/dynamicVChannel.service";
|
||||
import { DebugService } from "actions/debug/debug.service";
|
||||
import { ReactRolesService } from "actions/reactRole/reactRoles.service";
|
||||
import config from "config";
|
||||
|
||||
export default class DiscordController extends EventEmitter {
|
||||
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 dynamicVChannelService: DynamicVChannelService;
|
||||
private debugService: DebugService;
|
||||
|
||||
private channelListeners = new Map();
|
||||
private reactRolesService: ReactRolesService;
|
||||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let channelListeners = new Map();
|
||||
this.discordService = new DiscordService();
|
||||
this.waterMeService = new WaterMeService();
|
||||
this.greetingService = new GreetingService();
|
||||
this.medicationService = new MedicationService();
|
||||
this.helpService = new HelpService();
|
||||
this.supportService = new SupportService();
|
||||
this.activityService = new ActivityService();
|
||||
this.dmService = new DmService();
|
||||
this.customMessageService = new CustomMessageService();
|
||||
this.dynamicVChannelService = new DynamicVChannelService();
|
||||
this.debugService = new DebugService();
|
||||
this.reactRolesService = new ReactRolesService();
|
||||
|
||||
client.on("messageReactionAdd", async (reaction, user) => {
|
||||
await this.reactRolesService.roleMention(reaction, user, true);
|
||||
});
|
||||
|
||||
client.on("messageReactionRemove", async (reaction, user) => {
|
||||
await this.reactRolesService.roleMention(reaction, user, false);
|
||||
});
|
||||
|
||||
// log when running
|
||||
client.once("ready", async () => {
|
||||
const channels = client.channels;
|
||||
const logChannel = channels.cache.get(config.discord.channelIdLog);
|
||||
|
||||
if (logChannel?.isTextBased() && logChannel?.isSendable()) {
|
||||
try {
|
||||
console.log("bot is online");
|
||||
await logChannel.send("wieder online!!!");
|
||||
} catch (error) {
|
||||
console.error("failed to send online message:", error);
|
||||
}
|
||||
} else {
|
||||
console.error("log channel is not valid or sendable.");
|
||||
}
|
||||
await this.setActivity(100);
|
||||
console.log("ready");
|
||||
});
|
||||
|
||||
process.on("exit", async () => {
|
||||
const channels = client.channels;
|
||||
const logChannel = channels.cache.get(config.discord.channelIdLog);
|
||||
await this.handleShutdown(logChannel);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
const channels = client.channels;
|
||||
const logChannel = channels.cache.get(config.discord.channelIdLog);
|
||||
await this.handleShutdown(logChannel);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
const channels = client.channels;
|
||||
const logChannel = channels.cache.get(config.discord.channelIdLog);
|
||||
await this.handleShutdown(logChannel);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// listen for interactions
|
||||
client.on("interactionCreate", this.handleInteraction.bind(this));
|
||||
|
||||
client.on("messageCreate", async (message) => {
|
||||
console.log(message.id);
|
||||
if (message.channel.type === ChannelType.DM) {
|
||||
console.log("got msg");
|
||||
await this.dmService.forward(message);
|
||||
}
|
||||
});
|
||||
|
||||
client.on("guildMemberAdd", async (member) => {
|
||||
await this.greetingService.welcome(member);
|
||||
});
|
||||
|
||||
client.on(
|
||||
Events.VoiceStateUpdate,
|
||||
async (oldState: VoiceState, newState: VoiceState) => {
|
||||
// check if user joined a vc
|
||||
if (
|
||||
(!oldState.channelId && newState.channelId) ||
|
||||
oldState.channelId !== newState.channelId
|
||||
) {
|
||||
// check if right vc
|
||||
if (
|
||||
newState.channelId === config.discord.vchannelIdForTwo ||
|
||||
newState.channelId === config.discord.vchannelIdForThree ||
|
||||
newState.channelId === config.discord.vchannelIdForFour ||
|
||||
newState.channelId === config.discord.vchannelIdForGroup
|
||||
) {
|
||||
const channel = newState.channel;
|
||||
if (!channel) {
|
||||
console.error("channel not found");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newChannel = await this.dynamicVChannelService.createVChannel(
|
||||
newState,
|
||||
channel,
|
||||
);
|
||||
// move user in new channel
|
||||
await newState.setChannel(newChannel);
|
||||
// create specific listener for channel
|
||||
const channelListener = async (
|
||||
oldState: VoiceState,
|
||||
newState: VoiceState,
|
||||
) => {
|
||||
channelListeners =
|
||||
await this.dynamicVChannelService.deleteVChannel(
|
||||
oldState,
|
||||
newState,
|
||||
newChannel,
|
||||
channelListeners,
|
||||
channelListener,
|
||||
);
|
||||
};
|
||||
// save listener in map
|
||||
channelListeners.set(newChannel.id, channelListener);
|
||||
// add listener
|
||||
client.on(Events.VoiceStateUpdate, channelListener);
|
||||
} catch (error) {
|
||||
console.error("error while duplicating channel", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
console.log(`----------------\nversion ${config.discord.version}\n----------------`);
|
||||
}
|
||||
|
||||
async setActivity(state: number) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
client.user?.setActivity(" ", { type: 0 });
|
||||
console.log("set activity");
|
||||
client.user?.setPresence({
|
||||
status: "invisible",
|
||||
});
|
||||
break;
|
||||
default:
|
||||
client.user?.setActivity("spielt sudoku", { type: 0 });
|
||||
console.log("set activity");
|
||||
client.user?.setPresence({
|
||||
status: "online",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async handleShutdown(logChannel: Channel | undefined) {
|
||||
if (logChannel?.isTextBased() && logChannel?.isSendable()) {
|
||||
try {
|
||||
await logChannel.send("bot is going offline...");
|
||||
} catch (error) {
|
||||
console.error("failed to send offline message:", error);
|
||||
}
|
||||
} else {
|
||||
console.error("log channel is not valid or sendable.");
|
||||
}
|
||||
await this.setActivity(0);
|
||||
console.log("bot is offline.");
|
||||
}
|
||||
|
||||
|
||||
async init() {
|
||||
await this.discordService.init();
|
||||
}
|
||||
|
||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
||||
if (interaction.isModalSubmit()) {
|
||||
await this.handleModalSubmit(interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await this.handleChatInputCommand(interaction);
|
||||
return;
|
||||
}
|
||||
if (interaction.isButton()) {
|
||||
await this.handleButton(interaction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async handleButton(interaction: ButtonInteraction<CacheType>) {
|
||||
const { customId } = interaction;
|
||||
console.log(interaction.customId);
|
||||
|
||||
if (customId.toLowerCase().includes("moreWater")) {
|
||||
await this.waterMeService.handleInteraction(interaction);
|
||||
}
|
||||
if (customId.toLowerCase().includes("medication")) {
|
||||
await this.medicationService.handleInteraction(interaction);
|
||||
}
|
||||
}
|
||||
|
||||
async handleChatInputCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
) {
|
||||
const commandName = interaction.commandName as CommandsType;
|
||||
|
||||
// add commands
|
||||
switch (commandName) {
|
||||
case Commands.Enum.giessen:
|
||||
await this.waterMeService.handleInteraction(interaction); // zu chatinputcommand wechseln
|
||||
return;
|
||||
case Commands.Enum.medikamente:
|
||||
await this.medicationService.handleChatInputCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.hilfe:
|
||||
await this.helpService.handleInteraction(interaction); // zu chatinputcommand wechseln
|
||||
return;
|
||||
case Commands.Enum.support:
|
||||
case Commands.Enum.kofi:
|
||||
case Commands.Enum.disboard:
|
||||
case Commands.Enum.discadia:
|
||||
await this.supportService.handleInteraction(interaction);
|
||||
return;
|
||||
case Commands.Enum.accept:
|
||||
await this.greetingService.handleChatInputCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.welcome:
|
||||
await this.greetingService.handleChatInputCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.embed:
|
||||
case Commands.Enum.message:
|
||||
await this.customMessageService.handleChatInputCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.reminder:
|
||||
await this.greetingService.handleChatInputCommand(interaction);
|
||||
return;
|
||||
case Commands.Enum.version:
|
||||
await this.debugService.handleChatInputCommand(interaction);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// wenn neues fenster durch buttonclick or so
|
||||
async handleModalSubmit(interaction: ModalSubmitInteraction<CacheType>) {
|
||||
const { customId } = interaction;
|
||||
|
||||
switch (customId) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
core/src/discord.service.ts
Normal file
26
core/src/discord.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Routes } from "discord.js";
|
||||
import { REST } from "@discordjs/rest";
|
||||
import config from "config";
|
||||
import getCommands from "commands";
|
||||
|
||||
export default class DiscordService {
|
||||
rest: REST;
|
||||
|
||||
constructor() {
|
||||
this.rest = new REST({ version: "10" }).setToken(config.discord.token);
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.rest.put(
|
||||
Routes.applicationCommands(config.discord.applicationId),
|
||||
{
|
||||
body: getCommands(),
|
||||
},
|
||||
);
|
||||
console.log("Successfully added commands");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
core/src/entities/channels/channels.schema.ts
Normal file
29
core/src/entities/channels/channels.schema.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import z from "zod";
|
||||
|
||||
export const TextChannelOptions = [
|
||||
"bump",
|
||||
"bot",
|
||||
"notification",
|
||||
"testing",
|
||||
"news",
|
||||
"rules",
|
||||
"feedback",
|
||||
"welcome",
|
||||
"introduction",
|
||||
"off-topic",
|
||||
"help",
|
||||
"pomodoro",
|
||||
] as const;
|
||||
|
||||
export const VoiceChannelOptions = [
|
||||
"for-two",
|
||||
"for-three",
|
||||
"for-four",
|
||||
"for-group",
|
||||
"custom",
|
||||
"pomodoro-25-5",
|
||||
"pomodoro-50-10",
|
||||
] as const;
|
||||
|
||||
export const TextChannels = z.enum(TextChannelOptions);
|
||||
export const VoiceChannels = z.enum(VoiceChannelOptions);
|
||||
11
core/src/entities/roles/roles.schema.ts
Normal file
11
core/src/entities/roles/roles.schema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import z from "zod";
|
||||
|
||||
export const RoleOptions = [
|
||||
"admin",
|
||||
"mention",
|
||||
"mod",
|
||||
"people",
|
||||
"bot",
|
||||
] as const;
|
||||
|
||||
export const Roles = z.enum(RoleOptions);
|
||||
14
core/src/index.ts
Normal file
14
core/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import "actions/waterMe/waterMe.task";
|
||||
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();
|
||||
const pomodoroController = new PomodoroController();
|
||||
|
||||
discordController.init();
|
||||
22
core/src/lib/client.ts
Normal file
22
core/src/lib/client.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import config from "config";
|
||||
import { Client, GatewayIntentBits, Partials, ChannelType, Events, IntentsBitField } from "discord.js";
|
||||
|
||||
const client = new Client({
|
||||
intents: [IntentsBitField.Flags.Guilds,
|
||||
IntentsBitField.Flags.GuildMembers,
|
||||
IntentsBitField.Flags.GuildModeration,
|
||||
IntentsBitField.Flags.GuildMessages,
|
||||
IntentsBitField.Flags.GuildMessageReactions,
|
||||
IntentsBitField.Flags.GuildMessagePolls,
|
||||
IntentsBitField.Flags.GuildVoiceStates,
|
||||
IntentsBitField.Flags.MessageContent,
|
||||
IntentsBitField.Flags.DirectMessages,
|
||||
IntentsBitField.Flags.DirectMessageReactions,
|
||||
IntentsBitField.Flags.DirectMessageTyping,
|
||||
IntentsBitField.Flags.DirectMessagePolls,],
|
||||
partials: [Partials.Channel, Partials.Message, Partials.Reaction]
|
||||
});
|
||||
|
||||
await client.login(config.discord.token);
|
||||
|
||||
export default client;
|
||||
13
core/src/lib/utils.test.ts
Normal file
13
core/src/lib/utils.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { getRandomInt } from "./utils.ts";
|
||||
|
||||
describe("utils", () => {
|
||||
it("gets a random int", () => {
|
||||
const randomInt = getRandomInt(0, 10);
|
||||
|
||||
expect(randomInt).toBeDefined();
|
||||
expect(randomInt).toBeNumber();
|
||||
expect(randomInt).toBeLessThanOrEqual(10);
|
||||
expect(randomInt).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
5
core/src/lib/utils.ts
Normal file
5
core/src/lib/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function getRandomInt(min: number, max: number) {
|
||||
const nextMin = Math.ceil(min);
|
||||
const nextMax = Math.floor(max);
|
||||
return Math.floor(Math.random() * (nextMax - nextMin + 1)) + nextMin;
|
||||
}
|
||||
13
core/src/permissions/index.ts
Normal file
13
core/src/permissions/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import config from "config";
|
||||
import { GuildMember, GuildMemberRoleManager, type APIInteractionGuildMember } from "discord.js";
|
||||
|
||||
export async function checkPermission(member: GuildMember | APIInteractionGuildMember | null) {
|
||||
let permission = false;
|
||||
if (member?.roles instanceof GuildMemberRoleManager) {
|
||||
if (member.roles.cache.has(config.discord.roleAdmin) || member.roles.cache.has(config.discord.roleMod)) {
|
||||
permission = true;
|
||||
}
|
||||
}
|
||||
console.log("user permission == " + permission);
|
||||
return permission;
|
||||
}
|
||||
Reference in New Issue
Block a user