Skip to content

Commit 53a5b42

Browse files
committed
initial version
1 parent bdce4de commit 53a5b42

21 files changed

+2446
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
test.ts
2+
prisma/seed
3+
14
# Logs
25
logs
36
*.log

api/index.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require("dotenv").config()
2+
import { User, PrismaClient } from "@prisma/client"
3+
import express from "express"
4+
import { weeklyAmount } from "../discord/utils"
5+
const prisma = new PrismaClient()
6+
7+
const api = express()
8+
const PORT = process.env.API_PORT
9+
10+
api.get("/", (req, res) => {
11+
res.send("hello world!")
12+
})
13+
14+
api.get("/weekly", async (req, res) => {
15+
const weeklyAmounts = await weeklyAmount({}, ["userId"], false)
16+
17+
const users = await prisma.user.findMany({
18+
select: {
19+
id: true,
20+
username: true
21+
}
22+
})
23+
24+
let responseData: { userId: string | undefined, username: string | undefined, date: Date, amount: number }[] = [];
25+
26+
for (let amount of weeklyAmounts) {
27+
const user = users.find(user => user.id === amount.userId)
28+
responseData.push({
29+
userId: amount.userId,
30+
username: user ? user.username : undefined,
31+
date: amount.date,
32+
amount: amount.amount
33+
})
34+
}
35+
36+
res.send(responseData)
37+
})
38+
39+
api.listen(PORT, () => {
40+
console.log(`Server is running at https://localhost:${PORT}`)
41+
})

discord/commands/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export * as menü from "./menü"
2+
export * as ping from "./ping"
3+
export * as plus from "./plus"
4+
export * as prophezei from "./prophezei"
5+
export * as rangliste from "./rangliste"
6+
export * as stats from "./stats"
7+
export * as wann from "./wann"

discord/commands/menü.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { SlashCommandBuilder } from "@discordjs/builders";
2+
import { CommandInteraction } from "discord.js";
3+
4+
export const data = new SlashCommandBuilder()
5+
.setName("menü")
6+
.setDescription("Gib eine Menüempfehlung für den nächsten Stammtisch.")
7+
8+
export async function execute(interaction: CommandInteraction) {
9+
const mains = ["Schnitzel", "Cordon Bleu"]
10+
const sides = ["Salat", "Braterdäpfel", "Petersilerdäpfel", "Pommes"]
11+
12+
const selectedMain = mains[Math.floor(Math.random() * mains.length)]
13+
const selectedSide = sides[Math.floor(Math.random() * sides.length)]
14+
15+
let response = `Als Hauptspeise kann ich heute ${selectedMain} mit ${selectedSide} empfehlen.`
16+
17+
if (selectedSide != "Pommes") {
18+
response += "\nVielleicht Pommes dazu?"
19+
}
20+
21+
await interaction.reply(response)
22+
}

discord/commands/ping.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { CommandInteraction } from "discord.js"
2+
import { SlashCommandBuilder } from "@discordjs/builders"
3+
4+
export const data = new SlashCommandBuilder()
5+
.setName("ping")
6+
.setDescription("Antworte mit Pong!")
7+
8+
export async function execute(interaction: CommandInteraction) {
9+
await interaction.reply("Pong!")
10+
}

discord/commands/plus.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { SlashCommandBuilder } from "@discordjs/builders"
2+
import { CommandInteraction } from "discord.js";
3+
import { PrismaClient } from "@prisma/client"
4+
const prisma = new PrismaClient()
5+
6+
export const data = new SlashCommandBuilder()
7+
.setName("plus")
8+
.setDescription("Füge Werte für einen Benutzer in der Datenbank hinzu.")
9+
.addIntegerOption(value =>
10+
value.setName("value")
11+
.setDescription("Anzahl")
12+
.setRequired(true))
13+
.addUserOption(option =>
14+
option.setName("user")
15+
.setDescription("Ein Benutzer")
16+
.setRequired(true))
17+
18+
export async function execute(interaction: CommandInteraction) {
19+
const targetUser = interaction.options.getUser("user", true)
20+
const value = interaction.options.getInteger("value", true)
21+
22+
await prisma.user.upsert({
23+
where: { id: targetUser.id },
24+
create: {
25+
id: targetUser.id,
26+
username: targetUser.username,
27+
firstSeen: new Date(),
28+
beers: {
29+
create: {
30+
amount: value
31+
}
32+
}
33+
},
34+
update: {
35+
beers: {
36+
create: {
37+
amount: value
38+
}
39+
}
40+
}
41+
})
42+
43+
await interaction.reply(`${Math.abs(value)} :beer: für <@${targetUser.id}> ${value < 0 ? 'entfernt' : 'hinzugefügt'}.`)
44+
}

