added discord container for renaming and role-management
This commit is contained in:
@@ -19,6 +19,7 @@ NEXT_PUBLIC_HUB_URL=https://hub.premiumag.de
|
||||
NEXT_PUBLIC_HUB_SERVER_URL=https://api.hub.premiumag.de
|
||||
NEXT_PUBLIC_DISPATCH_URL=https://dispatch.premiumag.de
|
||||
NEXT_PUBLIC_DISPATCH_SERVER_URL=https://api.dispatch.premiumag.de
|
||||
DISCORD_SERVER_URL=http://discord-server
|
||||
|
||||
|
||||
NEXT_PUBLIC_ESRI_ACCESS_TOKEN=
|
||||
@@ -50,6 +51,7 @@ REDIS_PORT=6379
|
||||
# ───────────────────────────────────────────────
|
||||
HUB_SERVER_PORT=3000
|
||||
|
||||
DISCORD_SERVER_PORT=3005
|
||||
# ───────────────────────────────────────────────
|
||||
# 📚 Moodle
|
||||
# ───────────────────────────────────────────────
|
||||
@@ -58,6 +60,7 @@ MOODLE_API_TOKEN=ac346f0324647b68488d13fd52a9bbe8
|
||||
MOODLE_USER_PASSWORD=var-api-user-P1
|
||||
NEXT_PUBLIC_MOODLE_URL=https://02.premiumag.de:8081
|
||||
|
||||
|
||||
# ───────────────────────────────────────────────
|
||||
# 📧 E-Mail Einstellungen (nur HUB Server)
|
||||
# ───────────────────────────────────────────────
|
||||
@@ -70,6 +73,8 @@ MAIL_PASSWORD=b7316PB8aDPCC%-&
|
||||
# 🕹️ Discord OAuth (optional)
|
||||
# ───────────────────────────────────────────────
|
||||
|
||||
DISCORD_GUILD_ID=1077269395019141140
|
||||
|
||||
DISCORD_OAUTH_CLIENT_ID=930384053344034846
|
||||
DISCORD_OAUTH_SECRET=96aSvmIePqFTbGc54mad0QsZfDnYwhl1
|
||||
DISCORD_BOT_TOKEN=OTMwMzg0MDUzMzQ0MDM0ODQ2.G7zIy-._hE3dTbtUv6sd7nIP2PUn3d8s-2MFk0x3nYMg8
|
||||
|
||||
19
apps/discord-server/.d.ts
vendored
Normal file
19
apps/discord-server/.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
uid: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
}
|
||||
}
|
||||
declare module "cookie-parser";
|
||||
|
||||
import type { User } from "@repo/db";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: User | null;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
apps/discord-server/.dockerignore
Normal file
6
apps/discord-server/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
nodemon.json
|
||||
.env
|
||||
.env.example
|
||||
7
apps/discord-server/.env.example
Normal file
7
apps/discord-server/.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
DISCORD_SERVER_PORT=3005
|
||||
DISCORD_GUILD_ID=1077269395019141140
|
||||
DISCORD_OAUTH_CLIENT_ID=930384053344034846
|
||||
DISCORD_OAUTH_SECRET=96aSvmIePqFTbGc54mad0QsZfDnYwhl1
|
||||
DISCORD_BOT_TOKEN=OTMwMzg0MDUzMzQ0MDM0ODQ2.G7zIy-._hE3dTbtUv6sd7nIP2PUn3d8s-2MFk0x3nYMg8
|
||||
DISCORD_REDIRECT_URL=https://hub.premiumag.de/api/discord-redirect
|
||||
NEXT_PUBLIC_DISCORD_URL=https://discord.com/oauth2/authorize?client_id=930384053344034846&response_type=code&redirect_uri=https%3A%2F%2Fhub.premiumag.de%2Fapi%2Fdiscord-redirect&scope=identify+guilds+email
|
||||
50
apps/discord-server/Dockerfile
Normal file
50
apps/discord-server/Dockerfile
Normal file
@@ -0,0 +1,50 @@
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
ENV PNPM_HOME="/usr/local/pnpm"
|
||||
ENV PATH="${PNPM_HOME}:${PATH}"
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
RUN pnpm add -g turbo@^2.5
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk update
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
WORKDIR /usr/app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN ls -lh
|
||||
|
||||
RUN turbo prune discord-server --docker
|
||||
|
||||
FROM base AS installer
|
||||
RUN apk update
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
WORKDIR /usr/app
|
||||
|
||||
COPY --from=builder /usr/app/out/json/ .
|
||||
RUN pnpm install
|
||||
|
||||
# Build the project
|
||||
COPY --from=builder /usr/app/out/full/ .
|
||||
|
||||
RUN turbo run build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /usr/app
|
||||
|
||||
# Don't run production as root
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
USER nextjs
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=installer --chown=nextjs:nodejs /usr/app/ ./
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 3003
|
||||
|
||||
CMD ["pnpm", "--dir", "apps/discord-server", "run", "start"]
|
||||
19
apps/discord-server/index.ts
Normal file
19
apps/discord-server/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import "dotenv/config";
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
import router from "routes/router";
|
||||
import cookieParser from "cookie-parser";
|
||||
import cors from "cors";
|
||||
import "modules/chron";
|
||||
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
app.use(router);
|
||||
|
||||
server.listen(process.env.DISCORD_SERVER_PORT, () => {
|
||||
console.log(`Server running on port ${process.env.DISCORD_SERVER_PORT}`);
|
||||
});
|
||||
62
apps/discord-server/modules/chron.ts
Normal file
62
apps/discord-server/modules/chron.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { MissionLog, prisma } from "@repo/db";
|
||||
import cron from "node-cron";
|
||||
|
||||
const removeClosedMissions = async () => {
|
||||
const oldMissions = await prisma.mission.findMany({
|
||||
where: {
|
||||
state: "running",
|
||||
},
|
||||
});
|
||||
oldMissions.forEach(async (mission) => {
|
||||
const lastAlert = (mission.missionLog as unknown as MissionLog[]).find((l) => {
|
||||
return l.type === "alert-log";
|
||||
});
|
||||
const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null;
|
||||
|
||||
const aircraftsInMission = await prisma.connectedAircraft.findMany({
|
||||
where: {
|
||||
stationId: {
|
||||
in: mission.missionStationIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!aircraftsInMission ||
|
||||
!aircraftsInMission.some((a) => ["1", "2", "6"].includes(a.fmsStatus))
|
||||
)
|
||||
return;
|
||||
|
||||
const now = new Date();
|
||||
if (!lastAlertTime) return;
|
||||
// change State to closed if last alert was more than 180 minutes ago
|
||||
if (now.getTime() - lastAlertTime.getTime() < 30 * 60 * 1000) return;
|
||||
const log: MissionLog = {
|
||||
type: "completed-log",
|
||||
auto: true,
|
||||
timeStamp: new Date().toISOString(),
|
||||
data: {},
|
||||
};
|
||||
|
||||
await prisma.mission.update({
|
||||
where: {
|
||||
id: mission.id,
|
||||
},
|
||||
data: {
|
||||
state: "finished",
|
||||
missionLog: {
|
||||
push: log as any,
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(`Mission ${mission.id} closed due to inactivity.`);
|
||||
});
|
||||
};
|
||||
|
||||
cron.schedule("*/5 * * * *", async () => {
|
||||
try {
|
||||
await removeClosedMissions();
|
||||
} catch (error) {
|
||||
console.error("Error removing closed missions:", error);
|
||||
}
|
||||
});
|
||||
19
apps/discord-server/modules/discord.ts
Normal file
19
apps/discord-server/modules/discord.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Client, GatewayIntentBits } from "discord.js";
|
||||
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds],
|
||||
});
|
||||
|
||||
const token = process.env.DISCORD_BOT_TOKEN;
|
||||
|
||||
if (!token) {
|
||||
throw new Error("DISCORD_BOT_TOKEN environment variable is not set.");
|
||||
}
|
||||
|
||||
client.login(token);
|
||||
|
||||
client.on("ready", () => {
|
||||
console.log(`Logged in as ${client.user?.tag}`);
|
||||
});
|
||||
|
||||
export default client;
|
||||
5
apps/discord-server/nodemon.json
Normal file
5
apps/discord-server/nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"watch": ["."],
|
||||
"ext": "ts",
|
||||
"exec": "tsx index.ts"
|
||||
}
|
||||
35
apps/discord-server/package.json
Normal file
35
apps/discord-server/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "discord-server",
|
||||
"exports": {
|
||||
"helpers": "./helper"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nodemon --signal SIGINT",
|
||||
"start": "tsx index.ts --transpile-only",
|
||||
"build": "tsc"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.0",
|
||||
"devDependencies": {
|
||||
"@repo/db": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/cookie-parser": "^1.4.8",
|
||||
"@types/cors": "^2.8.18",
|
||||
"@types/express": "^5.0.2",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"concurrently": "^9.1.2",
|
||||
"typescript": "latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^4.3.1",
|
||||
"discord.js": "^14.19.3",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"node-cron": "^4.1.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"react": "^19.1.0",
|
||||
"tsx": "^4.19.4"
|
||||
}
|
||||
}
|
||||
69
apps/discord-server/routes/member.ts
Normal file
69
apps/discord-server/routes/member.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Router, Request, Response } from "express";
|
||||
import client from "modules/discord";
|
||||
|
||||
const GUILD_ID = process.env.DISCORD_GUILD_ID;
|
||||
if (!GUILD_ID) {
|
||||
throw new Error("DISCORD_GUILD_ID environment variable is not set.");
|
||||
}
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
const getMember = async (memberId: string) => {
|
||||
const guild = client.guilds.cache.get(GUILD_ID);
|
||||
if (!guild) throw new Error("Guild not found");
|
||||
try {
|
||||
return guild.members.cache.get(memberId) ?? (await guild.members.fetch(memberId));
|
||||
} catch (error) {
|
||||
console.error("Error fetching member:", error);
|
||||
throw new Error("Member not found");
|
||||
}
|
||||
};
|
||||
|
||||
router.post("/rename", async (req: Request, res: Response) => {
|
||||
const { newName, memberId } = req.body;
|
||||
if (typeof newName !== "string" || !memberId) {
|
||||
res.status(400).json({ error: "Invalid or missing newName or memberId" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const member = await getMember(memberId);
|
||||
await member.setNickname(newName);
|
||||
console.log(`Member ${member.id} renamed to ${newName}`);
|
||||
res.status(200).json({ message: "Member renamed successfully" });
|
||||
} catch (error) {
|
||||
console.error("Error renaming member:", error);
|
||||
res.status(500).json({ error: "Failed to rename member" });
|
||||
}
|
||||
});
|
||||
|
||||
const handleRoleChange = (action: "add" | "remove") => async (req: Request, res: Response) => {
|
||||
const { roleIds, memberId } = req.body;
|
||||
if (!Array.isArray(roleIds) || !memberId) {
|
||||
res.status(400).json({ error: "Invalid or missing roleIds or memberId" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const member = await getMember(memberId);
|
||||
|
||||
const currentRoleIds = member.roles.cache.map((role) => role.id);
|
||||
const filteredRoleIds =
|
||||
action === "add"
|
||||
? roleIds.filter((id: string) => !currentRoleIds.includes(id))
|
||||
: roleIds.filter((id: string) => currentRoleIds.includes(id));
|
||||
if (filteredRoleIds.length === 0) {
|
||||
res.status(200).json({ message: `No roles to ${action}` });
|
||||
return;
|
||||
}
|
||||
|
||||
await member.roles[action](roleIds);
|
||||
res.status(200).json({ message: `Roles ${action}ed successfully` });
|
||||
} catch (error) {
|
||||
console.error(`Error ${action}ing roles:`, error);
|
||||
res.status(500).json({ error: `Failed to ${action} roles` });
|
||||
}
|
||||
};
|
||||
|
||||
router.post("/add-role", handleRoleChange("add"));
|
||||
router.post("/remove-role", handleRoleChange("remove"));
|
||||
|
||||
export default router;
|
||||
8
apps/discord-server/routes/router.ts
Normal file
8
apps/discord-server/routes/router.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Router } from "express";
|
||||
import memberRouter from "./member";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
router.use("/member", memberRouter);
|
||||
|
||||
export default router;
|
||||
11
apps/discord-server/tsconfig.json
Normal file
11
apps/discord-server/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["**/*.ts", "./index.ts", "**/*.d.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
DISPATCH_SERVER_PORT=3002
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
DISCORD_SERVER_URL=http://discord-server
|
||||
DISPATCH_APP_TOKEN=dispatch
|
||||
LIVEKIT_API_KEY=APIAnsGdtdYp2Ho
|
||||
LIVEKIT_API_SECRET=tdPjVsYUx8ddC7K9NvdmVAeLRF9GeADD6Fedm1x63fWC
|
||||
38
apps/dispatch-server/modules/discord.ts
Normal file
38
apps/dispatch-server/modules/discord.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import axios from "axios";
|
||||
|
||||
const discordAxiosClient = axios.create({
|
||||
baseURL: process.env.DISCORD_SERVER_URL || "https://discord.com/api/v10",
|
||||
});
|
||||
|
||||
export const renameMember = async (memberId: string, newName: string) => {
|
||||
discordAxiosClient
|
||||
.post("/member/rename", {
|
||||
memberId,
|
||||
newName,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error renaming member:", error);
|
||||
});
|
||||
};
|
||||
|
||||
export const addRolesToMember = async (memberId: string, roleIds: string[]) => {
|
||||
discordAxiosClient
|
||||
.post("/member/add-role", {
|
||||
memberId,
|
||||
roleIds,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error adding roles to member:", error);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeRolesFromMember = async (memberId: string, roleIds: string[]) => {
|
||||
discordAxiosClient
|
||||
.post("/member/remove-role", {
|
||||
memberId,
|
||||
roleIds,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error removing roles from member:", error);
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
import { getPublicUser, prisma, User } from "@repo/db";
|
||||
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
|
||||
import { DISCORD_ROLES } from "@repo/db";
|
||||
import { Server, Socket } from "socket.io";
|
||||
|
||||
export const handleConnectDispatch =
|
||||
@@ -67,6 +69,21 @@ export const handleConnectDispatch =
|
||||
},
|
||||
});
|
||||
|
||||
const discordAccount = await prisma.discordAccount.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
if (discordAccount?.id) {
|
||||
await renameMember(
|
||||
discordAccount.discordId.toString(),
|
||||
`${getPublicUser(user).fullName} • ${selectedZone}`,
|
||||
);
|
||||
await addRolesToMember(discordAccount.discordId.toString(), [
|
||||
DISCORD_ROLES.ONLINE_DISPATCHER,
|
||||
]);
|
||||
}
|
||||
|
||||
socket.join("dispatchers"); // Dem Dispatcher-Raum beitreten
|
||||
socket.join(`user:${user.id}`); // Dem User-Raum beitreten
|
||||
|
||||
@@ -85,6 +102,15 @@ export const handleConnectDispatch =
|
||||
});
|
||||
io.to("dispatchers").emit("dispatchers-update");
|
||||
io.to("pilots").emit("dispatchers-update");
|
||||
if (discordAccount?.id) {
|
||||
await renameMember(
|
||||
discordAccount.discordId.toString(),
|
||||
`${getPublicUser(user).fullName} - ${user.publicId}`,
|
||||
);
|
||||
await removeRolesFromMember(discordAccount.discordId.toString(), [
|
||||
DISCORD_ROLES.ONLINE_DISPATCHER,
|
||||
]);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error connecting to dispatch server:", error);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getPublicUser, prisma, User } from "@repo/db";
|
||||
import { channel } from "diagnostics_channel";
|
||||
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
|
||||
import { DISCORD_ROLES } from "@repo/db";
|
||||
import { Server, Socket } from "socket.io";
|
||||
|
||||
export const handleConnectPilot =
|
||||
@@ -18,7 +19,6 @@ export const handleConnectPilot =
|
||||
|
||||
if (!user) return Error("User not found");
|
||||
|
||||
console.log("Pilot connected:", user.publicId);
|
||||
const existingConnection = await prisma.connectedAircraft.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
@@ -65,6 +65,20 @@ export const handleConnectPilot =
|
||||
stationId: parseInt(stationId),
|
||||
},
|
||||
});
|
||||
|
||||
const discordAccount = await prisma.discordAccount.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
if (discordAccount?.id) {
|
||||
await renameMember(
|
||||
discordAccount.discordId.toString(),
|
||||
`${getPublicUser(user).fullName} • ${Station?.bosCallsignShort}`,
|
||||
);
|
||||
await addRolesToMember(discordAccount.discordId.toString(), [DISCORD_ROLES.ONLINE_PILOT]);
|
||||
}
|
||||
|
||||
socket.join("dispatchers"); // Join the dispatchers room
|
||||
socket.join(`user:${userId}`); // Join the user-specific room
|
||||
socket.join(`station:${stationId}`); // Join the station-specific room
|
||||
@@ -106,6 +120,15 @@ export const handleConnectPilot =
|
||||
.catch(console.error);
|
||||
io.to("dispatchers").emit("update-connectedAircraft");
|
||||
io.to("pilots").emit("pilots-update");
|
||||
if (discordAccount?.id) {
|
||||
await renameMember(
|
||||
discordAccount.discordId.toString(),
|
||||
`${getPublicUser(user).fullName} - ${user.publicId}`,
|
||||
);
|
||||
await removeRolesFromMember(discordAccount.discordId.toString(), [
|
||||
DISCORD_ROLES.ONLINE_PILOT,
|
||||
]);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error connecting to dispatch server:", error);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { getMoodleCourseCompletionStatus, getMoodleUserById } from "./moodle";
|
||||
import { CronJob } from "cron";
|
||||
import { prisma } from "@repo/db";
|
||||
import { DISCORD_ROLES, prisma } from "@repo/db";
|
||||
import { sendCourseCompletedEmail } from "modules/mail";
|
||||
import { handleParticipantFinished } from "modules/event";
|
||||
import { eventCompleted } from "helper/events";
|
||||
import { addRolesToMember, removeRolesFromMember } from "modules/discord";
|
||||
|
||||
const syncMoodleIds = async () => {
|
||||
try {
|
||||
@@ -101,19 +102,91 @@ export const checkFinishedParticipants = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
const checkUnfinishedParticipants = async () => {
|
||||
const participantsPending = await prisma.participant.findMany({
|
||||
where: {
|
||||
completetionWorkflowFinished: false,
|
||||
},
|
||||
include: {
|
||||
Event: true,
|
||||
User: {
|
||||
include: {
|
||||
discordAccounts: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
participantsPending.forEach(async (p) => {
|
||||
if (!p.User) return;
|
||||
const completed = eventCompleted(p.Event, p);
|
||||
|
||||
if (completed) return;
|
||||
|
||||
if (p.User.discordAccounts[0] && p.Event.discordRoleId) {
|
||||
await addRolesToMember(p.User.discordAccounts[0].discordId, [p.Event.discordRoleId]);
|
||||
prisma.participant.update({
|
||||
where: {
|
||||
id: p.id,
|
||||
},
|
||||
data: {
|
||||
inscriptionWorkflowCompleted: true,
|
||||
statusLog: {
|
||||
push: {
|
||||
event: "Discord-Rolle hinzugefügt",
|
||||
timestamp: new Date(),
|
||||
user: "system",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const checkDiscordRoles = async () => {
|
||||
const user = await prisma.user.findMany({
|
||||
where: {
|
||||
discordAccounts: {
|
||||
some: {},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
discordAccounts: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const u of user) {
|
||||
// Here ony member Roles regarding their rights are checked
|
||||
if (!u.discordAccounts[0]) continue;
|
||||
const discordAccount = u.discordAccounts[0];
|
||||
|
||||
// For Pilot
|
||||
if (u.permissions.includes("PILOT")) {
|
||||
await addRolesToMember(discordAccount.discordId, [DISCORD_ROLES.PILOT]); // ONLINE_PILOT
|
||||
} else {
|
||||
await removeRolesFromMember(discordAccount.discordId, [DISCORD_ROLES.PILOT]); // ONLINE_PILOT
|
||||
}
|
||||
// for Dispatcher
|
||||
if (u.permissions.includes("DISPO")) {
|
||||
await addRolesToMember(discordAccount.discordId, [DISCORD_ROLES.DISPATCHER]); // ONLINE_DISPATCHER
|
||||
} else {
|
||||
await removeRolesFromMember(discordAccount.discordId, [DISCORD_ROLES.DISPATCHER]); // ONLINE_DISPATCHER
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
|
||||
CronJob.from({
|
||||
cronTime: "*/1 * * * *",
|
||||
onTick: async () => {
|
||||
await updateParticipantMoodleResults();
|
||||
await checkFinishedParticipants();
|
||||
await checkUnfinishedParticipants();
|
||||
},
|
||||
start: true,
|
||||
});
|
||||
|
||||
const debug = async () => {
|
||||
await updateParticipantMoodleResults();
|
||||
await checkFinishedParticipants();
|
||||
};
|
||||
|
||||
debug();
|
||||
CronJob.from({
|
||||
cronTime: "0 * * * *",
|
||||
onTick: checkDiscordRoles,
|
||||
start: true,
|
||||
});
|
||||
|
||||
38
apps/hub-server/modules/discord.ts
Normal file
38
apps/hub-server/modules/discord.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import axios from "axios";
|
||||
|
||||
const discordAxiosClient = axios.create({
|
||||
baseURL: process.env.DISCORD_SERVER_URL || "https://discord.com/api/v10",
|
||||
});
|
||||
|
||||
export const renameMember = async (memberId: string, newName: string) => {
|
||||
discordAxiosClient
|
||||
.post("/member/rename", {
|
||||
memberId,
|
||||
newName,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error renaming member:", error);
|
||||
});
|
||||
};
|
||||
|
||||
export const addRolesToMember = async (memberId: string, roleIds: string[]) => {
|
||||
discordAxiosClient
|
||||
.post("/member/add-role", {
|
||||
memberId,
|
||||
roleIds,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error adding roles to member:", error);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeRolesFromMember = async (memberId: string, roleIds: string[]) => {
|
||||
discordAxiosClient
|
||||
.post("/member/remove-role", {
|
||||
memberId,
|
||||
roleIds,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error removing roles from member:", error);
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Event, Participant, prisma, User } from "@repo/db";
|
||||
import { removeRolesFromMember } from "modules/discord";
|
||||
import { sendCourseCompletedEmail } from "modules/mail";
|
||||
|
||||
export const handleParticipantFinished = async (
|
||||
@@ -6,7 +7,7 @@ export const handleParticipantFinished = async (
|
||||
participant: Participant,
|
||||
user: User,
|
||||
) => {
|
||||
const discordID = await prisma.discordAccount.findFirst({
|
||||
const discordAccount = await prisma.discordAccount.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
@@ -33,7 +34,9 @@ export const handleParticipantFinished = async (
|
||||
},
|
||||
});
|
||||
|
||||
//TODO: Send Discord Message
|
||||
if (event.discordRoleId && discordAccount) {
|
||||
await removeRolesFromMember(discordAccount.discordId, [event.discordRoleId]);
|
||||
}
|
||||
await sendCourseCompletedEmail(user.email, user, event);
|
||||
|
||||
await prisma.participant.update({
|
||||
|
||||
@@ -14,7 +14,7 @@ export const GET = async (req: NextRequest) => {
|
||||
if (
|
||||
!process.env.DISCORD_OAUTH_CLIENT_ID ||
|
||||
!process.env.DISCORD_OAUTH_SECRET ||
|
||||
!process.env.DISCORD_REDIRECT ||
|
||||
!process.env.DISCORD_REDIRECT_URL ||
|
||||
!code
|
||||
) {
|
||||
return NextResponse.json(
|
||||
@@ -30,7 +30,7 @@ export const GET = async (req: NextRequest) => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: process.env.DISCORD_OAUTH_CLIENT_ID,
|
||||
client_secret: process.env.DISCORD_OAUTH_SECRET,
|
||||
redirect_uri: process.env.DISCORD_REDIRECT,
|
||||
redirect_uri: process.env.DISCORD_REDIRECT_URL,
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
});
|
||||
|
||||
39
apps/hub/helper/discord.ts
Normal file
39
apps/hub/helper/discord.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
"use server";
|
||||
import axios from "axios";
|
||||
|
||||
const discordAxiosClient = axios.create({
|
||||
baseURL: process.env.DISCORD_SERVER_URL || "https://discord.com/api/v10",
|
||||
});
|
||||
|
||||
export const renameMember = async (memberId: string, newName: string) => {
|
||||
discordAxiosClient
|
||||
.post("/member/rename", {
|
||||
memberId,
|
||||
newName,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error renaming member:", error);
|
||||
});
|
||||
};
|
||||
|
||||
export const addRolesToMember = async (memberId: string, roleIds: string[]) => {
|
||||
discordAxiosClient
|
||||
.post("/member/add-role", {
|
||||
memberId,
|
||||
roleIds,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error adding roles to member:", error);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeRolesFromMember = async (memberId: string, roleIds: string[]) => {
|
||||
discordAxiosClient
|
||||
.post("/member/remove-role", {
|
||||
memberId,
|
||||
roleIds,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error removing roles from member:", error);
|
||||
});
|
||||
};
|
||||
@@ -96,6 +96,7 @@ services:
|
||||
- "traefik.http.services.dispatch-server.loadbalancer.server.port=3000"
|
||||
- "traefik.docker.network=var-monorepo_traefik_network"
|
||||
networks:
|
||||
- discord_network
|
||||
- postgres_network
|
||||
- redis_network
|
||||
- traefik_network
|
||||
@@ -104,8 +105,18 @@ services:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
|
||||
discord-server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/discord-server/Dockerfile
|
||||
env_file:
|
||||
- .env.prod
|
||||
deploy:
|
||||
replicas: 1
|
||||
labels:
|
||||
- "traefik.enable=false"
|
||||
networks:
|
||||
- discord_network
|
||||
|
||||
# Hub Service
|
||||
hub:
|
||||
@@ -147,6 +158,7 @@ services:
|
||||
env_file:
|
||||
- .env.prod
|
||||
networks:
|
||||
- discord_network
|
||||
- postgres_network
|
||||
- traefik_network
|
||||
depends_on:
|
||||
@@ -264,12 +276,13 @@ services:
|
||||
- "traefik.http.routers.livekit.service=livekit"
|
||||
- "traefik.http.services.livekit.loadbalancer.server.port=7880"
|
||||
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
postgres_network:
|
||||
driver: bridge
|
||||
discord_network:
|
||||
driver: bridge
|
||||
redis_network:
|
||||
driver: bridge
|
||||
traefik_network:
|
||||
|
||||
@@ -8,6 +8,13 @@ export interface PublicUser {
|
||||
fullName: string;
|
||||
}
|
||||
|
||||
export const DISCORD_ROLES = {
|
||||
ONLINE_DISPATCHER: "1287399540390891571", // Replace with actual role ID
|
||||
ONLINE_PILOT: "1287399540390891571", // Replace with actual role ID
|
||||
DISPATCHER: "1081247459994501222",
|
||||
PILOT: "1081247405304975390",
|
||||
};
|
||||
|
||||
export const getPublicUser = (
|
||||
user: User,
|
||||
options = {
|
||||
|
||||
@@ -23,6 +23,7 @@ model Participant {
|
||||
attended Boolean @default(false)
|
||||
appointmentCancelled Boolean @default(false)
|
||||
completetionWorkflowFinished Boolean @default(false)
|
||||
inscriptionWorkflowCompleted Boolean @default(false)
|
||||
eventAppointmentId Int?
|
||||
enscriptionDate DateTime @default(now())
|
||||
|
||||
|
||||
219
pnpm-lock.yaml
generated
219
pnpm-lock.yaml
generated
@@ -18,6 +18,67 @@ importers:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
apps/discord-server:
|
||||
dependencies:
|
||||
axios:
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
cors:
|
||||
specifier: ^2.8.5
|
||||
version: 2.8.5
|
||||
cron:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1
|
||||
discord.js:
|
||||
specifier: ^14.19.3
|
||||
version: 14.19.3
|
||||
dotenv:
|
||||
specifier: ^16.5.0
|
||||
version: 16.5.0
|
||||
express:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
node-cron:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
nodemon:
|
||||
specifier: ^3.1.10
|
||||
version: 3.1.10
|
||||
react:
|
||||
specifier: ^19.1.0
|
||||
version: 19.1.0
|
||||
tsx:
|
||||
specifier: ^4.19.4
|
||||
version: 4.19.4
|
||||
devDependencies:
|
||||
'@repo/db':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/database
|
||||
'@repo/typescript-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/typescript-config
|
||||
'@types/cookie-parser':
|
||||
specifier: ^1.4.8
|
||||
version: 1.4.8(@types/express@5.0.2)
|
||||
'@types/cors':
|
||||
specifier: ^2.8.18
|
||||
version: 2.8.18
|
||||
'@types/express':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
'@types/node':
|
||||
specifier: ^22.15.29
|
||||
version: 22.15.29
|
||||
'@types/nodemailer':
|
||||
specifier: ^6.4.17
|
||||
version: 6.4.17
|
||||
concurrently:
|
||||
specifier: ^9.1.2
|
||||
version: 9.1.2
|
||||
typescript:
|
||||
specifier: latest
|
||||
version: 5.8.3
|
||||
|
||||
apps/dispatch:
|
||||
dependencies:
|
||||
'@hookform/resolvers':
|
||||
@@ -577,6 +638,34 @@ packages:
|
||||
'@date-fns/tz@1.2.0':
|
||||
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
|
||||
|
||||
'@discordjs/builders@1.11.2':
|
||||
resolution: {integrity: sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@discordjs/collection@1.5.3':
|
||||
resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@discordjs/collection@2.1.1':
|
||||
resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@discordjs/formatters@0.6.1':
|
||||
resolution: {integrity: sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@discordjs/rest@2.5.0':
|
||||
resolution: {integrity: sha512-PWhchxTzpn9EV3vvPRpwS0EE2rNYB9pvzDU/eLLW3mByJl0ZHZjHI2/wA8EbH2gRMQV7nu+0FoDF84oiPl8VAQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@discordjs/util@1.1.1':
|
||||
resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@discordjs/ws@1.2.2':
|
||||
resolution: {integrity: sha512-dyfq7yn0wO0IYeYOs3z79I6/HumhmKISzFL0Z+007zQJMtAFGtt3AEoq1nuLXtcunUE5YYYQqgKvybXukAK8/w==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@emnapi/core@1.4.3':
|
||||
resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
|
||||
|
||||
@@ -1339,6 +1428,18 @@ packages:
|
||||
'@rushstack/eslint-patch@1.11.0':
|
||||
resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
|
||||
|
||||
'@sapphire/async-queue@1.5.5':
|
||||
resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==}
|
||||
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
|
||||
|
||||
'@sapphire/shapeshift@4.0.0':
|
||||
resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==}
|
||||
engines: {node: '>=v16'}
|
||||
|
||||
'@sapphire/snowflake@3.5.3':
|
||||
resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==}
|
||||
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
|
||||
|
||||
'@selderee/plugin-htmlparser2@0.11.0':
|
||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||
|
||||
@@ -1597,6 +1698,9 @@ packages:
|
||||
'@types/unist@3.0.3':
|
||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.33.1':
|
||||
resolution: {integrity: sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -1759,6 +1863,10 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@vladfrangu/async_event_emitter@2.4.6':
|
||||
resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==}
|
||||
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
|
||||
|
||||
'@webassemblyjs/ast@1.14.1':
|
||||
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
|
||||
|
||||
@@ -2249,6 +2357,13 @@ packages:
|
||||
resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==}
|
||||
hasBin: true
|
||||
|
||||
discord-api-types@0.38.11:
|
||||
resolution: {integrity: sha512-XN0qhcQpetkyb/49hcDHuoeUPsQqOkb17wbV/t48gUkoEDi4ajhsxqugGcxvcN17BBtI9FPPWEgzv6IhQmCwyw==}
|
||||
|
||||
discord.js@14.19.3:
|
||||
resolution: {integrity: sha512-lncTRk0k+8Q5D3nThnODBR8fR8x2fM798o8Vsr40Krx0DjPwpZCuxxTcFMrXMQVOqM1QB9wqWgaXPg3TbmlHqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
doctrine@2.1.0:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3204,6 +3319,9 @@ packages:
|
||||
lodash.once@4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||
|
||||
lodash.snakecase@4.1.1:
|
||||
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
@@ -3238,6 +3356,9 @@ packages:
|
||||
resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
magic-bytes.js@1.12.1:
|
||||
resolution: {integrity: sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==}
|
||||
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
@@ -4337,6 +4458,9 @@ packages:
|
||||
ts-debounce@4.0.0:
|
||||
resolution: {integrity: sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==}
|
||||
|
||||
ts-mixer@6.0.4:
|
||||
resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==}
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
|
||||
|
||||
@@ -4443,6 +4567,10 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@6.21.1:
|
||||
resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==}
|
||||
engines: {node: '>=18.17'}
|
||||
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
@@ -4768,6 +4896,53 @@ snapshots:
|
||||
|
||||
'@date-fns/tz@1.2.0': {}
|
||||
|
||||
'@discordjs/builders@1.11.2':
|
||||
dependencies:
|
||||
'@discordjs/formatters': 0.6.1
|
||||
'@discordjs/util': 1.1.1
|
||||
'@sapphire/shapeshift': 4.0.0
|
||||
discord-api-types: 0.38.11
|
||||
fast-deep-equal: 3.1.3
|
||||
ts-mixer: 6.0.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@discordjs/collection@1.5.3': {}
|
||||
|
||||
'@discordjs/collection@2.1.1': {}
|
||||
|
||||
'@discordjs/formatters@0.6.1':
|
||||
dependencies:
|
||||
discord-api-types: 0.38.11
|
||||
|
||||
'@discordjs/rest@2.5.0':
|
||||
dependencies:
|
||||
'@discordjs/collection': 2.1.1
|
||||
'@discordjs/util': 1.1.1
|
||||
'@sapphire/async-queue': 1.5.5
|
||||
'@sapphire/snowflake': 3.5.3
|
||||
'@vladfrangu/async_event_emitter': 2.4.6
|
||||
discord-api-types: 0.38.11
|
||||
magic-bytes.js: 1.12.1
|
||||
tslib: 2.8.1
|
||||
undici: 6.21.1
|
||||
|
||||
'@discordjs/util@1.1.1': {}
|
||||
|
||||
'@discordjs/ws@1.2.2':
|
||||
dependencies:
|
||||
'@discordjs/collection': 2.1.1
|
||||
'@discordjs/rest': 2.5.0
|
||||
'@discordjs/util': 1.1.1
|
||||
'@sapphire/async-queue': 1.5.5
|
||||
'@types/ws': 8.18.1
|
||||
'@vladfrangu/async_event_emitter': 2.4.6
|
||||
discord-api-types: 0.38.11
|
||||
tslib: 2.8.1
|
||||
ws: 8.17.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@emnapi/core@1.4.3':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.0.2
|
||||
@@ -5404,6 +5579,15 @@ snapshots:
|
||||
|
||||
'@rushstack/eslint-patch@1.11.0': {}
|
||||
|
||||
'@sapphire/async-queue@1.5.5': {}
|
||||
|
||||
'@sapphire/shapeshift@4.0.0':
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
lodash: 4.17.21
|
||||
|
||||
'@sapphire/snowflake@3.5.3': {}
|
||||
|
||||
'@selderee/plugin-htmlparser2@0.11.0':
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
@@ -5660,6 +5844,10 @@ snapshots:
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 22.15.29
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@@ -5842,6 +6030,8 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.7.9':
|
||||
optional: true
|
||||
|
||||
'@vladfrangu/async_event_emitter@2.4.6': {}
|
||||
|
||||
'@webassemblyjs/ast@1.14.1':
|
||||
dependencies:
|
||||
'@webassemblyjs/helper-numbers': 1.13.2
|
||||
@@ -6378,6 +6568,27 @@ snapshots:
|
||||
|
||||
direction@2.0.1: {}
|
||||
|
||||
discord-api-types@0.38.11: {}
|
||||
|
||||
discord.js@14.19.3:
|
||||
dependencies:
|
||||
'@discordjs/builders': 1.11.2
|
||||
'@discordjs/collection': 1.5.3
|
||||
'@discordjs/formatters': 0.6.1
|
||||
'@discordjs/rest': 2.5.0
|
||||
'@discordjs/util': 1.1.1
|
||||
'@discordjs/ws': 1.2.2
|
||||
'@sapphire/snowflake': 3.5.3
|
||||
discord-api-types: 0.38.11
|
||||
fast-deep-equal: 3.1.3
|
||||
lodash.snakecase: 4.1.1
|
||||
magic-bytes.js: 1.12.1
|
||||
tslib: 2.8.1
|
||||
undici: 6.21.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
doctrine@2.1.0:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
@@ -7603,6 +7814,8 @@ snapshots:
|
||||
|
||||
lodash.once@4.1.1: {}
|
||||
|
||||
lodash.snakecase@4.1.1: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
loglevel@1.9.1: {}
|
||||
@@ -7629,6 +7842,8 @@ snapshots:
|
||||
|
||||
luxon@3.6.1: {}
|
||||
|
||||
magic-bytes.js@1.12.1: {}
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
@@ -9039,6 +9254,8 @@ snapshots:
|
||||
|
||||
ts-debounce@4.0.0: {}
|
||||
|
||||
ts-mixer@6.0.4: {}
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
dependencies:
|
||||
'@types/json5': 0.0.29
|
||||
@@ -9162,6 +9379,8 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@6.21.1: {}
|
||||
|
||||
unified@11.0.5:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
Reference in New Issue
Block a user