From c06ac10d09bf841a546a4c092cb46a335b342b50 Mon Sep 17 00:00:00 2001 From: moriese Date: Fri, 27 Dec 2024 01:23:35 +0100 Subject: [PATCH] wip --- .gitignore | 175 ++++++++++++++++++++++++++ Biomefile | 3 + bun.lockb | Bin 0 -> 13570 bytes package.json | 20 +++ src/components/commands.component.ts | 15 +++ src/config.ts | 12 ++ src/controllers/discord.controller.ts | 83 ++++++++++++ src/index.ts | 11 ++ src/lib/client.ts | 8 ++ src/lib/utils.ts | 5 + src/services/discord.service.ts | 26 ++++ src/services/drink.service.ts | 14 +++ src/services/greeting.service.ts | 14 +++ src/services/water-me.service.ts | 98 +++++++++++++++ src/tasks/drink.task.ts | 0 src/tasks/greeting.task.ts | 16 +++ src/tasks/water-me.task.ts | 16 +++ tsconfig.json | 27 ++++ 18 files changed, 543 insertions(+) create mode 100644 .gitignore create mode 100644 Biomefile create mode 100755 bun.lockb create mode 100644 package.json create mode 100644 src/components/commands.component.ts create mode 100644 src/config.ts create mode 100644 src/controllers/discord.controller.ts create mode 100644 src/index.ts create mode 100644 src/lib/client.ts create mode 100644 src/lib/utils.ts create mode 100644 src/services/discord.service.ts create mode 100644 src/services/drink.service.ts create mode 100644 src/services/greeting.service.ts create mode 100644 src/services/water-me.service.ts create mode 100644 src/tasks/drink.task.ts create mode 100644 src/tasks/greeting.task.ts create mode 100644 src/tasks/water-me.task.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/Biomefile b/Biomefile new file mode 100644 index 0000000..2853af7 --- /dev/null +++ b/Biomefile @@ -0,0 +1,3 @@ +{ + "name": "avocadi-bot" +} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..95e0258e7a5fb5748fef196fef7cc9ab7647aa0b GIT binary patch literal 13570 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 + +export default function getCommands() { + const commands = [ + new SlashCommandBuilder() + .setName(Commands.Enum["water-me"]) + .setDescription("giess mich mit etwas wasser :3"), + ].map((command) => command.toJSON()); + + return commands; +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..0ecdb35 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,12 @@ +const test = true; + +export default { + discord: { + channelId: + (test + ? process.env.DISCORD_TEST_CHANNEL_ID + : process.env.DISCORD_CHANNEL_ID) || "", + applicationId: process.env.DISCORD_APPLICATION_ID || "", + token: process.env.DISCORD_TOKEN || "", + }, +}; diff --git a/src/controllers/discord.controller.ts b/src/controllers/discord.controller.ts new file mode 100644 index 0000000..3008995 --- /dev/null +++ b/src/controllers/discord.controller.ts @@ -0,0 +1,83 @@ +import { Commands, type CommandsType } from "components/commands.component"; +import type { + ButtonInteraction, + CacheType, + ChatInputCommandInteraction, + Interaction, + ModalSubmitInteraction, +} from "discord.js"; +import client from "lib/client"; +import EventEmitter from "node:events"; +import DiscordService from "services/discord.service"; +import { WaterMeService } from "services/water-me.service"; + +export default class DiscordController extends EventEmitter { + private discordService!: DiscordService; + waterMeService: WaterMeService; + + constructor() { + super(); + this.discordService = new DiscordService(); + this.waterMeService = new WaterMeService(); + // log when running + client.once("ready", () => { + console.log("Listening..."); + }); + // listen for interactions + client.on("interactionCreate", this.handleInteraction.bind(this)); + } + + async init() { + await this.discordService.init(); + } + + async handleInteraction(interaction: Interaction) { + 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) { + const { customId } = interaction; + console.log(interaction.customId); + + if (customId === "moreWater") { + await this.waterMeService.handleInteraction(interaction); + } + } + + async handleChatInputCommand( + interaction: ChatInputCommandInteraction, + ) { + const commandName = interaction.commandName as CommandsType; + + // add commands + switch (commandName) { + case Commands.Enum["water-me"]: + await this.waterMeService.handleInteraction(interaction); + return; + default: + break; + } + } + + // wenn neues fenster durch buttonclick or so + async handleModalSubmit(interaction: ModalSubmitInteraction) { + const { customId } = interaction; + + switch (customId) { + default: + break; + } + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..059bbf8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,11 @@ +import "tasks/water-me.task"; +import "tasks/greeting.task"; +import "tasks/drink.task"; +import DiscordController from "controllers/discord.controller"; + +// = main file + +// bootstrap application + +const discordController = new DiscordController(); +discordController.init(); diff --git a/src/lib/client.ts b/src/lib/client.ts new file mode 100644 index 0000000..e50912b --- /dev/null +++ b/src/lib/client.ts @@ -0,0 +1,8 @@ +import config from "config"; +import { Client, IntentsBitField } from "discord.js"; + +const client = new Client({ intents: [IntentsBitField.Flags.Guilds] }); + +await client.login(config.discord.token); + +export default client; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..2d37ba2 --- /dev/null +++ b/src/lib/utils.ts @@ -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; +} diff --git a/src/services/discord.service.ts b/src/services/discord.service.ts new file mode 100644 index 0000000..bff3fe7 --- /dev/null +++ b/src/services/discord.service.ts @@ -0,0 +1,26 @@ +import { Routes } from "discord.js"; +import { REST } from "@discordjs/rest"; +import config from "config"; +import getCommands from "components/commands.component"; + +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); + } + } +} diff --git a/src/services/drink.service.ts b/src/services/drink.service.ts new file mode 100644 index 0000000..519eb4c --- /dev/null +++ b/src/services/drink.service.ts @@ -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.channelId); + + if (channel?.isTextBased && channel?.isSendable()) { + await channel.send({ content: "HALLOOOO" }); + } + } +} diff --git a/src/services/greeting.service.ts b/src/services/greeting.service.ts new file mode 100644 index 0000000..519eb4c --- /dev/null +++ b/src/services/greeting.service.ts @@ -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.channelId); + + if (channel?.isTextBased && channel?.isSendable()) { + await channel.send({ content: "HALLOOOO" }); + } + } +} diff --git a/src/services/water-me.service.ts b/src/services/water-me.service.ts new file mode 100644 index 0000000..758c928 --- /dev/null +++ b/src/services/water-me.service.ts @@ -0,0 +1,98 @@ +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???", + "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.channelId); + + if ( + channel?.isTextBased && + channel?.isSendable() && + this.waterLevel <= this.thirsty + ) { + await channel.send({ content: "ich brauche wasser :(" }); + } + } + + waterMe() { + const reply = this.getReply(); + + this.waterLevel++; + + return { + reply, + }; + } + + async handleInteraction(interaction: Interaction) { + const result = this.waterMe(); + + const moreButton = new ButtonBuilder() + .setCustomId("more") + .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: + components: [row as any], + }); + } else if (interaction.isButton()) { + await interaction.reply({ + content: result.reply, + }); + } + } +} diff --git a/src/tasks/drink.task.ts b/src/tasks/drink.task.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/tasks/greeting.task.ts b/src/tasks/greeting.task.ts new file mode 100644 index 0000000..f57bd44 --- /dev/null +++ b/src/tasks/greeting.task.ts @@ -0,0 +1,16 @@ +import { CronJob } from "cron"; +import { GreetingService } from "services/greeting.service"; + +const greetingService = new GreetingService(); + +new CronJob( + "0 */1 * * * *", // cronTime + async () => { + console.log("called greeting"); + await greetingService.greet(); + }, // onTick + null, // onComplete + true, // start + "Europe/Berlin", // timeZone +); +// job.start() is optional here because of the fourth parameter set to true. diff --git a/src/tasks/water-me.task.ts b/src/tasks/water-me.task.ts new file mode 100644 index 0000000..2e22e94 --- /dev/null +++ b/src/tasks/water-me.task.ts @@ -0,0 +1,16 @@ +import { CronJob } from "cron"; +import { WaterMeService } from "services/water-me.service"; + +const waterMeService = new WaterMeService(); + +new CronJob( + "0 0 */2 * * *", // 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. diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3753d85 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "baseUrl": "src", + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}