discord/commands/prophezei.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
require("dotenv").config()
2+
3+
import fs from "fs";
4+
import path from "path";
5+
import { SlashCommandBuilder } from "@discordjs/builders";
6+
import { CommandInteraction, DiscordAPIError, MessageEmbed } from "discord.js";
7+
8+
const SHARED_DATA_DIR = process.env.SHARED_DATA_DIR || "./"
9+
const PLOT_DIR = path.join(SHARED_DATA_DIR, "plots")
10+
11+
export const data = new SlashCommandBuilder()
12+
.setName("prophezei")
13+
.setDescription("Erstelle eine Vorhersage für das Jahresende.")
14+
.addUserOption(option => option.setName("user").setDescription("Ein Benutzer").setRequired(true))
15+
16+
export async function execute(interaction: CommandInteraction) {
17+
const targetUser = interaction.options.getUser("user")
18+
if (!targetUser) throw new Error("Invalid user");
19+
20+
const embed = new MessageEmbed()
21+
.setTitle("Vorhersage")
22+
23+
const plot = fs.readdirSync(PLOT_DIR)
24+
.find(file => file.includes(targetUser.id))
25+
26+
if (!plot) throw new Error("No file found");
27+
28+
interaction.reply({ embeds: [embed], files: [path.join(PLOT_DIR, plot)] })
29+
}

discord/commands/rangliste.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { SlashCommandBuilder } from "@discordjs/builders"
2+
import { PrismaClient } from "@prisma/client"
3+
import { CommandInteraction } from "discord.js"
4+
const prisma = new PrismaClient()
5+
6+
export const data = new SlashCommandBuilder()
7+
.setName("rangliste")
8+
.setDescription("Berechne die :beer: Rangliste aller Benutzer.")
9+
10+
export async function execute(interaction: CommandInteraction) {
11+
const ranking = await prisma.consumption.groupBy({
12+
by: ["userId"],
13+
where: {
14+
user: {
15+
username: {
16+
not: "offset"
17+
}
18+
}
19+
},
20+
_sum: {
21+
amount: true
22+
},
23+
orderBy: {
24+
_sum: {
25+
amount: "desc"
26+
}
27+
}
28+
})
29+
30+
let response = `**Rangliste**\n`;
31+
for (let i = 0; i < ranking.length; i++) {
32+
const userId = ranking[i].userId
33+
const userTotal = ranking[i]._sum.amount
34+
response += `${i + 1}. <@${userId}> - ${userTotal} :beers:\n`
35+
}
36+
37+
await interaction.reply(response)
38+
}

discord/commands/stats.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { SlashCommandBuilder } from "@discordjs/builders"
2+
import { PrismaClient } from "@prisma/client"
3+
import { format } from "date-fns"
4+
import { CommandInteraction } from "discord.js"
5+
import { mean, weeklyAmount } from "../utils"
6+
7+
const prisma = new PrismaClient()
8+
9+
export const data = new SlashCommandBuilder()
10+
.setName("stats")
11+
.setDescription("Berechne die :beer: Statistiken für einen Benutzer.")
12+
.addUserOption(option => option.setName("user").setDescription("Ein Benutzer"))
13+
14+
export async function execute(interaction: CommandInteraction) {
15+
const targetUser = interaction.options.getUser("user")
16+
const filter = targetUser ? { userId: targetUser.id } : {}
17+
const results = await prisma.consumption.aggregate({
18+
where: filter,
19+
_sum: { amount: true },
20+
_min: { timestamp: true }
21+
})
22+
23+
if (!results._min.timestamp) {
24+
throw new Error("invalid date")
25+
}
26+
27+
const weekly = await weeklyAmount(filter)
28+
const weeklyAverage = mean(weekly.map(d => d.amount))
29+
30+
let reply: string;
31+
const date = format(results._min.timestamp, "dd.MM.yyyy")
32+
if (targetUser) {
33+
reply = `<@${targetUser.id}> hat seit ${date} insgesamt ${results._sum.amount} :beers: getrunken!`
34+
} else {
35+
reply = `Seit ${date} wurden insgesamt ${results._sum.amount} :beers: getrunken!`
36+
}
37+
reply += `\nIm Durchschnitt sind das ${Math.round(weeklyAverage * 10) / 10} :beers: pro Stammtisch.`
38+
await interaction.reply(reply)
39+
}

