add permission scanner, info panels, image QR scan, invite tracker, ticket templates
Five new features: - /permissions scan: audits admin/ban roles, dangerous bot permissions, public channels, empty/duplicate/useless roles, too-many-admins warning. - /panel create: fixed info panels (rules/support/bewerbung/partner/ rollen/events/faq) with buttons wired into the existing ticket/ register/partner flows, FAQ panels get a question dropdown. - Image moderation: QR-code detection in message attachments via jimp+jsqr (pure JS, no native build deps) with a small scam-pattern check; opt-in toggle in dashboard settings since decoding costs per-message. - Invite tracker extension: now resolves the inviter (not just the code), tracks suspicious joins per invite, and the dashboard growth page gets an invite breakdown + recent-joins table. - Ticket categories: /ticketconfig lets each ticket topic get its own ping role and a modal question template shown before the channel is created; topics without config behave exactly as before. Also fixes two rough edges found along the way: the welcome embed had no way to attach an image despite the backend already supporting it (added URL/file-upload fields + live preview), and Reaction Roles required typing raw role IDs into a textarea (replaced with a proper role picker). Incidentally repairs node_modules/.bin symlinks that were committed as literal broken shell-script text instead of real symlinks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,14 @@ type Props = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
footer?: string;
|
||||
image?: string;
|
||||
accentColor?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
const now = () => new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
export function DiscordPreview({ botName = 'Papo', title, description, footer, accentColor = '#5865f2', children }: Props) {
|
||||
export function DiscordPreview({ botName = 'Papo', title, description, footer, image, accentColor = '#5865f2', children }: Props) {
|
||||
return (
|
||||
<div className="rounded-lg bg-[#313338] p-4">
|
||||
<div className="flex gap-3">
|
||||
@@ -26,7 +27,7 @@ export function DiscordPreview({ botName = 'Papo', title, description, footer, a
|
||||
<span className="text-xs text-[#949ba4]">Heute um {now()}</span>
|
||||
</div>
|
||||
|
||||
{(title || description || footer) && (
|
||||
{(title || description || footer || image) && (
|
||||
<div
|
||||
className="mt-1 max-w-md rounded border-l-4 bg-[#2b2d31] p-3"
|
||||
style={{ borderLeftColor: accentColor }}
|
||||
@@ -36,6 +37,7 @@ export function DiscordPreview({ botName = 'Papo', title, description, footer, a
|
||||
<div className="mt-1 whitespace-pre-wrap text-sm leading-snug text-[#dbdee1]">{description}</div>
|
||||
)}
|
||||
{footer && <div className="mt-2.5 text-xs text-[#949ba4]">{footer}</div>}
|
||||
{image && <img src={image} alt="" className="mt-2.5 max-h-64 w-full max-w-full rounded object-cover" />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import type {
|
||||
AppConfig, User, Guild, NavKey, TicketRecord, StatusService,
|
||||
EventItem, ReactionRoleSet, ModuleItem, LogEntry, SettingsState,
|
||||
SupportLoginConfig, SupportLoginStatus, RegisterForm, RegisterFormField,
|
||||
RegisterApplication, MusicSession, StaffTask, WatchlistEntry, GrowthStats
|
||||
RegisterApplication, MusicSession, StaffTask, WatchlistEntry, GrowthStats,
|
||||
InviteBreakdownEntry, RecentJoinEntry
|
||||
} from '../types';
|
||||
|
||||
const appConfig: AppConfig = (window as any).__PAPO__ || {};
|
||||
@@ -48,6 +49,8 @@ type AppState = {
|
||||
taskDraft: { title: string; description: string };
|
||||
watchlistEntries: WatchlistEntry[];
|
||||
growthStats: GrowthStats | null;
|
||||
inviteBreakdown: InviteBreakdownEntry[];
|
||||
recentJoins: RecentJoinEntry[];
|
||||
};
|
||||
|
||||
type AppContextType = AppState & {
|
||||
@@ -131,6 +134,7 @@ type AppContextType = AppState & {
|
||||
loadWatchlist: () => Promise<void>;
|
||||
removeFromWatchlist: (userId: string) => Promise<void>;
|
||||
loadGrowthStats: () => Promise<void>;
|
||||
loadInviteBreakdown: () => Promise<void>;
|
||||
};
|
||||
|
||||
const AppContext = createContext<AppContextType | null>(null);
|
||||
@@ -165,7 +169,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||
const [eventDraft, setEventDraft] = useState({ title: '', description: '', channelId: '', startsAt: '' });
|
||||
const [statusDraft, setStatusDraft] = useState<any>(null);
|
||||
const [statsDraft, setStatsDraft] = useState<any>(null);
|
||||
const [reactionDraft, setReactionDraft] = useState({ title: '', channelId: '', entries: '' });
|
||||
const [reactionDraft, setReactionDraft] = useState<{ title: string; channelId: string; entries: { emoji: string; roleId: string; label: string; description: string }[] }>({ title: '', channelId: '', entries: [] });
|
||||
const [supportLogin, setSupportLogin] = useState<{ config: SupportLoginConfig; status: SupportLoginStatus; supportRoleId?: string } | null>(null);
|
||||
const [registerForms, setRegisterForms] = useState<RegisterForm[]>([]);
|
||||
const [registerApps, setRegisterApps] = useState<RegisterApplication[]>([]);
|
||||
@@ -190,6 +194,8 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||
const [taskDraft, setTaskDraft] = useState({ title: '', description: '' });
|
||||
const [watchlistEntries, setWatchlistEntries] = useState<WatchlistEntry[]>([]);
|
||||
const [growthStats, setGrowthStats] = useState<GrowthStats | null>(null);
|
||||
const [inviteBreakdown, setInviteBreakdown] = useState<InviteBreakdownEntry[]>([]);
|
||||
const [recentJoins, setRecentJoins] = useState<RecentJoinEntry[]>([]);
|
||||
|
||||
const setSection = useCallback((key: NavKey) => {
|
||||
setSectionState(key);
|
||||
@@ -277,7 +283,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||
setRegisterApps(registerAppsRes.applications || []);
|
||||
setTasks(tasksRes.tasks || []);
|
||||
setWatchlistEntries(watchlistRes.entries || []);
|
||||
setReactionDraft({ title: '', channelId: '', entries: '' });
|
||||
setReactionDraft({ title: '', channelId: '', entries: [] });
|
||||
await Promise.all([loadTicketData(guildId), loadAdminData()]);
|
||||
setStatusMessage('');
|
||||
} catch { setStatusMessage('Daten konnten nicht geladen werden'); }
|
||||
@@ -356,11 +362,10 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
async function saveReactionRole() {
|
||||
const entries = reactionDraft.entries.split('\n').map((l) => l.trim()).filter(Boolean)
|
||||
.map((line) => { const p = line.split('|').map((s) => s.trim()); return { emoji: p[0], roleId: p[1], label: p[2], description: p[3] }; })
|
||||
.filter((e) => e.emoji && e.roleId);
|
||||
const entries = (reactionDraft.entries as any[]).filter((e) => e.emoji && e.roleId);
|
||||
await apiFetch('/reactionroles', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, channelId: reactionDraft.channelId, title: reactionDraft.title, entries }) });
|
||||
await loadGuildData(currentGuildId);
|
||||
setReactionDraft({ title: '', channelId: '', entries: [] });
|
||||
setStatusMessage('Reaction Role gespeichert');
|
||||
}
|
||||
|
||||
@@ -606,6 +611,13 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||
setGrowthStats(res.stats || null);
|
||||
}
|
||||
|
||||
async function loadInviteBreakdown() {
|
||||
if (!currentGuildId) return;
|
||||
const res = await apiFetch<any>(`/growth/invites?guildId=${encodeURIComponent(currentGuildId)}`);
|
||||
setInviteBreakdown(res.breakdown || []);
|
||||
setRecentJoins(res.recentJoins || []);
|
||||
}
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
window.location.href = `${appConfig.baseAuth || '/auth'}/logout`;
|
||||
}, []);
|
||||
@@ -621,6 +633,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||
ticketDetail, ticketMessages, kbEditDraft, automationEditDraft,
|
||||
automodStrikes, registerStatusFilter, registerFormFilter, selectedAppId,
|
||||
appNotes, appHistory, noteDraft, tasks, taskDraft, watchlistEntries, growthStats,
|
||||
inviteBreakdown, recentJoins,
|
||||
setCurrentGuildId, setSection, setSettings, setBirthday, setSupportLogin,
|
||||
setStatusDraft, setStatsDraft, setStatusMessage, loadGuildData,
|
||||
saveSettingsPayload, saveBirthday, saveStatuspage, saveServerStats,
|
||||
@@ -636,7 +649,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||
loadAutomodStrikes, resetAutomodStrike, setRegisterStatusFilter, setRegisterFormFilter,
|
||||
loadRegisterApps, openAppDetail, setNoteDraft, addAppNote,
|
||||
setTaskDraft, createTask, updateTaskStatus, deleteTask,
|
||||
loadWatchlist, removeFromWatchlist, loadGrowthStats,
|
||||
loadWatchlist, removeFromWatchlist, loadGrowthStats, loadInviteBreakdown,
|
||||
}}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader } from '@heroui/react';
|
||||
import { TrendingUp, TrendingDown, Gem, Link2, Handshake } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, Chip } from '@heroui/react';
|
||||
import { TrendingUp, TrendingDown, Gem, Link2, Handshake, Users } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
import { StatCard } from '../components/shared/StatCard';
|
||||
import { BarComparisonChart } from '../components/shared/BarComparisonChart';
|
||||
import { formatDate } from '../utils/formatters';
|
||||
|
||||
export function Growth() {
|
||||
const { currentGuildId, growthStats, loadGrowthStats } = useApp();
|
||||
const { currentGuildId, growthStats, loadGrowthStats, inviteBreakdown, recentJoins, loadInviteBreakdown } = useApp();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentGuildId) loadGrowthStats();
|
||||
if (currentGuildId) {
|
||||
loadGrowthStats();
|
||||
loadInviteBreakdown();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentGuildId]);
|
||||
|
||||
@@ -71,6 +75,64 @@ export function Growth() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid gap-5 xl:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader className="px-5 pt-5 pb-0">
|
||||
<h3 className="text-base font-semibold">Invite-Übersicht (30 Tage)</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-2 p-5">
|
||||
{inviteBreakdown.length ? inviteBreakdown.map((inv) => (
|
||||
<div key={inv.code} className="bg-surface-tertiary flex items-center justify-between gap-2 rounded-xl px-4 py-3 text-sm">
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium truncate">discord.gg/{inv.code}</div>
|
||||
<div className="text-xs text-muted truncate">
|
||||
{inv.inviterId ? `von <@${inv.inviterId}>` : 'Unbekannter Ersteller'} · {inv.uses} Nutzungen gesamt
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<Chip size="sm" variant="soft" color="accent">{inv.joins30} Joins</Chip>
|
||||
{inv.suspiciousJoins30 > 0 && (
|
||||
<Chip size="sm" variant="soft" color="danger">{inv.suspiciousJoins30} auffällig</Chip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)) : (
|
||||
<div className="flex flex-col items-center gap-2 py-4 text-center text-xs text-muted">
|
||||
<Link2 size={20} />
|
||||
Keine Invite-Nutzung in den letzten 30 Tagen
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="px-5 pt-5 pb-0">
|
||||
<h3 className="text-base font-semibold">Letzte Beitritte</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex max-h-96 flex-col gap-2 overflow-y-auto p-5">
|
||||
{recentJoins.length ? recentJoins.map((j) => (
|
||||
<div key={j.id} className="bg-surface-tertiary flex items-center gap-3 rounded-xl px-4 py-3 text-sm">
|
||||
<Users size={14} className="text-muted shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate">
|
||||
User-ID {j.userId}{j.inviterId ? ` · eingeladen von ${j.inviterId}` : ''}
|
||||
</div>
|
||||
<div className="text-xs text-muted">
|
||||
{j.inviteCode ? `discord.gg/${j.inviteCode}` : 'Unbekannter Invite'} · {formatDate(j.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
{j.suspicious && <Chip size="sm" variant="soft" color="danger">Verdächtig</Chip>}
|
||||
</div>
|
||||
)) : (
|
||||
<div className="flex flex-col items-center gap-2 py-4 text-center text-xs text-muted">
|
||||
<Users size={20} />
|
||||
Noch keine Beitritte erfasst
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</SectionCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Tag, Save, Hash, List } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, Input, Button, Chip, TextField, Label } from '@heroui/react';
|
||||
import { Tag, Save, Plus, X } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||
import { RoleSelect } from '../components/shared/RoleSelect';
|
||||
import { useGuildResources } from '../hooks/useGuildResources';
|
||||
|
||||
export function ReactionRoles() {
|
||||
const { reactionRoles, reactionDraft, setReactionDraft, saveReactionRole, currentGuildId } = useApp();
|
||||
const { channels } = useGuildResources(currentGuildId);
|
||||
const { channels, roles } = useGuildResources(currentGuildId);
|
||||
const [entryDraft, setEntryDraft] = useState({ emoji: '', roleId: '', label: '' });
|
||||
|
||||
const addEntry = () => {
|
||||
if (!entryDraft.emoji.trim() || !entryDraft.roleId) return;
|
||||
setReactionDraft((s) => ({
|
||||
...s,
|
||||
entries: [...s.entries, { emoji: entryDraft.emoji.trim(), roleId: entryDraft.roleId, label: entryDraft.label.trim(), description: '' }]
|
||||
}));
|
||||
setEntryDraft({ emoji: '', roleId: '', label: '' });
|
||||
};
|
||||
|
||||
const removeEntry = (index: number) => {
|
||||
setReactionDraft((s) => ({ ...s, entries: s.entries.filter((_: any, i: number) => i !== index) }));
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionCard title="Reaction Roles" subtitle="Sets anzeigen und neue Zuordnungen anlegen">
|
||||
@@ -61,20 +77,56 @@ export function ReactionRoles() {
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextField>
|
||||
<div>
|
||||
<Label>Einträge</Label>
|
||||
<TextArea
|
||||
placeholder="Emoji | Role ID | Label :emoji: | 123456789 | Rolle 1 :wave: | 987654321 | Rolle 2"
|
||||
rows={6}
|
||||
value={reactionDraft.entries}
|
||||
onChange={(e) => setReactionDraft((s) => ({ ...s, entries: e.target.value }))}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
Pro Zeile: Emoji | Role ID | Label (optional) | Beschreibung (optional)
|
||||
</p>
|
||||
</TextField>
|
||||
<div className="mt-2 flex flex-col gap-2">
|
||||
{reactionDraft.entries.map((entry, i) => {
|
||||
const role = roles.find((r) => r.id === entry.roleId);
|
||||
return (
|
||||
<div key={i} className="bg-default-soft flex items-center gap-2 rounded-xl py-1.5 pl-3 pr-1.5 text-sm">
|
||||
<span>{entry.emoji}</span>
|
||||
{role?.color && <span className="size-2.5 shrink-0 rounded-full" style={{ backgroundColor: role.color }} />}
|
||||
<span className="min-w-0 flex-1 truncate">{entry.label || role?.name || entry.roleId}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-5 shrink-0 items-center justify-center rounded-full text-muted hover:bg-danger-soft hover:text-danger"
|
||||
onClick={() => removeEntry(i)}
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!reactionDraft.entries.length && <p className="text-xs text-muted">Noch keine Einträge hinzugefügt.</p>}
|
||||
</div>
|
||||
|
||||
<Button variant="primary" onPress={saveReactionRole}>
|
||||
<div className="mt-3 flex flex-col gap-2 rounded-xl border border-border p-3">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
className="w-16 shrink-0"
|
||||
placeholder="😀"
|
||||
value={entryDraft.emoji}
|
||||
onChange={(e) => setEntryDraft((s) => ({ ...s, emoji: e.target.value }))}
|
||||
/>
|
||||
<RoleSelect
|
||||
options={roles}
|
||||
value={entryDraft.roleId}
|
||||
onChange={(id) => setEntryDraft((s) => ({ ...s, roleId: id }))}
|
||||
placeholder="Rolle wählen"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Label (optional, z.B. Gamer)"
|
||||
value={entryDraft.label}
|
||||
onChange={(e) => setEntryDraft((s) => ({ ...s, label: e.target.value }))}
|
||||
/>
|
||||
<Button size="sm" variant="tertiary" onPress={addEntry} isDisabled={!entryDraft.emoji.trim() || !entryDraft.roleId}>
|
||||
<Plus size={14} /> Eintrag hinzufügen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="primary" onPress={saveReactionRole} isDisabled={!reactionDraft.entries.length || !reactionDraft.channelId}>
|
||||
<Save size={16} /> Reaction Role speichern
|
||||
</Button>
|
||||
</CardContent>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Card, CardContent, CardHeader, Button, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Settings, Save, Logs, Bell, Shield, Edit3, Trash2 } from 'lucide-react';
|
||||
import { Settings, Save, Logs, Bell, Shield, Edit3, Trash2, ImageIcon, QrCode } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||
import { RoleSelect } from '../components/shared/RoleSelect';
|
||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
||||
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||
import { useGuildResources } from '../hooks/useGuildResources';
|
||||
|
||||
export function SettingsPage() {
|
||||
@@ -99,6 +100,34 @@ export function SettingsPage() {
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="px-5 pt-5 pb-0">
|
||||
<h3 className="text-base font-semibold">Bild-Moderation</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<ModuleActiveToggle
|
||||
icon={<ImageIcon size={16} />}
|
||||
title="Bild-Moderation aktiv"
|
||||
description="Scannt Bild-Anhänge auf QR-Codes und gleicht sie mit bekannten Scam-Mustern ab."
|
||||
isSelected={settings.imageModerationConfig?.enabled === true}
|
||||
onChange={(v) => setSettings((s) => ({ ...s, imageModerationConfig: { ...(s.imageModerationConfig || {}), enabled: v } }))}
|
||||
/>
|
||||
|
||||
<AppSwitch
|
||||
isSelected={settings.imageModerationConfig?.alertOnly === true}
|
||||
onChange={(v) => setSettings((s) => ({ ...s, imageModerationConfig: { ...(s.imageModerationConfig || {}), alertOnly: v } }))}
|
||||
label={<div className="flex items-center gap-2"><QrCode size={14} /> Nur bei erkanntem Scam-Verdacht alarmieren</div>}
|
||||
description="Wenn aus, wird bei jedem gefundenen QR-Code alarmiert, nicht nur bei verdächtigen."
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Button variant="primary" onPress={() => saveSettingsPayload(settings, 'Settings gespeichert')}>
|
||||
<Save size={16} /> Speichern
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</SectionCard>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, TextArea, Button, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Sparkles, Save } from 'lucide-react';
|
||||
import { Sparkles, Save, X } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
import { DiscordPreview } from '../components/shared/DiscordPreview';
|
||||
@@ -7,10 +7,28 @@ import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||
import { useGuildResources } from '../hooks/useGuildResources';
|
||||
|
||||
const MAX_IMAGE_BYTES = 3 * 1024 * 1024;
|
||||
|
||||
export function Welcome() {
|
||||
const { settings, setSettings, saveSettingsPayload, currentGuildId } = useApp();
|
||||
const { channels } = useGuildResources(currentGuildId);
|
||||
|
||||
const imagePreview = settings.welcomeConfig?.embedImageData || settings.welcomeConfig?.embedImage;
|
||||
|
||||
const handleImageUpload = (file: File) => {
|
||||
if (file.size > MAX_IMAGE_BYTES) {
|
||||
alert('Bild ist zu groß (max. 3 MB).');
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedImageData: reader.result as string, embedImage: undefined } }));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const clearImage = () => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedImage: undefined, embedImageData: undefined } }));
|
||||
|
||||
return (
|
||||
<SectionCard title="Willkommen" subtitle="Welcome-Embeds und Join-Nachrichten">
|
||||
<div className="grid gap-5 xl:grid-cols-2">
|
||||
@@ -67,6 +85,38 @@ export function Welcome() {
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextField>
|
||||
<Label>Bild-URL</Label>
|
||||
<Input
|
||||
placeholder="https://..."
|
||||
value={settings.welcomeConfig?.embedImage || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedImage: e.target.value, embedImageData: undefined } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextField>
|
||||
<Label>oder Bild hochladen</Label>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
className="text-sm text-muted"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) handleImageUpload(file);
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
{imagePreview && (
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={imagePreview} alt="Vorschau" className="h-12 w-20 rounded object-cover" />
|
||||
<Button size="sm" variant="danger-soft" onPress={clearImage}>
|
||||
<X size={14} /> Bild entfernen
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
<Button size="lg" variant="primary" onPress={() => saveSettingsPayload({ welcomeConfig: settings.welcomeConfig || {} }, 'Welcome gespeichert')}>
|
||||
@@ -87,6 +137,7 @@ export function Welcome() {
|
||||
title={settings.welcomeConfig?.embedTitle || 'Willkommen!'}
|
||||
description={settings.welcomeConfig?.embedDescription || 'Willkommen auf dem Server!'}
|
||||
footer={settings.welcomeConfig?.embedFooter}
|
||||
image={imagePreview}
|
||||
/>
|
||||
|
||||
<p className="text-sm text-muted">
|
||||
|
||||
@@ -179,6 +179,23 @@ export type GrowthStats = {
|
||||
dailyJoins: { day: string; count: number }[];
|
||||
};
|
||||
|
||||
export type InviteBreakdownEntry = {
|
||||
code: string;
|
||||
inviterId?: string;
|
||||
uses: number;
|
||||
joins30: number;
|
||||
suspiciousJoins30: number;
|
||||
};
|
||||
|
||||
export type RecentJoinEntry = {
|
||||
id: string;
|
||||
userId: string;
|
||||
inviterId?: string | null;
|
||||
inviteCode?: string | null;
|
||||
suspicious: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type MusicSession = {
|
||||
guildId: string;
|
||||
nowPlaying?: { title: string; url: string } | null;
|
||||
|
||||
17
node_modules/.bin/acorn
generated
vendored
17
node_modules/.bin/acorn
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../acorn/bin/acorn" "$@"
|
||||
else
|
||||
exec node "$basedir/../acorn/bin/acorn" "$@"
|
||||
fi
|
||||
../acorn/bin/acorn
|
||||
17
node_modules/.bin/mime
generated
vendored
17
node_modules/.bin/mime
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../mime/cli.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../mime/cli.js" "$@"
|
||||
fi
|
||||
../mime/cli.js
|
||||
17
node_modules/.bin/mkdirp
generated
vendored
17
node_modules/.bin/mkdirp
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../mkdirp/bin/cmd.js" "$@"
|
||||
fi
|
||||
../mkdirp/bin/cmd.js
|
||||
17
node_modules/.bin/prisma
generated
vendored
17
node_modules/.bin/prisma
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../prisma/build/index.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../prisma/build/index.js" "$@"
|
||||
fi
|
||||
../prisma/build/index.js
|
||||
17
node_modules/.bin/resolve
generated
vendored
17
node_modules/.bin/resolve
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../resolve/bin/resolve" "$@"
|
||||
else
|
||||
exec node "$basedir/../resolve/bin/resolve" "$@"
|
||||
fi
|
||||
../resolve/bin/resolve
|
||||
17
node_modules/.bin/rimraf
generated
vendored
17
node_modules/.bin/rimraf
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../rimraf/bin.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../rimraf/bin.js" "$@"
|
||||
fi
|
||||
../rimraf/bin.js
|
||||
17
node_modules/.bin/tree-kill
generated
vendored
17
node_modules/.bin/tree-kill
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../tree-kill/cli.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../tree-kill/cli.js" "$@"
|
||||
fi
|
||||
../tree-kill/cli.js
|
||||
17
node_modules/.bin/ts-node
generated
vendored
17
node_modules/.bin/ts-node
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node/dist/bin.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node/dist/bin.js" "$@"
|
||||
fi
|
||||
../ts-node/dist/bin.js
|
||||
17
node_modules/.bin/ts-node-cwd
generated
vendored
17
node_modules/.bin/ts-node-cwd
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node/dist/bin-cwd.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node/dist/bin-cwd.js" "$@"
|
||||
fi
|
||||
../ts-node/dist/bin-cwd.js
|
||||
17
node_modules/.bin/ts-node-dev
generated
vendored
17
node_modules/.bin/ts-node-dev
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node-dev/lib/bin.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node-dev/lib/bin.js" "$@"
|
||||
fi
|
||||
../ts-node-dev/lib/bin.js
|
||||
17
node_modules/.bin/ts-node-esm
generated
vendored
17
node_modules/.bin/ts-node-esm
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node/dist/bin-esm.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node/dist/bin-esm.js" "$@"
|
||||
fi
|
||||
../ts-node/dist/bin-esm.js
|
||||
17
node_modules/.bin/ts-node-script
generated
vendored
17
node_modules/.bin/ts-node-script
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node/dist/bin-script.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node/dist/bin-script.js" "$@"
|
||||
fi
|
||||
../ts-node/dist/bin-script.js
|
||||
17
node_modules/.bin/ts-node-transpile-only
generated
vendored
17
node_modules/.bin/ts-node-transpile-only
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node/dist/bin-transpile.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node/dist/bin-transpile.js" "$@"
|
||||
fi
|
||||
../ts-node/dist/bin-transpile.js
|
||||
17
node_modules/.bin/ts-script
generated
vendored
17
node_modules/.bin/ts-script
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node/dist/bin-script-deprecated.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node/dist/bin-script-deprecated.js" "$@"
|
||||
fi
|
||||
../ts-node/dist/bin-script-deprecated.js
|
||||
17
node_modules/.bin/tsc
generated
vendored
17
node_modules/.bin/tsc
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsc" "$@"
|
||||
fi
|
||||
../typescript/bin/tsc
|
||||
17
node_modules/.bin/tsnd
generated
vendored
17
node_modules/.bin/tsnd
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../ts-node-dev/lib/bin.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../ts-node-dev/lib/bin.js" "$@"
|
||||
fi
|
||||
../ts-node-dev/lib/bin.js
|
||||
17
node_modules/.bin/tsserver
generated
vendored
17
node_modules/.bin/tsserver
generated
vendored
@@ -1,16 +1 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
||||
fi
|
||||
../typescript/bin/tsserver
|
||||
823
node_modules/.package-lock.json
generated
vendored
823
node_modules/.package-lock.json
generated
vendored
@@ -4,6 +4,16 @@
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@borewit/text-codec": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
|
||||
"integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@@ -264,6 +274,430 @@
|
||||
"scripts/actions/documentation"
|
||||
]
|
||||
},
|
||||
"node_modules/@jimp/core": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz",
|
||||
"integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/file-ops": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"await-to-js": "^3.0.0",
|
||||
"exif-parser": "^0.1.12",
|
||||
"file-type": "^21.3.3",
|
||||
"mime": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/core/node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/diff": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz",
|
||||
"integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"pixelmatch": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/file-ops": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz",
|
||||
"integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-bmp": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz",
|
||||
"integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"bmp-ts": "^1.0.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-gif": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz",
|
||||
"integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"gifwrap": "^0.10.1",
|
||||
"omggif": "^1.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-jpeg": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz",
|
||||
"integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"jpeg-js": "^0.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-png": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz",
|
||||
"integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"pngjs": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-tiff": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz",
|
||||
"integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"utif2": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-blit": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz",
|
||||
"integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-blur": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz",
|
||||
"integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/utils": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-circle": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz",
|
||||
"integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-color": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz",
|
||||
"integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-contain": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz",
|
||||
"integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-blit": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-cover": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz",
|
||||
"integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-crop": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-crop": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz",
|
||||
"integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-displace": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz",
|
||||
"integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-dither": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz",
|
||||
"integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-fisheye": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz",
|
||||
"integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-flip": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz",
|
||||
"integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-hash": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz",
|
||||
"integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/js-bmp": "1.6.1",
|
||||
"@jimp/js-jpeg": "1.6.1",
|
||||
"@jimp/js-png": "1.6.1",
|
||||
"@jimp/js-tiff": "1.6.1",
|
||||
"@jimp/plugin-color": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"any-base": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-mask": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz",
|
||||
"integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-print": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz",
|
||||
"integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/js-jpeg": "1.6.1",
|
||||
"@jimp/js-png": "1.6.1",
|
||||
"@jimp/plugin-blit": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"parse-bmfont-ascii": "^1.0.6",
|
||||
"parse-bmfont-binary": "^1.0.6",
|
||||
"parse-bmfont-xml": "^1.1.6",
|
||||
"simple-xml-to-json": "^1.2.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-quantize": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz",
|
||||
"integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"image-q": "^4.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-resize": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz",
|
||||
"integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-rotate": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz",
|
||||
"integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-crop": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-threshold": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz",
|
||||
"integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-color": "1.6.1",
|
||||
"@jimp/plugin-hash": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/types": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz",
|
||||
"integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/utils": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz",
|
||||
"integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"tinycolor2": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -421,22 +855,84 @@
|
||||
"@snazzah/davey-win32-x64-msvc": "0.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@snazzah/davey-win32-x64-msvc": {
|
||||
"node_modules/@snazzah/davey-linux-x64-gnu": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@snazzah/davey-win32-x64-msvc/-/davey-win32-x64-msvc-0.1.8.tgz",
|
||||
"integrity": "sha512-JKIco1miwtM4NgVwU/H9TJdUaSlJ+kdtydy3+tiV9cmJv0u1SM2NpwjV85H44xAQWW2zcRBHP0ZDriciHw09qQ==",
|
||||
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-x64-gnu/-/davey-linux-x64-gnu-0.1.8.tgz",
|
||||
"integrity": "sha512-yghgG7iXZUHy734Cq3PcgrbRnLhhB233JNTX5VPRxRqdwFAg2MzAJ2iSWpP12K6hSqKq9hw0sdt8CNpr0mEXjQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@snazzah/davey-linux-x64-musl": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-x64-musl/-/davey-linux-x64-musl-0.1.8.tgz",
|
||||
"integrity": "sha512-WwCiAge27ZOEu7NRx5NFjpCAkGcUGHGtoROBY4ElRYE0Tp3DfuzWU06qSu6JBQPlzhTTBN29X2/kGP8iRUwqnQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
|
||||
"integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.3",
|
||||
"token-types": "^6.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
@@ -724,6 +1220,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/any-base": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
|
||||
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
@@ -771,6 +1273,15 @@
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/await-to-js": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
|
||||
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -831,6 +1342,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bmp-ts": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz",
|
||||
"integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
@@ -1279,6 +1796,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/exif-parser": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
|
||||
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
@@ -1381,6 +1903,24 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "21.3.4",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz",
|
||||
"integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/inflate": "^0.4.1",
|
||||
"strtok3": "^10.3.4",
|
||||
"token-types": "^6.1.1",
|
||||
"uint8array-extras": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -1527,6 +2067,16 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gifwrap": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz",
|
||||
"integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"image-q": "^4.0.0",
|
||||
"omggif": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -1682,6 +2232,41 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/image-q": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz",
|
||||
"integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "16.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/image-q/node_modules/@types/node": {
|
||||
"version": "16.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
|
||||
"integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -1779,6 +2364,56 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jimp": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz",
|
||||
"integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/diff": "1.6.1",
|
||||
"@jimp/js-bmp": "1.6.1",
|
||||
"@jimp/js-gif": "1.6.1",
|
||||
"@jimp/js-jpeg": "1.6.1",
|
||||
"@jimp/js-png": "1.6.1",
|
||||
"@jimp/js-tiff": "1.6.1",
|
||||
"@jimp/plugin-blit": "1.6.1",
|
||||
"@jimp/plugin-blur": "1.6.1",
|
||||
"@jimp/plugin-circle": "1.6.1",
|
||||
"@jimp/plugin-color": "1.6.1",
|
||||
"@jimp/plugin-contain": "1.6.1",
|
||||
"@jimp/plugin-cover": "1.6.1",
|
||||
"@jimp/plugin-crop": "1.6.1",
|
||||
"@jimp/plugin-displace": "1.6.1",
|
||||
"@jimp/plugin-dither": "1.6.1",
|
||||
"@jimp/plugin-fisheye": "1.6.1",
|
||||
"@jimp/plugin-flip": "1.6.1",
|
||||
"@jimp/plugin-hash": "1.6.1",
|
||||
"@jimp/plugin-mask": "1.6.1",
|
||||
"@jimp/plugin-print": "1.6.1",
|
||||
"@jimp/plugin-quantize": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/plugin-rotate": "1.6.1",
|
||||
"@jimp/plugin-threshold": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jpeg-js": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
||||
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/jsqr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
|
||||
"integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/libsodium": {
|
||||
"version": "0.7.15",
|
||||
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz",
|
||||
@@ -2080,6 +2715,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/omggif": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
|
||||
"integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -2110,6 +2751,34 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parse-bmfont-ascii": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
|
||||
"integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parse-bmfont-binary": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
|
||||
"integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parse-bmfont-xml": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
|
||||
"integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-parse-from-string": "^1.0.0",
|
||||
"xml2js": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-cache-control": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
|
||||
@@ -2159,6 +2828,27 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
|
||||
"integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pngjs": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pixelmatch": "bin/pixelmatch"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch/node_modules/pngjs": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
|
||||
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/play-audio": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
|
||||
@@ -2177,6 +2867,15 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prism-media": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
|
||||
@@ -2393,6 +3092,15 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
|
||||
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
@@ -2549,6 +3257,15 @@
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/simple-xml-to-json": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz",
|
||||
"integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.12.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sodium-native": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz",
|
||||
@@ -2643,6 +3360,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "10.3.5",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz",
|
||||
"integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
@@ -2673,6 +3406,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -2695,6 +3434,24 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
|
||||
"integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@borewit/text-codec": "^0.2.1",
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
@@ -2860,6 +3617,18 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/uint8array-extras": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
|
||||
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
@@ -2884,6 +3653,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utif2": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz",
|
||||
"integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -2967,6 +3745,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-parse-from-string": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
|
||||
"integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
@@ -2992,6 +3798,15 @@
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
node_modules/.prisma/client/edge.js
generated
vendored
25
node_modules/.prisma/client/edge.js
generated
vendored
File diff suppressed because one or more lines are too long
19
node_modules/.prisma/client/index-browser.js
generated
vendored
19
node_modules/.prisma/client/index-browser.js
generated
vendored
@@ -151,6 +151,8 @@ exports.Prisma.GuildSettingsScalarFieldEnum = {
|
||||
brandingConfig: 'brandingConfig',
|
||||
partnerConfig: 'partnerConfig',
|
||||
galleryConfig: 'galleryConfig',
|
||||
imageModerationConfig: 'imageModerationConfig',
|
||||
ticketConfig: 'ticketConfig',
|
||||
supportRoleId: 'supportRoleId',
|
||||
updatedAt: 'updatedAt',
|
||||
createdAt: 'createdAt'
|
||||
@@ -469,6 +471,20 @@ exports.Prisma.GuildGrowthEventScalarFieldEnum = {
|
||||
type: 'type',
|
||||
userId: 'userId',
|
||||
inviteCode: 'inviteCode',
|
||||
inviterId: 'inviterId',
|
||||
suspicious: 'suspicious',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
exports.Prisma.InfoPanelScalarFieldEnum = {
|
||||
id: 'id',
|
||||
guildId: 'guildId',
|
||||
channelId: 'channelId',
|
||||
messageId: 'messageId',
|
||||
type: 'type',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
items: 'items',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -534,7 +550,8 @@ exports.Prisma.ModelName = {
|
||||
PartnerRequest: 'PartnerRequest',
|
||||
GalleryPost: 'GalleryPost',
|
||||
GalleryVote: 'GalleryVote',
|
||||
GuildGrowthEvent: 'GuildGrowthEvent'
|
||||
GuildGrowthEvent: 'GuildGrowthEvent',
|
||||
InfoPanel: 'InfoPanel'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
1321
node_modules/.prisma/client/index.d.ts
generated
vendored
1321
node_modules/.prisma/client/index.d.ts
generated
vendored
File diff suppressed because it is too large
Load Diff
25
node_modules/.prisma/client/index.js
generated
vendored
25
node_modules/.prisma/client/index.js
generated
vendored
File diff suppressed because one or more lines are too long
19
node_modules/.prisma/client/wasm.js
generated
vendored
19
node_modules/.prisma/client/wasm.js
generated
vendored
@@ -151,6 +151,8 @@ exports.Prisma.GuildSettingsScalarFieldEnum = {
|
||||
brandingConfig: 'brandingConfig',
|
||||
partnerConfig: 'partnerConfig',
|
||||
galleryConfig: 'galleryConfig',
|
||||
imageModerationConfig: 'imageModerationConfig',
|
||||
ticketConfig: 'ticketConfig',
|
||||
supportRoleId: 'supportRoleId',
|
||||
updatedAt: 'updatedAt',
|
||||
createdAt: 'createdAt'
|
||||
@@ -469,6 +471,20 @@ exports.Prisma.GuildGrowthEventScalarFieldEnum = {
|
||||
type: 'type',
|
||||
userId: 'userId',
|
||||
inviteCode: 'inviteCode',
|
||||
inviterId: 'inviterId',
|
||||
suspicious: 'suspicious',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
exports.Prisma.InfoPanelScalarFieldEnum = {
|
||||
id: 'id',
|
||||
guildId: 'guildId',
|
||||
channelId: 'channelId',
|
||||
messageId: 'messageId',
|
||||
type: 'type',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
items: 'items',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -534,7 +550,8 @@ exports.Prisma.ModelName = {
|
||||
PartnerRequest: 'PartnerRequest',
|
||||
GalleryPost: 'GalleryPost',
|
||||
GalleryVote: 'GalleryVote',
|
||||
GuildGrowthEvent: 'GuildGrowthEvent'
|
||||
GuildGrowthEvent: 'GuildGrowthEvent',
|
||||
InfoPanel: 'InfoPanel'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
802
package-lock.json
generated
802
package-lock.json
generated
@@ -20,6 +20,8 @@
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"jimp": "^1.6.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"libsodium-wrappers": "^0.7.13",
|
||||
"play-dl": "^1.9.7",
|
||||
"sodium-native": "^4.0.4"
|
||||
@@ -34,6 +36,16 @@
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@borewit/text-codec": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
|
||||
"integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@@ -325,6 +337,430 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/core": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz",
|
||||
"integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/file-ops": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"await-to-js": "^3.0.0",
|
||||
"exif-parser": "^0.1.12",
|
||||
"file-type": "^21.3.3",
|
||||
"mime": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/core/node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/diff": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz",
|
||||
"integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"pixelmatch": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/file-ops": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz",
|
||||
"integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-bmp": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz",
|
||||
"integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"bmp-ts": "^1.0.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-gif": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz",
|
||||
"integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"gifwrap": "^0.10.1",
|
||||
"omggif": "^1.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-jpeg": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz",
|
||||
"integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"jpeg-js": "^0.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-png": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz",
|
||||
"integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"pngjs": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-tiff": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz",
|
||||
"integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"utif2": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-blit": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz",
|
||||
"integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-blur": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz",
|
||||
"integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/utils": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-circle": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz",
|
||||
"integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-color": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz",
|
||||
"integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-contain": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz",
|
||||
"integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-blit": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-cover": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz",
|
||||
"integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-crop": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-crop": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz",
|
||||
"integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-displace": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz",
|
||||
"integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-dither": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz",
|
||||
"integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-fisheye": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz",
|
||||
"integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-flip": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz",
|
||||
"integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-hash": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz",
|
||||
"integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/js-bmp": "1.6.1",
|
||||
"@jimp/js-jpeg": "1.6.1",
|
||||
"@jimp/js-png": "1.6.1",
|
||||
"@jimp/js-tiff": "1.6.1",
|
||||
"@jimp/plugin-color": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"any-base": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-mask": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz",
|
||||
"integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-print": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz",
|
||||
"integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/js-jpeg": "1.6.1",
|
||||
"@jimp/js-png": "1.6.1",
|
||||
"@jimp/plugin-blit": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"parse-bmfont-ascii": "^1.0.6",
|
||||
"parse-bmfont-binary": "^1.0.6",
|
||||
"parse-bmfont-xml": "^1.1.6",
|
||||
"simple-xml-to-json": "^1.2.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-quantize": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz",
|
||||
"integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"image-q": "^4.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-resize": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz",
|
||||
"integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-rotate": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz",
|
||||
"integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-crop": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-threshold": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz",
|
||||
"integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/plugin-color": "1.6.1",
|
||||
"@jimp/plugin-hash": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/types": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz",
|
||||
"integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/utils": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz",
|
||||
"integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.1",
|
||||
"tinycolor2": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -718,6 +1154,52 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
|
||||
"integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.3",
|
||||
"token-types": "^6.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
@@ -1015,6 +1497,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/any-base": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
|
||||
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
@@ -1062,6 +1550,15 @@
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/await-to-js": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
|
||||
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -1122,6 +1619,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bmp-ts": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz",
|
||||
"integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
@@ -1570,6 +2073,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/exif-parser": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
|
||||
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
@@ -1672,6 +2180,24 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "21.3.4",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz",
|
||||
"integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/inflate": "^0.4.1",
|
||||
"strtok3": "^10.3.4",
|
||||
"token-types": "^6.1.1",
|
||||
"uint8array-extras": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -1755,7 +2281,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -1833,6 +2358,16 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gifwrap": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz",
|
||||
"integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"image-q": "^4.0.0",
|
||||
"omggif": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -1988,6 +2523,41 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/image-q": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz",
|
||||
"integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "16.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/image-q/node_modules/@types/node": {
|
||||
"version": "16.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
|
||||
"integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -2085,6 +2655,56 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jimp": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz",
|
||||
"integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.1",
|
||||
"@jimp/diff": "1.6.1",
|
||||
"@jimp/js-bmp": "1.6.1",
|
||||
"@jimp/js-gif": "1.6.1",
|
||||
"@jimp/js-jpeg": "1.6.1",
|
||||
"@jimp/js-png": "1.6.1",
|
||||
"@jimp/js-tiff": "1.6.1",
|
||||
"@jimp/plugin-blit": "1.6.1",
|
||||
"@jimp/plugin-blur": "1.6.1",
|
||||
"@jimp/plugin-circle": "1.6.1",
|
||||
"@jimp/plugin-color": "1.6.1",
|
||||
"@jimp/plugin-contain": "1.6.1",
|
||||
"@jimp/plugin-cover": "1.6.1",
|
||||
"@jimp/plugin-crop": "1.6.1",
|
||||
"@jimp/plugin-displace": "1.6.1",
|
||||
"@jimp/plugin-dither": "1.6.1",
|
||||
"@jimp/plugin-fisheye": "1.6.1",
|
||||
"@jimp/plugin-flip": "1.6.1",
|
||||
"@jimp/plugin-hash": "1.6.1",
|
||||
"@jimp/plugin-mask": "1.6.1",
|
||||
"@jimp/plugin-print": "1.6.1",
|
||||
"@jimp/plugin-quantize": "1.6.1",
|
||||
"@jimp/plugin-resize": "1.6.1",
|
||||
"@jimp/plugin-rotate": "1.6.1",
|
||||
"@jimp/plugin-threshold": "1.6.1",
|
||||
"@jimp/types": "1.6.1",
|
||||
"@jimp/utils": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jpeg-js": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
||||
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/jsqr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
|
||||
"integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/libsodium": {
|
||||
"version": "0.7.15",
|
||||
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz",
|
||||
@@ -2386,6 +3006,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/omggif": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
|
||||
"integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -2416,6 +3042,34 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parse-bmfont-ascii": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
|
||||
"integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parse-bmfont-binary": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
|
||||
"integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parse-bmfont-xml": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
|
||||
"integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-parse-from-string": "^1.0.0",
|
||||
"xml2js": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-cache-control": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
|
||||
@@ -2465,6 +3119,27 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
|
||||
"integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pngjs": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pixelmatch": "bin/pixelmatch"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch/node_modules/pngjs": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
|
||||
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/play-audio": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
|
||||
@@ -2483,6 +3158,15 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prism-media": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
|
||||
@@ -2699,6 +3383,15 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
|
||||
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
@@ -2855,6 +3548,15 @@
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/simple-xml-to-json": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz",
|
||||
"integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.12.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sodium-native": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz",
|
||||
@@ -2949,6 +3651,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "10.3.5",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz",
|
||||
"integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
@@ -2979,6 +3697,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -3001,6 +3725,24 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
|
||||
"integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@borewit/text-codec": "^0.2.1",
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
@@ -3166,6 +3908,18 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/uint8array-extras": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
|
||||
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
@@ -3190,6 +3944,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utif2": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz",
|
||||
"integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -3273,6 +4036,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-parse-from-string": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
|
||||
"integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
@@ -3298,6 +4089,15 @@
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/opus": "^0.9.0",
|
||||
"@discordjs/rest": "^2.2.0",
|
||||
"@discordjs/voice": "^0.19.0",
|
||||
"@discordjs/opus": "^0.9.0",
|
||||
"@prisma/client": "^5.4.2",
|
||||
"@snazzah/davey": "^0.1.8",
|
||||
"cookie-parser": "^1.4.6",
|
||||
@@ -24,10 +24,12 @@
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"jimp": "^1.6.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"libsodium-wrappers": "^0.7.13",
|
||||
"play-dl": "^1.9.7",
|
||||
"sodium-native": "^4.0.4",
|
||||
"ffmpeg-static": "^5.2.0"
|
||||
"sodium-native": "^4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
|
||||
65
src/commands/admin/permissions.ts
Normal file
65
src/commands/admin/permissions.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ChatInputCommandInteraction, EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types';
|
||||
import { context } from '../../config/context';
|
||||
|
||||
function formatList(items: { id: string; name: string }[], max = 10): string {
|
||||
if (!items.length) return 'Keine';
|
||||
const shown = items.slice(0, max).map((i) => i.name);
|
||||
const rest = items.length - shown.length;
|
||||
return shown.join(', ') + (rest > 0 ? ` (+${rest} weitere)` : '');
|
||||
}
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('permissions')
|
||||
.setDescription('Analysiert Rollen und Berechtigungen auf Sicherheitsrisiken.')
|
||||
.addSubcommand((sub) => sub.setName('scan').setDescription('Startet die Rechte-Analyse.'))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const result = await context.permissionScan.scan(interaction.guild);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('🔐 Rechte-Scan')
|
||||
.setColor(result.tooManyAdmins ? 0xdc2626 : 0xf97316)
|
||||
.setDescription(`Analyse für **${interaction.guild.name}**`)
|
||||
.addFields(
|
||||
{ name: 'Admin-Rollen', value: formatList(result.adminRoles), inline: false },
|
||||
{ name: 'Rollen, die bannen können', value: formatList(result.banRoles), inline: false },
|
||||
{
|
||||
name: 'Bots mit gefährlichen Rechten',
|
||||
value: result.dangerousBots.length
|
||||
? result.dangerousBots.slice(0, 10).map((b) => `${b.tag}: ${b.perms.join(', ')}`).join('\n')
|
||||
: 'Keine',
|
||||
inline: false
|
||||
},
|
||||
{ name: 'Öffentliche Kanäle', value: formatList(result.publicChannels), inline: false },
|
||||
{ name: '@everyone kann schreiben', value: formatList(result.everyoneCanSendChannels), inline: false },
|
||||
{ name: 'Leere Rollen', value: formatList(result.emptyRoles), inline: false },
|
||||
{
|
||||
name: 'Doppelte Rollen (gleiche Rechte)',
|
||||
value: result.duplicateRoleGroups.length
|
||||
? result.duplicateRoleGroups.slice(0, 5).map((g) => g.map((r) => r.name).join(' = ')).join('\n')
|
||||
: 'Keine',
|
||||
inline: false
|
||||
},
|
||||
{ name: 'Nutzlose Rollen (keine Rechte, keine Mitglieder)', value: formatList(result.uselessRoles), inline: false },
|
||||
{
|
||||
name: '⚠️ Zu viele Admin-Rechte?',
|
||||
value: result.tooManyAdmins
|
||||
? `Ja — ${result.adminRoles.length} Admin-Rolle(n), ${result.adminMemberCount} Mitglied(er) mit Administrator.`
|
||||
: 'Nein, sieht unauffällig aus.',
|
||||
inline: false
|
||||
}
|
||||
)
|
||||
.setTimestamp();
|
||||
context.branding.applyFooter(embed, interaction.guild.id);
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
67
src/commands/tickets/ticketconfig.ts
Normal file
67
src/commands/tickets/ticketconfig.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { ChatInputCommandInteraction, EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types';
|
||||
import { settingsStore } from '../../config/state';
|
||||
import { context } from '../../config/context';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ticketconfig')
|
||||
.setDescription('Konfiguriert Rollen und Vorlagen je Ticket-Kategorie.')
|
||||
.addSubcommandGroup((group) =>
|
||||
group
|
||||
.setName('topic')
|
||||
.setDescription('Verwaltet Ticket-Kategorien')
|
||||
.addSubcommand((sub) =>
|
||||
sub
|
||||
.setName('set')
|
||||
.setDescription('Setzt Rolle und/oder Fragen-Vorlage für eine Kategorie.')
|
||||
.addStringOption((opt) => opt.setName('topic').setDescription('Kategorie-Slug (z.B. ban, help, feedback, other)').setRequired(true))
|
||||
.addRoleOption((opt) => opt.setName('role').setDescription('Rolle, die bei dieser Kategorie gepingt wird'))
|
||||
.addStringOption((opt) => opt.setName('questions').setDescription('Fragen für die Vorlage, eine pro Zeile (max. 5)'))
|
||||
)
|
||||
.addSubcommand((sub) => sub.setName('list').setDescription('Zeigt die aktuelle Konfiguration aller Kategorien.'))
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
const sub = interaction.options.getSubcommand();
|
||||
|
||||
if (sub === 'set') {
|
||||
const topic = interaction.options.getString('topic', true).trim().toLowerCase();
|
||||
const role = interaction.options.getRole('role');
|
||||
const questionsRaw = interaction.options.getString('questions');
|
||||
const current = settingsStore.get(interaction.guildId)?.ticketConfig?.topics?.[topic] || {};
|
||||
|
||||
const questions = questionsRaw
|
||||
? questionsRaw.split('\n').map((q) => q.trim()).filter(Boolean).slice(0, 5)
|
||||
: current.questions;
|
||||
|
||||
await settingsStore.set(interaction.guildId, {
|
||||
ticketConfig: { topics: { [topic]: { roleId: role?.id ?? current.roleId, questions } } }
|
||||
});
|
||||
|
||||
await interaction.reply({ content: `Konfiguration für Kategorie \`${topic}\` gespeichert.`, ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === 'list') {
|
||||
const topics = settingsStore.get(interaction.guildId)?.ticketConfig?.topics || {};
|
||||
const entries = Object.entries(topics);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Ticket-Kategorien')
|
||||
.setColor(context.branding.getColor(interaction.guildId))
|
||||
.setDescription(
|
||||
entries.length
|
||||
? entries
|
||||
.map(([topic, cfg]) => `**${topic}** — Rolle: ${cfg.roleId ? `<@&${cfg.roleId}>` : 'Standard'} · Fragen: ${cfg.questions?.length ?? 0}`)
|
||||
.join('\n')
|
||||
: 'Noch keine Kategorien konfiguriert.'
|
||||
);
|
||||
context.branding.applyFooter(embed, interaction.guildId);
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
40
src/commands/utility/panel.ts
Normal file
40
src/commands/utility/panel.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ChannelType, ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types';
|
||||
import { context } from '../../config/context';
|
||||
import { PanelType } from '../../services/infoPanelService';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('panel')
|
||||
.setDescription('Erstellt feste Info-Panels mit Buttons/Dropdowns.')
|
||||
.addSubcommand((sub) =>
|
||||
sub
|
||||
.setName('create')
|
||||
.setDescription('Erstellt ein neues Panel.')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('type')
|
||||
.setDescription('Panel-Typ')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: 'Regeln', value: 'rules' },
|
||||
{ name: 'Support', value: 'support' },
|
||||
{ name: 'Bewerbungen', value: 'bewerbung' },
|
||||
{ name: 'Partner', value: 'partner' },
|
||||
{ name: 'Rollen', value: 'rollen' },
|
||||
{ name: 'Events', value: 'events' },
|
||||
{ name: 'FAQ', value: 'faq' }
|
||||
)
|
||||
)
|
||||
.addChannelOption((opt) => opt.setName('channel').setDescription('Zielkanal').addChannelTypes(ChannelType.GuildText).setRequired(true))
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const type = interaction.options.getString('type', true) as PanelType;
|
||||
const channel = interaction.options.getChannel('channel', true);
|
||||
await context.infoPanels.openModal(interaction, type, channel.id);
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
@@ -32,6 +32,9 @@ import { PartnerService } from '../services/partnerService';
|
||||
import { AltAccountService } from '../services/altAccountService';
|
||||
import { GalleryService } from '../services/galleryService';
|
||||
import { GrowthService } from '../services/growthService';
|
||||
import { PermissionScanService } from '../services/permissionScanService';
|
||||
import { InfoPanelService } from '../services/infoPanelService';
|
||||
import { ImageModerationService } from '../services/imageModerationService';
|
||||
|
||||
const logging = new LoggingService();
|
||||
const moduleService = new BotModuleService();
|
||||
@@ -74,7 +77,10 @@ export const context = {
|
||||
partners: new PartnerService(),
|
||||
altAccounts: new AltAccountService(),
|
||||
gallery: new GalleryService(),
|
||||
growth: new GrowthService()
|
||||
growth: new GrowthService(),
|
||||
permissionScan: new PermissionScanService(),
|
||||
infoPanels: new InfoPanelService(),
|
||||
imageModeration: new ImageModerationService()
|
||||
};
|
||||
|
||||
birthdayService.setBadgeService(badgeService);
|
||||
|
||||
@@ -103,6 +103,13 @@ export interface GuildSettings {
|
||||
artistRoleId?: string;
|
||||
lastWinnerAt?: string;
|
||||
};
|
||||
imageModerationConfig?: {
|
||||
enabled?: boolean;
|
||||
alertOnly?: boolean;
|
||||
};
|
||||
ticketConfig?: {
|
||||
topics?: Record<string, { roleId?: string; questions?: string[] }>;
|
||||
};
|
||||
}
|
||||
|
||||
class SettingsStore {
|
||||
@@ -170,6 +177,8 @@ class SettingsStore {
|
||||
brandingConfig: (row as any).brandingConfig ?? undefined,
|
||||
partnerConfig: (row as any).partnerConfig ?? undefined,
|
||||
galleryConfig: (row as any).galleryConfig ?? undefined,
|
||||
imageModerationConfig: (row as any).imageModerationConfig ?? undefined,
|
||||
ticketConfig: (row as any).ticketConfig ?? undefined,
|
||||
supportRoleId: row.supportRoleId ?? undefined
|
||||
} satisfies GuildSettings;
|
||||
this.cache.set(row.guildId, this.applyModuleDefaults(cfg));
|
||||
@@ -237,6 +246,16 @@ class SettingsStore {
|
||||
if (partial.galleryConfig) {
|
||||
merged.galleryConfig = { ...(merged.galleryConfig ?? {}), ...partial.galleryConfig };
|
||||
}
|
||||
if (partial.imageModerationConfig) {
|
||||
merged.imageModerationConfig = { ...(merged.imageModerationConfig ?? {}), ...partial.imageModerationConfig };
|
||||
}
|
||||
if (partial.ticketConfig) {
|
||||
merged.ticketConfig = {
|
||||
...(merged.ticketConfig ?? {}),
|
||||
...partial.ticketConfig,
|
||||
topics: { ...(merged.ticketConfig?.topics ?? {}), ...(partial.ticketConfig.topics ?? {}) }
|
||||
};
|
||||
}
|
||||
merged.automodConfig = { ...mergedAutomod, supportLoginConfig: merged.supportLoginConfig ?? mergedAutomod['supportLoginConfig'] };
|
||||
merged.statuspageEnabled = mergedAutomod.statuspageEnabled;
|
||||
merged.statuspageConfig = mergedAutomod.statuspageConfig;
|
||||
@@ -272,6 +291,8 @@ class SettingsStore {
|
||||
brandingConfig: merged.brandingConfig ?? Prisma.JsonNull,
|
||||
partnerConfig: merged.partnerConfig ?? Prisma.JsonNull,
|
||||
galleryConfig: merged.galleryConfig ?? Prisma.JsonNull,
|
||||
imageModerationConfig: merged.imageModerationConfig ?? Prisma.JsonNull,
|
||||
ticketConfig: merged.ticketConfig ?? Prisma.JsonNull,
|
||||
supportRoleId: merged.supportRoleId ?? null
|
||||
},
|
||||
create: {
|
||||
@@ -302,6 +323,8 @@ class SettingsStore {
|
||||
brandingConfig: merged.brandingConfig ?? Prisma.JsonNull,
|
||||
partnerConfig: merged.partnerConfig ?? Prisma.JsonNull,
|
||||
galleryConfig: merged.galleryConfig ?? Prisma.JsonNull,
|
||||
imageModerationConfig: merged.imageModerationConfig ?? Prisma.JsonNull,
|
||||
ticketConfig: merged.ticketConfig ?? Prisma.JsonNull,
|
||||
supportRoleId: merged.supportRoleId ?? null
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "GuildSettings" ADD COLUMN "imageModerationConfig" JSONB,
|
||||
ADD COLUMN "ticketConfig" JSONB;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "GuildGrowthEvent" ADD COLUMN "inviterId" TEXT,
|
||||
ADD COLUMN "suspicious" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InfoPanel" (
|
||||
"id" TEXT NOT NULL,
|
||||
"guildId" TEXT NOT NULL,
|
||||
"channelId" TEXT NOT NULL,
|
||||
"messageId" TEXT,
|
||||
"type" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"items" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "InfoPanel_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InfoPanel_guildId_idx" ON "InfoPanel"("guildId");
|
||||
@@ -36,6 +36,8 @@ model GuildSettings {
|
||||
brandingConfig Json?
|
||||
partnerConfig Json?
|
||||
galleryConfig Json?
|
||||
imageModerationConfig Json?
|
||||
ticketConfig Json?
|
||||
supportRoleId String?
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
@@ -442,7 +444,23 @@ model GuildGrowthEvent {
|
||||
type String
|
||||
userId String
|
||||
inviteCode String?
|
||||
inviterId String?
|
||||
suspicious Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([guildId, type, createdAt])
|
||||
}
|
||||
|
||||
model InfoPanel {
|
||||
id String @id @default(cuid())
|
||||
guildId String
|
||||
channelId String
|
||||
messageId String?
|
||||
type String
|
||||
title String
|
||||
description String?
|
||||
items Json?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([guildId])
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ const event: EventHandler = {
|
||||
return;
|
||||
}
|
||||
|
||||
context.altAccounts.check(member).then(({ score, reasons }) => {
|
||||
if (context.altAccounts.isSuspicious(score)) context.logging.logSuspiciousJoin(member, reasons);
|
||||
}).catch(() => undefined);
|
||||
|
||||
context.growth.resolveUsedInvite(member.guild).then((inviteCode) => {
|
||||
context.growth.recordJoin(member.guild.id, member.id, inviteCode);
|
||||
}).catch(() => undefined);
|
||||
Promise.all([context.altAccounts.check(member), context.growth.resolveUsedInvite(member.guild)])
|
||||
.then(([{ score, reasons }, invite]) => {
|
||||
const suspicious = context.altAccounts.isSuspicious(score);
|
||||
if (suspicious) context.logging.logSuspiciousJoin(member, reasons);
|
||||
context.growth.recordJoin(member.guild.id, member.id, invite, suspicious);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
|
||||
const guildConfig = settingsStore.get(member.guild.id);
|
||||
const welcomeCfg = guildConfig?.welcomeConfig || guildConfig?.automodConfig?.welcomeConfig;
|
||||
|
||||
@@ -49,6 +49,10 @@ const event: EventHandler = {
|
||||
await context.gallery.handleComponent(interaction);
|
||||
return;
|
||||
}
|
||||
if (interaction.customId.startsWith('panel:')) {
|
||||
await context.infoPanels.handleComponent(interaction as any);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.isButton()) {
|
||||
@@ -87,6 +91,14 @@ const event: EventHandler = {
|
||||
await context.partners.handleModal(interaction);
|
||||
return;
|
||||
}
|
||||
if (interaction.customId.startsWith('panel:create:')) {
|
||||
await context.infoPanels.handleModal(interaction);
|
||||
return;
|
||||
}
|
||||
if (interaction.customId.startsWith('ticket:template:')) {
|
||||
await context.tickets.handleModal(interaction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,6 +11,22 @@ const event: EventHandler = {
|
||||
if (cfg?.automodEnabled === true) context.automod.checkMessage(message, cfg);
|
||||
if (cfg?.levelingEnabled === true) context.leveling.handleMessage(message);
|
||||
if (cfg?.badgesEnabled !== false) context.badges.trackMessage(message);
|
||||
if (cfg?.imageModerationConfig?.enabled === true && message.guild) {
|
||||
const imageAttachments = message.attachments.filter((a) => a.contentType?.startsWith('image/'));
|
||||
imageAttachments.forEach((attachment) => {
|
||||
context.imageModeration.scanAttachment(attachment.url).then((result) => {
|
||||
if (!result.found) return;
|
||||
// alertOnly = nur bei erkanntem Scam-Verdacht alarmieren, sonst bei jedem gefundenen QR-Code
|
||||
if (cfg.imageModerationConfig?.alertOnly === true && !result.flagged) return;
|
||||
context.logging.logImageAlert(message.guild!, {
|
||||
userTag: message.author.tag,
|
||||
channel: message.channel as any,
|
||||
content: result.content || '',
|
||||
messageUrl: message.url
|
||||
});
|
||||
}).catch(() => undefined);
|
||||
});
|
||||
}
|
||||
// Ticket SLA + KB
|
||||
await context.tickets.trackFirstResponse(message);
|
||||
await context.tickets.suggestKnowledgeBase(message);
|
||||
|
||||
@@ -2,6 +2,11 @@ import { Guild } from 'discord.js';
|
||||
import { prisma } from '../database';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
interface UsedInvite {
|
||||
code: string;
|
||||
inviterId?: string;
|
||||
}
|
||||
|
||||
export class GrowthService {
|
||||
private inviteCache = new Map<string, Map<string, number>>();
|
||||
|
||||
@@ -16,19 +21,19 @@ export class GrowthService {
|
||||
}
|
||||
}
|
||||
|
||||
public async resolveUsedInvite(guild: Guild): Promise<string | null> {
|
||||
public async resolveUsedInvite(guild: Guild): Promise<UsedInvite | null> {
|
||||
const before = this.inviteCache.get(guild.id);
|
||||
try {
|
||||
const invites = await guild.invites.fetch();
|
||||
let usedCode: string | null = null;
|
||||
let used: UsedInvite | null = null;
|
||||
invites.forEach((inv) => {
|
||||
const prevUses = before?.get(inv.code) ?? 0;
|
||||
if ((inv.uses ?? 0) > prevUses) usedCode = inv.code;
|
||||
if ((inv.uses ?? 0) > prevUses) used = { code: inv.code, inviterId: inv.inviter?.id };
|
||||
});
|
||||
if (!usedCode && before) {
|
||||
if (!used && before) {
|
||||
for (const code of before.keys()) {
|
||||
if (!invites.has(code)) {
|
||||
usedCode = code;
|
||||
used = { code };
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -36,15 +41,17 @@ export class GrowthService {
|
||||
const map = new Map<string, number>();
|
||||
invites.forEach((inv) => map.set(inv.code, inv.uses ?? 0));
|
||||
this.inviteCache.set(guild.id, map);
|
||||
return usedCode;
|
||||
return used;
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to resolve used invite for ${guild.id}: ${err}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async recordJoin(guildId: string, userId: string, inviteCode: string | null) {
|
||||
await prisma.guildGrowthEvent.create({ data: { guildId, userId, type: 'join', inviteCode: inviteCode ?? undefined } }).catch(() => undefined);
|
||||
public async recordJoin(guildId: string, userId: string, invite: UsedInvite | null, suspicious = false) {
|
||||
await prisma.guildGrowthEvent
|
||||
.create({ data: { guildId, userId, type: 'join', inviteCode: invite?.code, inviterId: invite?.inviterId, suspicious } })
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
public async recordLeave(guildId: string, userId: string) {
|
||||
@@ -93,4 +100,42 @@ export class GrowthService {
|
||||
.sort((a, b) => a.day.localeCompare(b.day))
|
||||
};
|
||||
}
|
||||
|
||||
public async getInviteBreakdown(guild: Guild) {
|
||||
const since30 = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
||||
const [liveInvites, groups, suspiciousGroups] = await Promise.all([
|
||||
guild.invites.fetch().catch(() => null),
|
||||
prisma.guildGrowthEvent.groupBy({
|
||||
by: ['inviteCode', 'inviterId'],
|
||||
where: { guildId: guild.id, type: 'join', inviteCode: { not: null }, createdAt: { gte: since30 } },
|
||||
_count: { _all: true }
|
||||
}),
|
||||
prisma.guildGrowthEvent.groupBy({
|
||||
by: ['inviteCode'],
|
||||
where: { guildId: guild.id, type: 'join', inviteCode: { not: null }, suspicious: true, createdAt: { gte: since30 } },
|
||||
_count: { _all: true }
|
||||
})
|
||||
]);
|
||||
|
||||
const suspiciousByCode = new Map(suspiciousGroups.map((g) => [g.inviteCode, g._count._all]));
|
||||
const usesByCode = new Map((liveInvites?.map((inv) => [inv.code, inv.uses ?? 0]) as [string, number][]) ?? []);
|
||||
|
||||
return groups
|
||||
.map((g) => ({
|
||||
code: g.inviteCode as string,
|
||||
inviterId: g.inviterId ?? undefined,
|
||||
uses: usesByCode.get(g.inviteCode as string) ?? g._count._all,
|
||||
joins30: g._count._all,
|
||||
suspiciousJoins30: suspiciousByCode.get(g.inviteCode) ?? 0
|
||||
}))
|
||||
.sort((a, b) => b.joins30 - a.joins30);
|
||||
}
|
||||
|
||||
public async getRecentJoins(guildId: string, limit = 20) {
|
||||
return prisma.guildGrowthEvent.findMany({
|
||||
where: { guildId, type: 'join' },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: limit
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
37
src/services/imageModerationService.ts
Normal file
37
src/services/imageModerationService.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Jimp } from 'jimp';
|
||||
import jsQR from 'jsqr';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
const SCAM_PATTERNS = [
|
||||
/discord-?nitro/i,
|
||||
/free-?nitro/i,
|
||||
/steamcommunlty/i,
|
||||
/steamcommunity\.[a-z]{2,}\.[a-z]{2,}/i,
|
||||
/discordapp-?gift/i,
|
||||
/dlscord/i,
|
||||
/dicsord/i
|
||||
];
|
||||
|
||||
export interface ImageScanResult {
|
||||
found: boolean;
|
||||
content?: string;
|
||||
flagged?: boolean;
|
||||
}
|
||||
|
||||
export class ImageModerationService {
|
||||
public async scanAttachment(url: string): Promise<ImageScanResult> {
|
||||
try {
|
||||
const image = await Jimp.read(url);
|
||||
const { data, width, height } = image.bitmap;
|
||||
const clamped = new Uint8ClampedArray(data.buffer, data.byteOffset, data.length);
|
||||
const result = jsQR(clamped, width, height);
|
||||
if (!result) return { found: false };
|
||||
const content = result.data;
|
||||
const flagged = SCAM_PATTERNS.some((p) => p.test(content));
|
||||
return { found: true, content, flagged };
|
||||
} catch (err) {
|
||||
logger.warn(`Image scan failed for ${url}: ${err}`);
|
||||
return { found: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
140
src/services/infoPanelService.ts
Normal file
140
src/services/infoPanelService.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
ChatInputCommandInteraction,
|
||||
EmbedBuilder,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
StringSelectMenuBuilder,
|
||||
StringSelectMenuInteraction,
|
||||
TextInputBuilder,
|
||||
TextInputStyle
|
||||
} from 'discord.js';
|
||||
import { prisma } from '../database';
|
||||
import { context } from '../config/context';
|
||||
|
||||
export type PanelType = 'rules' | 'support' | 'bewerbung' | 'partner' | 'rollen' | 'events' | 'faq';
|
||||
|
||||
interface FaqItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export class InfoPanelService {
|
||||
public async openModal(interaction: ChatInputCommandInteraction, type: PanelType, channelId: string) {
|
||||
const modal = new ModalBuilder().setCustomId(`panel:create:${type}:${channelId}`).setTitle('Panel erstellen');
|
||||
const title = new TextInputBuilder().setCustomId('title').setLabel('Titel').setStyle(TextInputStyle.Short).setRequired(true);
|
||||
const description = new TextInputBuilder().setCustomId('description').setLabel('Beschreibung').setStyle(TextInputStyle.Paragraph).setRequired(type !== 'faq');
|
||||
const rows = [new ActionRowBuilder<TextInputBuilder>().addComponents(title), new ActionRowBuilder<TextInputBuilder>().addComponents(description)];
|
||||
|
||||
if (type === 'faq') {
|
||||
const items = new TextInputBuilder()
|
||||
.setCustomId('items')
|
||||
.setLabel('Fragen (ein "Frage: Antwort" pro Zeile)')
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setPlaceholder('Wie erstelle ich ein Ticket?: Klicke auf den Support-Button.')
|
||||
.setRequired(true);
|
||||
rows.push(new ActionRowBuilder<TextInputBuilder>().addComponents(items));
|
||||
}
|
||||
|
||||
modal.addComponents(...rows);
|
||||
await interaction.showModal(modal);
|
||||
}
|
||||
|
||||
public async handleModal(interaction: ModalSubmitInteraction) {
|
||||
if (!interaction.customId.startsWith('panel:create:') || !interaction.guildId || !interaction.guild) return;
|
||||
const [, , type, channelId] = interaction.customId.split(':') as [string, string, PanelType, string];
|
||||
|
||||
const channel = await interaction.guild.channels.fetch(channelId).catch(() => null);
|
||||
if (!channel || !channel.isTextBased()) {
|
||||
await interaction.reply({ content: 'Zielkanal nicht gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const title = interaction.fields.getTextInputValue('title');
|
||||
const description = interaction.fields.getTextInputValue('description') || undefined;
|
||||
let items: FaqItem[] | undefined;
|
||||
if (type === 'faq') {
|
||||
items = interaction.fields
|
||||
.getTextInputValue('items')
|
||||
.split('\n')
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 25)
|
||||
.map((line) => {
|
||||
const [q, ...rest] = line.split(':');
|
||||
return { question: (q || '').trim(), answer: rest.join(':').trim() || 'Keine Antwort hinterlegt.' };
|
||||
})
|
||||
.filter((i) => i.question);
|
||||
}
|
||||
|
||||
const panel = await prisma.infoPanel.create({
|
||||
data: { guildId: interaction.guildId, channelId, type, title, description, items: (items as any) ?? undefined }
|
||||
});
|
||||
|
||||
const { embed, components } = await this.render(panel.id, type, title, description, items, interaction.guildId);
|
||||
const sent = await (channel as any).send({ embeds: [embed], components });
|
||||
await prisma.infoPanel.update({ where: { id: panel.id }, data: { messageId: sent.id } });
|
||||
|
||||
await interaction.reply({ content: `Panel wurde in ${channel} gepostet.`, ephemeral: true });
|
||||
}
|
||||
|
||||
public async handleComponent(interaction: ButtonInteraction | StringSelectMenuInteraction) {
|
||||
if (!interaction.isStringSelectMenu() || !interaction.customId.startsWith('panel:faq:')) return;
|
||||
const panelId = interaction.customId.split(':')[2];
|
||||
const panel = await prisma.infoPanel.findUnique({ where: { id: panelId } });
|
||||
if (!panel) {
|
||||
await interaction.reply({ content: 'Dieses Panel ist nicht mehr verfügbar.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
const items = (panel.items as unknown as FaqItem[]) || [];
|
||||
const index = parseInt(interaction.values[0], 10);
|
||||
const item = items[index];
|
||||
await interaction.reply({
|
||||
content: item ? `**${item.question}**\n${item.answer}` : 'Diese Frage wurde nicht gefunden.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
private async render(panelId: string, type: PanelType, title: string, description: string | undefined, items: FaqItem[] | undefined, guildId: string) {
|
||||
const embed = new EmbedBuilder().setTitle(title).setColor(context.branding.getColor(guildId));
|
||||
if (description) embed.setDescription(description);
|
||||
context.branding.applyFooter(embed, guildId);
|
||||
|
||||
const components: ActionRowBuilder<any>[] = [];
|
||||
|
||||
if (type === 'faq' && items?.length) {
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId(`panel:faq:${panelId}`)
|
||||
.setPlaceholder('Frage auswählen')
|
||||
.addOptions(items.slice(0, 25).map((item, idx) => ({ label: item.question.slice(0, 100), value: String(idx) })));
|
||||
components.push(new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(select));
|
||||
} else if (type === 'support') {
|
||||
components.push(
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder().setCustomId('ticket:create:other').setLabel('Ticket erstellen').setEmoji('🎫').setStyle(ButtonStyle.Primary)
|
||||
)
|
||||
);
|
||||
} else if (type === 'bewerbung') {
|
||||
const forms = await context.register.listForms(guildId);
|
||||
const active = forms.find((f) => f.isActive);
|
||||
if (active) {
|
||||
components.push(
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder().setCustomId(`register:form:${active.id}`).setLabel('Bewerben').setEmoji('📝').setStyle(ButtonStyle.Primary)
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (type === 'partner') {
|
||||
components.push(
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder().setCustomId('partner:apply').setLabel('Partner werden').setEmoji('🤝').setStyle(ButtonStyle.Primary)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return { embed, components };
|
||||
}
|
||||
}
|
||||
@@ -241,6 +241,27 @@ export class LoggingService {
|
||||
adminSink?.pushGuildLog({ guildId: guild.id, level: 'INFO', message: `Partner: ${message}`, timestamp: Date.now(), category: 'system' });
|
||||
}
|
||||
|
||||
logImageAlert(guild: Guild, options: { userTag: string; channel?: GuildBasedChannel | null; content: string; messageUrl?: string }) {
|
||||
const { channel } = this.resolve(guild);
|
||||
if (!channel) return;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('🖼️ QR-Code in Bild erkannt')
|
||||
.setDescription(`${options.userTag} in ${options.channel ? `<#${options.channel.id}>` : 'einem Kanal'}`)
|
||||
.addFields({ name: 'Inhalt', value: this.safeField(options.content) })
|
||||
.setColor(0xdc2626)
|
||||
.setTimestamp();
|
||||
if (options.messageUrl) embed.addFields({ name: 'Link', value: options.messageUrl });
|
||||
context.branding.applyFooter(embed, guild.id);
|
||||
channel.send({ embeds: [embed] }).catch((err) => logger.error('Failed to log image alert', err));
|
||||
adminSink?.pushGuildLog({
|
||||
guildId: guild.id,
|
||||
level: 'WARN',
|
||||
message: `Bild-Alarm: ${options.userTag} (QR-Code erkannt)`,
|
||||
timestamp: Date.now(),
|
||||
category: 'automodActions'
|
||||
});
|
||||
}
|
||||
|
||||
logRoleUpdate(member: GuildMember, added: string[], removed: string[]) {
|
||||
const guildId = member.guild.id;
|
||||
adminSink?.pushGuildLog({
|
||||
|
||||
102
src/services/permissionScanService.ts
Normal file
102
src/services/permissionScanService.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { ChannelType, Guild, PermissionFlagsBits, Role, TextChannel } from 'discord.js';
|
||||
|
||||
const DANGEROUS_BOT_PERMS: { flag: bigint; label: string }[] = [
|
||||
{ flag: PermissionFlagsBits.Administrator, label: 'Administrator' },
|
||||
{ flag: PermissionFlagsBits.BanMembers, label: 'Bannen' },
|
||||
{ flag: PermissionFlagsBits.KickMembers, label: 'Kicken' },
|
||||
{ flag: PermissionFlagsBits.ManageGuild, label: 'Server verwalten' },
|
||||
{ flag: PermissionFlagsBits.ManageRoles, label: 'Rollen verwalten' },
|
||||
{ flag: PermissionFlagsBits.ManageChannels, label: 'Kanäle verwalten' },
|
||||
{ flag: PermissionFlagsBits.ManageWebhooks, label: 'Webhooks verwalten' }
|
||||
];
|
||||
|
||||
interface NamedEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface PermissionScanResult {
|
||||
adminRoles: NamedEntry[];
|
||||
banRoles: NamedEntry[];
|
||||
dangerousBots: { id: string; tag: string; perms: string[] }[];
|
||||
publicChannels: NamedEntry[];
|
||||
everyoneCanSendChannels: NamedEntry[];
|
||||
emptyRoles: NamedEntry[];
|
||||
duplicateRoleGroups: NamedEntry[][];
|
||||
uselessRoles: NamedEntry[];
|
||||
tooManyAdmins: boolean;
|
||||
adminMemberCount: number;
|
||||
}
|
||||
|
||||
export class PermissionScanService {
|
||||
public async scan(guild: Guild): Promise<PermissionScanResult> {
|
||||
await guild.members.fetch().catch(() => undefined);
|
||||
const everyone = guild.roles.everyone;
|
||||
const roles: Role[] = Array.from(guild.roles.cache.values()).filter((r) => r.id !== guild.id);
|
||||
|
||||
const adminRoles = roles.filter((r) => r.permissions.has(PermissionFlagsBits.Administrator)).map((r) => ({ id: r.id, name: r.name }));
|
||||
const banRoles = roles.filter((r) => r.permissions.has(PermissionFlagsBits.BanMembers)).map((r) => ({ id: r.id, name: r.name }));
|
||||
|
||||
const dangerousBots = Array.from(guild.members.cache.values())
|
||||
.filter((m) => m.user.bot)
|
||||
.map((m) => ({
|
||||
id: m.id,
|
||||
tag: m.user.tag,
|
||||
perms: DANGEROUS_BOT_PERMS.filter((p) => m.permissions.has(p.flag)).map((p) => p.label)
|
||||
}))
|
||||
.filter((b) => b.perms.length > 0);
|
||||
|
||||
const publicChannels: NamedEntry[] = [];
|
||||
const everyoneCanSendChannels: NamedEntry[] = [];
|
||||
guild.channels.cache
|
||||
.filter((c) => c.type === ChannelType.GuildText)
|
||||
.forEach((channel) => {
|
||||
const c = channel as TextChannel;
|
||||
const perms = c.permissionsFor(everyone);
|
||||
if (!perms) return;
|
||||
if (perms.has(PermissionFlagsBits.ViewChannel)) {
|
||||
publicChannels.push({ id: c.id, name: c.name });
|
||||
if (perms.has(PermissionFlagsBits.SendMessages)) everyoneCanSendChannels.push({ id: c.id, name: c.name });
|
||||
}
|
||||
});
|
||||
|
||||
const emptyRoles = roles.filter((r) => !r.managed && r.members.size === 0).map((r) => ({ id: r.id, name: r.name }));
|
||||
|
||||
const byBitfield = new Map<string, Role[]>();
|
||||
roles
|
||||
.filter((r) => !r.managed && r.permissions.bitfield !== 0n)
|
||||
.forEach((r) => {
|
||||
const key = r.permissions.bitfield.toString();
|
||||
const arr = byBitfield.get(key) || [];
|
||||
arr.push(r);
|
||||
byBitfield.set(key, arr);
|
||||
});
|
||||
const duplicateRoleGroups = Array.from(byBitfield.values())
|
||||
.filter((g) => g.length > 1)
|
||||
.map((g) => g.map((r) => ({ id: r.id, name: r.name })));
|
||||
|
||||
const uselessRoles = roles
|
||||
.filter((r) => !r.managed && r.permissions.bitfield === 0n && r.members.size === 0)
|
||||
.map((r) => ({ id: r.id, name: r.name }));
|
||||
|
||||
const adminMemberIds = new Set<string>();
|
||||
guild.members.cache.forEach((m) => {
|
||||
if (m.permissions.has(PermissionFlagsBits.Administrator)) adminMemberIds.add(m.id);
|
||||
});
|
||||
const memberCount = guild.memberCount || guild.members.cache.size;
|
||||
const tooManyAdmins = adminRoles.length > 3 || (memberCount > 0 && adminMemberIds.size / memberCount > 0.1);
|
||||
|
||||
return {
|
||||
adminRoles,
|
||||
banRoles,
|
||||
dangerousBots,
|
||||
publicChannels,
|
||||
everyoneCanSendChannels,
|
||||
emptyRoles,
|
||||
duplicateRoleGroups,
|
||||
uselessRoles,
|
||||
tooManyAdmins,
|
||||
adminMemberCount: adminMemberIds.size
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,12 @@ import {
|
||||
EmbedBuilder,
|
||||
Guild,
|
||||
GuildMember,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
PermissionsBitField,
|
||||
TextChannel,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
Client,
|
||||
Message
|
||||
} from 'discord.js';
|
||||
@@ -88,6 +92,21 @@ export class TicketService {
|
||||
return;
|
||||
}
|
||||
|
||||
const questions = settingsStore.get(interaction.guild.id)?.ticketConfig?.topics?.[topic]?.questions;
|
||||
if (questions?.length) {
|
||||
const modal = new ModalBuilder().setCustomId(`ticket:template:${topic}`).setTitle('Angaben zum Ticket');
|
||||
questions.slice(0, 5).forEach((q, i) => {
|
||||
const input = new TextInputBuilder()
|
||||
.setCustomId(`q${i}`)
|
||||
.setLabel(q.slice(0, 45))
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setRequired(true);
|
||||
modal.addComponents(new ActionRowBuilder<TextInputBuilder>().addComponents(input));
|
||||
});
|
||||
await interaction.showModal(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
const record = await this.openTicket(interaction.guild, interaction.member as GuildMember, topic);
|
||||
await interaction.reply({
|
||||
content: record ? 'Ticket erstellt! Schau im neuen Kanal nach.' : 'Ticket konnte nicht erstellt werden.',
|
||||
@@ -156,6 +175,39 @@ export class TicketService {
|
||||
}
|
||||
}
|
||||
|
||||
public async handleModal(interaction: ModalSubmitInteraction) {
|
||||
if (!interaction.customId.startsWith('ticket:template:') || !interaction.guild) return;
|
||||
const topic = interaction.customId.split(':')[2] || 'allgemein';
|
||||
|
||||
const existing = await prisma.ticket.findFirst({
|
||||
where: { userId: interaction.user.id, guildId: interaction.guild.id, status: { notIn: ['closed', 'erledigt'] } }
|
||||
});
|
||||
if (existing) {
|
||||
await interaction.reply({ content: 'Du hast bereits ein offenes Ticket. Bitte schließe es zuerst.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const questions = settingsStore.get(interaction.guild.id)?.ticketConfig?.topics?.[topic]?.questions || [];
|
||||
const answers = questions.slice(0, 5).map((q, i) => ({ question: q, answer: interaction.fields.getTextInputValue(`q${i}`) }));
|
||||
|
||||
const record = await this.openTicket(interaction.guild, interaction.member as GuildMember, topic);
|
||||
if (record && answers.length) {
|
||||
const channel = await interaction.guild.channels.fetch(record.channelId).catch(() => null);
|
||||
if (channel && channel.isTextBased()) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Angaben zum Ticket')
|
||||
.setColor(context.branding.getColor(interaction.guild.id))
|
||||
.addFields(answers.map((a) => ({ name: a.question.slice(0, 256), value: a.answer.slice(0, 1024) || '-' })));
|
||||
await (channel as any).send({ embeds: [embed] }).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
content: record ? 'Ticket erstellt! Schau im neuen Kanal nach.' : 'Ticket konnte nicht erstellt werden.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
public async claimTicket(interaction: ChatInputCommandInteraction) {
|
||||
const channel = interaction.channel as TextChannel;
|
||||
const ticket = await prisma.ticket.findFirst({ where: { channelId: channel.id } });
|
||||
@@ -247,7 +299,9 @@ export class TicketService {
|
||||
private async openTicket(guild: Guild, member: GuildMember, topic: string): Promise<TicketRecord | null> {
|
||||
if (!this.isEnabled(guild.id)) return null;
|
||||
const category = await this.ensureCategory(guild);
|
||||
const supportRoleId = settingsStore.get(guild.id)?.supportRoleId || env.supportRoleId || null;
|
||||
const guildCfg = settingsStore.get(guild.id);
|
||||
const topicRoleId = guildCfg?.ticketConfig?.topics?.[topic]?.roleId;
|
||||
const supportRoleId = topicRoleId || guildCfg?.supportRoleId || env.supportRoleId || null;
|
||||
const overwrites: any[] = [
|
||||
{
|
||||
id: guild.id,
|
||||
|
||||
@@ -102,6 +102,15 @@ router.get('/growth', requireAuth, async (req, res) => {
|
||||
res.json({ stats });
|
||||
});
|
||||
|
||||
router.get('/growth/invites', requireAuth, async (req, res) => {
|
||||
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
||||
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||
const guild = context.client?.guilds.cache.get(guildId) || (await context.client?.guilds.fetch(guildId).catch(() => null));
|
||||
if (!guild) return res.status(404).json({ error: 'guild not found' });
|
||||
const [breakdown, recentJoins] = await Promise.all([context.growth.getInviteBreakdown(guild), context.growth.getRecentJoins(guildId)]);
|
||||
res.json({ breakdown, recentJoins });
|
||||
});
|
||||
|
||||
router.get('/guild/activity', requireAuth, (req, res) => {
|
||||
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
||||
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||
|
||||
Reference in New Issue
Block a user