discord/commands/wann.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { SlashCommandBuilder } from "@discordjs/builders"
2+
import { set, nextThursday, formatDistanceStrict } from "date-fns"
3+
import { de } from "date-fns/locale"
4+
import { CommandInteraction } from "discord.js"
5+
6+
export const data = new SlashCommandBuilder()
7+
.setName("wann")
8+
.setDescription("Zeit bis zum nächsten Stammtisch")
9+
10+
export async function execute(interaction: CommandInteraction) {
11+
const now = new Date()
12+
const upcoming = set(nextThursday(now), { hours: 19, minutes: 0, seconds: 0, milliseconds: 0 })
13+
const timeToUpcoming = formatDistanceStrict(now, upcoming, { locale: de })
14+
await interaction.reply(`Und da wächst die Lust auf ein :beer:! Nur noch ${timeToUpcoming} bis zum nächsten Stammtisch...`)
15+
}

discord/deploy-commands.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require("dotenv").config()
2+
import { REST } from "@discordjs/rest"
3+
import { Routes } from "discord-api-types/v9"
4+
import * as commandModules from "./commands"
5+
import { SlashCommandBuilder } from "@discordjs/builders"
6+
import { CommandInteraction } from "discord.js"
7+
8+
const token = process.env.DISCORD_TOKEN || ""
9+
const clientId = process.env.DISCORD_CLIENT_ID || ""
10+
const guildId = process.env.DISCORD_GUILD_ID || ""
11+
12+
type Command = {
13+
data: SlashCommandBuilder
14+
execute: (interaction: CommandInteraction) => void
15+
}
16+
17+
const commands = Object(commandModules)
18+
19+
const body = [];
20+
for (const module of Object.values<Command>(commands)) {
21+
body.push(module.data.toJSON())
22+
}
23+
24+
const rest = new REST({ version: "9" }).setToken(token)
25+
26+
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: body })
27+
.then(() => console.log("Sucessfully registered application commands!"))
28+
.catch(e => console.error(e))

discord/events/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * as interactionCreate from "./interactionCreate"
2+
export * as messageCreate from "./messageCreate"
3+
export * as ready from "./ready"

discord/events/interactionCreate.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as commandModules from "../commands"
2+
3+
const commands = Object(commandModules)
4+
export const name = "interactionCreate"
5+
6+
export async function execute(interaction: any) {
7+
if (!interaction.isCommand()) return;
8+
9+
const { commandName } = interaction
10+
11+
try {
12+
await commands[commandName].execute(interaction)
13+
} catch (e) {
14+
console.error(e)
15+
await interaction.reply({
16+
content: "Es ist ein Fehler aufgetreten!",
17+
ephemeral: true
18+
})
19+
}
20+
21+
}

discord/events/messageCreate.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Interaction, Message } from "discord.js";
2+
import { PrismaClient } from "@prisma/client";
3+
const prisma = new PrismaClient()
4+
5+
export const name = "messageCreate"
6+
7+
export async function execute(message: Message) {
8+
const nBeers = message.content.match(/🍺/g)
9+
if (!nBeers) return;
10+
11+
const beerCount = nBeers.length
12+
13+
await prisma.user.upsert({
14+
where: { id: message.author.id },
15+
create: {
16+
id: message.author.id,
17+
username: message.author.username,
18+
firstSeen: new Date(),
19+
beers: {
20+
create: {
21+
amount: beerCount
22+
}
23+
}
24+
},
25+
update: {
26+
lastSeen: new Date(),
27+
beers: {
28+
create: {
29+
amount: beerCount
30+
}
31+
}
32+
}
33+
})
34+
35+
await message.react("👍")
36+
await message.reply(`Prost <@${message.author.id}>! Ich habe ein :beer: hinzugefügt.`)
37+
}

discord/events/ready.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Client, ClientApplication } from "discord.js"
2+
3+
export const name = "ready"
4+
export const once = true
5+
6+
export function execute(client: Client) {
7+
if (client.user) {
8+
console.log(`Ready! Logged in as ${client.user.tag}`)
9+
}
10+
}

0 commit comments

Comments
 (0)