Compare commits
3 Commits
aa246cd7ea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ac8d9460d | |||
| 9943254aa4 | |||
| 54376fdad7 |
@@ -22,6 +22,8 @@ import { Watchlist } from './pages/Watchlist';
|
|||||||
import { Admin } from './pages/Admin';
|
import { Admin } from './pages/Admin';
|
||||||
import { Branding } from './pages/Branding';
|
import { Branding } from './pages/Branding';
|
||||||
import { Growth } from './pages/Growth';
|
import { Growth } from './pages/Growth';
|
||||||
|
import { Permissions } from './pages/Permissions';
|
||||||
|
import { Panels } from './pages/Panels';
|
||||||
|
|
||||||
const THEME_COLORS: Record<string, string> = {
|
const THEME_COLORS: Record<string, string> = {
|
||||||
orange: '#f97316',
|
orange: '#f97316',
|
||||||
@@ -68,6 +70,8 @@ function AppContent() {
|
|||||||
case 'watchlist': return <Watchlist />;
|
case 'watchlist': return <Watchlist />;
|
||||||
case 'branding': return <Branding />;
|
case 'branding': return <Branding />;
|
||||||
case 'growth': return <Growth />;
|
case 'growth': return <Growth />;
|
||||||
|
case 'permissions': return <Permissions />;
|
||||||
|
case 'panels': return <Panels />;
|
||||||
case 'admin': return <Admin />;
|
case 'admin': return <Admin />;
|
||||||
default: return <Dashboard />;
|
default: return <Dashboard />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
LogOut, PanelLeftClose, PanelLeft, Activity, AudioLines, CalendarDays,
|
LogOut, PanelLeftClose, PanelLeft, Activity, AudioLines, CalendarDays,
|
||||||
ClipboardList, Eye, Home, LogIn, ListChecks, Music, Palette, Puzzle, RadioTower, Settings,
|
ClipboardList, Eye, Home, LogIn, ListChecks, Music, Palette, Puzzle, RadioTower, Settings,
|
||||||
Shield, Sparkles, Tag, Ticket, TrendingUp, Wrench
|
Shield, ShieldAlert, Sparkles, Tag, Ticket, TrendingUp, LayoutPanelTop, Wrench
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useApp } from '../../context/AppContext';
|
import { useApp } from '../../context/AppContext';
|
||||||
import { AppAvatar } from '../shared/AppAvatar';
|
import { AppAvatar } from '../shared/AppAvatar';
|
||||||
@@ -24,6 +24,8 @@ const navGroups = [
|
|||||||
{ key: 'welcome', label: 'Willkommen', icon: <Sparkles size={18} /> },
|
{ key: 'welcome', label: 'Willkommen', icon: <Sparkles size={18} /> },
|
||||||
{ key: 'birthday', label: 'Birthday', icon: <CalendarDays size={18} /> },
|
{ key: 'birthday', label: 'Birthday', icon: <CalendarDays size={18} /> },
|
||||||
{ key: 'events', label: 'Events', icon: <Activity size={18} /> },
|
{ key: 'events', label: 'Events', icon: <Activity size={18} /> },
|
||||||
|
{ key: 'reactionroles', label: 'Reaction Roles', icon: <Tag size={18} /> },
|
||||||
|
{ key: 'panels', label: 'Info-Panels', icon: <LayoutPanelTop size={18} /> },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -38,7 +40,7 @@ const navGroups = [
|
|||||||
label: 'Moderation',
|
label: 'Moderation',
|
||||||
items: [
|
items: [
|
||||||
{ key: 'automod', label: 'Automod', icon: <Shield size={18} /> },
|
{ key: 'automod', label: 'Automod', icon: <Shield size={18} /> },
|
||||||
{ key: 'reactionroles', label: 'Reaction Roles', icon: <Tag size={18} /> },
|
{ key: 'permissions', label: 'Rechte-Scanner', icon: <ShieldAlert size={18} /> },
|
||||||
{ key: 'tasks', label: 'Team-Aufgaben', icon: <ListChecks size={18} /> },
|
{ key: 'tasks', label: 'Team-Aufgaben', icon: <ListChecks size={18} /> },
|
||||||
{ key: 'watchlist', label: 'Watchlist', icon: <Eye size={18} /> },
|
{ key: 'watchlist', label: 'Watchlist', icon: <Eye size={18} /> },
|
||||||
]
|
]
|
||||||
@@ -48,6 +50,11 @@ const navGroups = [
|
|||||||
items: [
|
items: [
|
||||||
{ key: 'dynamicvoice', label: 'Dynamic Voice', icon: <AudioLines size={18} /> },
|
{ key: 'dynamicvoice', label: 'Dynamic Voice', icon: <AudioLines size={18} /> },
|
||||||
{ key: 'music', label: 'Musik', icon: <Music size={18} /> },
|
{ key: 'music', label: 'Musik', icon: <Music size={18} /> },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Statistiken',
|
||||||
|
items: [
|
||||||
{ key: 'statuspage', label: 'Statuspage', icon: <RadioTower size={18} /> },
|
{ key: 'statuspage', label: 'Statuspage', icon: <RadioTower size={18} /> },
|
||||||
{ key: 'serverstats', label: 'Server Stats', icon: <Activity size={18} /> },
|
{ key: 'serverstats', label: 'Server Stats', icon: <Activity size={18} /> },
|
||||||
{ key: 'growth', label: 'Wachstum', icon: <TrendingUp size={18} /> },
|
{ key: 'growth', label: 'Wachstum', icon: <TrendingUp size={18} /> },
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ type Props = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
footer?: string;
|
footer?: string;
|
||||||
|
image?: string;
|
||||||
accentColor?: string;
|
accentColor?: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const now = () => new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
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 (
|
return (
|
||||||
<div className="rounded-lg bg-[#313338] p-4">
|
<div className="rounded-lg bg-[#313338] p-4">
|
||||||
<div className="flex gap-3">
|
<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>
|
<span className="text-xs text-[#949ba4]">Heute um {now()}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(title || description || footer) && (
|
{(title || description || footer || image) && (
|
||||||
<div
|
<div
|
||||||
className="mt-1 max-w-md rounded border-l-4 bg-[#2b2d31] p-3"
|
className="mt-1 max-w-md rounded border-l-4 bg-[#2b2d31] p-3"
|
||||||
style={{ borderLeftColor: accentColor }}
|
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>
|
<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>}
|
{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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
25
frontend/src/components/shared/ModuleActiveToggle.tsx
Normal file
25
frontend/src/components/shared/ModuleActiveToggle.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { AppSwitch } from './AppSwitch';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
icon: ReactNode;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
isSelected: boolean;
|
||||||
|
onChange: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ModuleActiveToggle({ icon, title, description, isSelected, onChange }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3 rounded-xl border border-accent bg-accent-soft p-3">
|
||||||
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-accent text-accent-foreground">
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="text-sm font-semibold">{title}</div>
|
||||||
|
<div className="text-xs text-muted">{description}</div>
|
||||||
|
</div>
|
||||||
|
<AppSwitch aria-label={title} isSelected={isSelected} onChange={onChange} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import type {
|
|||||||
AppConfig, User, Guild, NavKey, TicketRecord, StatusService,
|
AppConfig, User, Guild, NavKey, TicketRecord, StatusService,
|
||||||
EventItem, ReactionRoleSet, ModuleItem, LogEntry, SettingsState,
|
EventItem, ReactionRoleSet, ModuleItem, LogEntry, SettingsState,
|
||||||
SupportLoginConfig, SupportLoginStatus, RegisterForm, RegisterFormField,
|
SupportLoginConfig, SupportLoginStatus, RegisterForm, RegisterFormField,
|
||||||
RegisterApplication, MusicSession, StaffTask, WatchlistEntry, GrowthStats
|
RegisterApplication, MusicSession, StaffTask, WatchlistEntry, GrowthStats,
|
||||||
|
InviteBreakdownEntry, RecentJoinEntry, PermissionScanResult, InfoPanel, InfoPanelType,
|
||||||
|
TicketTopicConfig
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
const appConfig: AppConfig = (window as any).__PAPO__ || {};
|
const appConfig: AppConfig = (window as any).__PAPO__ || {};
|
||||||
@@ -48,6 +50,14 @@ type AppState = {
|
|||||||
taskDraft: { title: string; description: string };
|
taskDraft: { title: string; description: string };
|
||||||
watchlistEntries: WatchlistEntry[];
|
watchlistEntries: WatchlistEntry[];
|
||||||
growthStats: GrowthStats | null;
|
growthStats: GrowthStats | null;
|
||||||
|
inviteBreakdown: InviteBreakdownEntry[];
|
||||||
|
recentJoins: RecentJoinEntry[];
|
||||||
|
permissionScan: PermissionScanResult | null;
|
||||||
|
permissionScanLoading: boolean;
|
||||||
|
infoPanels: InfoPanel[];
|
||||||
|
panelDraft: { type: InfoPanelType; channelId: string; title: string; description: string; items: string };
|
||||||
|
ticketTopics: Record<string, TicketTopicConfig>;
|
||||||
|
ticketTopicDraft: { topic: string; roleId: string; questions: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
type AppContextType = AppState & {
|
type AppContextType = AppState & {
|
||||||
@@ -131,6 +141,15 @@ type AppContextType = AppState & {
|
|||||||
loadWatchlist: () => Promise<void>;
|
loadWatchlist: () => Promise<void>;
|
||||||
removeFromWatchlist: (userId: string) => Promise<void>;
|
removeFromWatchlist: (userId: string) => Promise<void>;
|
||||||
loadGrowthStats: () => Promise<void>;
|
loadGrowthStats: () => Promise<void>;
|
||||||
|
loadInviteBreakdown: () => Promise<void>;
|
||||||
|
loadPermissionScan: () => Promise<void>;
|
||||||
|
loadInfoPanels: () => Promise<void>;
|
||||||
|
setPanelDraft: (s: any | ((prev: any) => any)) => void;
|
||||||
|
createInfoPanel: () => Promise<void>;
|
||||||
|
deleteInfoPanel: (id: string) => Promise<void>;
|
||||||
|
loadTicketTopics: () => Promise<void>;
|
||||||
|
setTicketTopicDraft: (s: any | ((prev: any) => any)) => void;
|
||||||
|
saveTicketTopic: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppContext = createContext<AppContextType | null>(null);
|
const AppContext = createContext<AppContextType | null>(null);
|
||||||
@@ -165,7 +184,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
const [eventDraft, setEventDraft] = useState({ title: '', description: '', channelId: '', startsAt: '' });
|
const [eventDraft, setEventDraft] = useState({ title: '', description: '', channelId: '', startsAt: '' });
|
||||||
const [statusDraft, setStatusDraft] = useState<any>(null);
|
const [statusDraft, setStatusDraft] = useState<any>(null);
|
||||||
const [statsDraft, setStatsDraft] = 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 [supportLogin, setSupportLogin] = useState<{ config: SupportLoginConfig; status: SupportLoginStatus; supportRoleId?: string } | null>(null);
|
||||||
const [registerForms, setRegisterForms] = useState<RegisterForm[]>([]);
|
const [registerForms, setRegisterForms] = useState<RegisterForm[]>([]);
|
||||||
const [registerApps, setRegisterApps] = useState<RegisterApplication[]>([]);
|
const [registerApps, setRegisterApps] = useState<RegisterApplication[]>([]);
|
||||||
@@ -190,6 +209,14 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
const [taskDraft, setTaskDraft] = useState({ title: '', description: '' });
|
const [taskDraft, setTaskDraft] = useState({ title: '', description: '' });
|
||||||
const [watchlistEntries, setWatchlistEntries] = useState<WatchlistEntry[]>([]);
|
const [watchlistEntries, setWatchlistEntries] = useState<WatchlistEntry[]>([]);
|
||||||
const [growthStats, setGrowthStats] = useState<GrowthStats | null>(null);
|
const [growthStats, setGrowthStats] = useState<GrowthStats | null>(null);
|
||||||
|
const [inviteBreakdown, setInviteBreakdown] = useState<InviteBreakdownEntry[]>([]);
|
||||||
|
const [recentJoins, setRecentJoins] = useState<RecentJoinEntry[]>([]);
|
||||||
|
const [permissionScan, setPermissionScan] = useState<PermissionScanResult | null>(null);
|
||||||
|
const [permissionScanLoading, setPermissionScanLoading] = useState(false);
|
||||||
|
const [infoPanels, setInfoPanels] = useState<InfoPanel[]>([]);
|
||||||
|
const [panelDraft, setPanelDraft] = useState<{ type: InfoPanelType; channelId: string; title: string; description: string; items: string }>({ type: 'rules', channelId: '', title: '', description: '', items: '' });
|
||||||
|
const [ticketTopics, setTicketTopics] = useState<Record<string, TicketTopicConfig>>({});
|
||||||
|
const [ticketTopicDraft, setTicketTopicDraft] = useState({ topic: '', roleId: '', questions: '' });
|
||||||
|
|
||||||
const setSection = useCallback((key: NavKey) => {
|
const setSection = useCallback((key: NavKey) => {
|
||||||
setSectionState(key);
|
setSectionState(key);
|
||||||
@@ -198,7 +225,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hash = window.location.hash.replace('#', '') as NavKey;
|
const hash = window.location.hash.replace('#', '') as NavKey;
|
||||||
const validKeys: NavKey[] = ['overview', 'tickets', 'supportlogin', 'automod', 'welcome', 'dynamicvoice', 'birthday', 'reactionroles', 'statuspage', 'serverstats', 'register', 'music', 'settings', 'modules', 'events', 'admin'];
|
const validKeys: NavKey[] = ['overview', 'tickets', 'supportlogin', 'automod', 'welcome', 'dynamicvoice', 'birthday', 'reactionroles', 'statuspage', 'serverstats', 'register', 'music', 'settings', 'modules', 'events', 'tasks', 'watchlist', 'branding', 'growth', 'permissions', 'panels', 'admin'];
|
||||||
if (validKeys.includes(hash)) setSectionState(hash);
|
if (validKeys.includes(hash)) setSectionState(hash);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -277,7 +304,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
setRegisterApps(registerAppsRes.applications || []);
|
setRegisterApps(registerAppsRes.applications || []);
|
||||||
setTasks(tasksRes.tasks || []);
|
setTasks(tasksRes.tasks || []);
|
||||||
setWatchlistEntries(watchlistRes.entries || []);
|
setWatchlistEntries(watchlistRes.entries || []);
|
||||||
setReactionDraft({ title: '', channelId: '', entries: '' });
|
setReactionDraft({ title: '', channelId: '', entries: [] });
|
||||||
await Promise.all([loadTicketData(guildId), loadAdminData()]);
|
await Promise.all([loadTicketData(guildId), loadAdminData()]);
|
||||||
setStatusMessage('');
|
setStatusMessage('');
|
||||||
} catch { setStatusMessage('Daten konnten nicht geladen werden'); }
|
} catch { setStatusMessage('Daten konnten nicht geladen werden'); }
|
||||||
@@ -356,11 +383,10 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveReactionRole() {
|
async function saveReactionRole() {
|
||||||
const entries = reactionDraft.entries.split('\n').map((l) => l.trim()).filter(Boolean)
|
const entries = (reactionDraft.entries as any[]).filter((e) => e.emoji && e.roleId);
|
||||||
.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);
|
|
||||||
await apiFetch('/reactionroles', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, channelId: reactionDraft.channelId, title: reactionDraft.title, entries }) });
|
await apiFetch('/reactionroles', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, channelId: reactionDraft.channelId, title: reactionDraft.title, entries }) });
|
||||||
await loadGuildData(currentGuildId);
|
await loadGuildData(currentGuildId);
|
||||||
|
setReactionDraft({ title: '', channelId: '', entries: [] });
|
||||||
setStatusMessage('Reaction Role gespeichert');
|
setStatusMessage('Reaction Role gespeichert');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,6 +632,71 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
setGrowthStats(res.stats || null);
|
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 || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPermissionScan() {
|
||||||
|
if (!currentGuildId) return;
|
||||||
|
setPermissionScanLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await apiFetch<any>(`/permissions/scan?guildId=${encodeURIComponent(currentGuildId)}`);
|
||||||
|
setPermissionScan(res.result || null);
|
||||||
|
} finally {
|
||||||
|
setPermissionScanLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadInfoPanels() {
|
||||||
|
if (!currentGuildId) return;
|
||||||
|
const res = await apiFetch<any>(`/panels?guildId=${encodeURIComponent(currentGuildId)}`);
|
||||||
|
setInfoPanels(res.panels || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createInfoPanel() {
|
||||||
|
if (!currentGuildId || !panelDraft.channelId || !panelDraft.title) return;
|
||||||
|
const items = panelDraft.type === 'faq'
|
||||||
|
? panelDraft.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)
|
||||||
|
: undefined;
|
||||||
|
await apiFetch('/panels', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ guildId: currentGuildId, channelId: panelDraft.channelId, type: panelDraft.type, title: panelDraft.title, description: panelDraft.description || undefined, items })
|
||||||
|
});
|
||||||
|
setPanelDraft({ type: 'rules', channelId: '', title: '', description: '', items: '' });
|
||||||
|
setStatusMessage('Panel wurde gepostet');
|
||||||
|
await loadInfoPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteInfoPanel(id: string) {
|
||||||
|
await apiFetch(`/panels/${id}?guildId=${encodeURIComponent(currentGuildId)}`, { method: 'DELETE' });
|
||||||
|
setStatusMessage('Panel gelöscht');
|
||||||
|
await loadInfoPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTicketTopics() {
|
||||||
|
if (!currentGuildId) return;
|
||||||
|
const res = await apiFetch<any>(`/ticketconfig?guildId=${encodeURIComponent(currentGuildId)}`);
|
||||||
|
setTicketTopics(res.topics || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTicketTopic() {
|
||||||
|
if (!currentGuildId || !ticketTopicDraft.topic.trim()) return;
|
||||||
|
const questions = ticketTopicDraft.questions.split('\n').map((q) => q.trim()).filter(Boolean).slice(0, 5);
|
||||||
|
await apiFetch('/ticketconfig', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ guildId: currentGuildId, topic: ticketTopicDraft.topic.trim().toLowerCase(), roleId: ticketTopicDraft.roleId || undefined, questions })
|
||||||
|
});
|
||||||
|
setTicketTopicDraft({ topic: '', roleId: '', questions: '' });
|
||||||
|
setStatusMessage('Ticket-Kategorie gespeichert');
|
||||||
|
await loadTicketTopics();
|
||||||
|
}
|
||||||
|
|
||||||
const handleLogout = useCallback(() => {
|
const handleLogout = useCallback(() => {
|
||||||
window.location.href = `${appConfig.baseAuth || '/auth'}/logout`;
|
window.location.href = `${appConfig.baseAuth || '/auth'}/logout`;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -621,6 +712,8 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
ticketDetail, ticketMessages, kbEditDraft, automationEditDraft,
|
ticketDetail, ticketMessages, kbEditDraft, automationEditDraft,
|
||||||
automodStrikes, registerStatusFilter, registerFormFilter, selectedAppId,
|
automodStrikes, registerStatusFilter, registerFormFilter, selectedAppId,
|
||||||
appNotes, appHistory, noteDraft, tasks, taskDraft, watchlistEntries, growthStats,
|
appNotes, appHistory, noteDraft, tasks, taskDraft, watchlistEntries, growthStats,
|
||||||
|
inviteBreakdown, recentJoins, permissionScan, permissionScanLoading, infoPanels,
|
||||||
|
panelDraft, ticketTopics, ticketTopicDraft,
|
||||||
setCurrentGuildId, setSection, setSettings, setBirthday, setSupportLogin,
|
setCurrentGuildId, setSection, setSettings, setBirthday, setSupportLogin,
|
||||||
setStatusDraft, setStatsDraft, setStatusMessage, loadGuildData,
|
setStatusDraft, setStatsDraft, setStatusMessage, loadGuildData,
|
||||||
saveSettingsPayload, saveBirthday, saveStatuspage, saveServerStats,
|
saveSettingsPayload, saveBirthday, saveStatuspage, saveServerStats,
|
||||||
@@ -636,7 +729,9 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
|||||||
loadAutomodStrikes, resetAutomodStrike, setRegisterStatusFilter, setRegisterFormFilter,
|
loadAutomodStrikes, resetAutomodStrike, setRegisterStatusFilter, setRegisterFormFilter,
|
||||||
loadRegisterApps, openAppDetail, setNoteDraft, addAppNote,
|
loadRegisterApps, openAppDetail, setNoteDraft, addAppNote,
|
||||||
setTaskDraft, createTask, updateTaskStatus, deleteTask,
|
setTaskDraft, createTask, updateTaskStatus, deleteTask,
|
||||||
loadWatchlist, removeFromWatchlist, loadGrowthStats,
|
loadWatchlist, removeFromWatchlist, loadGrowthStats, loadInviteBreakdown,
|
||||||
|
loadPermissionScan, loadInfoPanels, setPanelDraft, createInfoPanel, deleteInfoPanel,
|
||||||
|
loadTicketTopics, setTicketTopicDraft, saveTicketTopic,
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { SectionCard } from '../components/shared/SectionCard';
|
|||||||
import { ChannelSelect, type ChannelOption } from '../components/shared/ChannelSelect';
|
import { ChannelSelect, type ChannelOption } from '../components/shared/ChannelSelect';
|
||||||
import { RoleSelect, type RoleOption } from '../components/shared/RoleSelect';
|
import { RoleSelect, type RoleOption } from '../components/shared/RoleSelect';
|
||||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
import { AppSwitch } from '../components/shared/AppSwitch';
|
||||||
|
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||||
import { useGuildResources } from '../hooks/useGuildResources';
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
const FILTERS = [
|
const FILTERS = [
|
||||||
@@ -206,20 +207,13 @@ export function Automod() {
|
|||||||
<h3 className="text-base font-semibold">Automod</h3>
|
<h3 className="text-base font-semibold">Automod</h3>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 p-5">
|
<CardContent className="flex flex-col gap-4 p-5">
|
||||||
<div className="flex items-center gap-3 rounded-xl border border-accent bg-accent-soft p-3">
|
<ModuleActiveToggle
|
||||||
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-accent text-accent-foreground">
|
icon={<Shield size={16} />}
|
||||||
<Shield size={16} />
|
title="Automod aktiv"
|
||||||
</div>
|
description="Schaltet alle Filter unten gesammelt ein oder aus."
|
||||||
<div className="min-w-0 flex-1">
|
isSelected={settings.automodEnabled !== false}
|
||||||
<div className="text-sm font-semibold">Automod aktiv</div>
|
onChange={(v) => setSettings((s) => ({ ...s, automodEnabled: v }))}
|
||||||
<div className="text-xs text-muted">Schaltet alle Filter unten gesammelt ein oder aus.</div>
|
/>
|
||||||
</div>
|
|
||||||
<AppSwitch
|
|
||||||
aria-label="Automod aktiv"
|
|
||||||
isSelected={settings.automodEnabled !== false}
|
|
||||||
onChange={(v) => setSettings((s) => ({ ...s, automodEnabled: v }))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{FILTERS.map((f) => {
|
{FILTERS.map((f) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CalendarDays, Save, Cake, Clock } from 'lucide-react';
|
|||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||||
import { useGuildResources } from '../hooks/useGuildResources';
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
export function Birthday() {
|
export function Birthday() {
|
||||||
@@ -18,10 +18,12 @@ export function Birthday() {
|
|||||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 p-5">
|
<CardContent className="flex flex-col gap-4 p-5">
|
||||||
<AppSwitch
|
<ModuleActiveToggle
|
||||||
|
icon={<Cake size={16} />}
|
||||||
|
title="Birthday aktiv"
|
||||||
|
description="Speichert Geburtstage und sendet automatisch Glückwünsche."
|
||||||
isSelected={birthday.config?.enabled !== false}
|
isSelected={birthday.config?.enabled !== false}
|
||||||
onChange={(v) => setBirthday((s) => ({ ...s, config: { ...s.config, enabled: v } }))}
|
onChange={(v) => setBirthday((s) => ({ ...s, config: { ...s.config, enabled: v } }))}
|
||||||
label={<div className="flex items-center gap-2"><Cake size={16} /> Birthday aktiv</div>}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField>
|
<TextField>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AudioLines, Save, Mic, Users } from 'lucide-react';
|
|||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||||
import { useGuildResources } from '../hooks/useGuildResources';
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
export function DynamicVoice() {
|
export function DynamicVoice() {
|
||||||
@@ -18,10 +18,12 @@ export function DynamicVoice() {
|
|||||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 p-5">
|
<CardContent className="flex flex-col gap-4 p-5">
|
||||||
<AppSwitch
|
<ModuleActiveToggle
|
||||||
|
icon={<AudioLines size={16} />}
|
||||||
|
title="Dynamic Voice aktiv"
|
||||||
|
description="Erstellt private Voice-Channels aus der konfigurierten Lobby."
|
||||||
isSelected={settings.dynamicVoiceEnabled !== false}
|
isSelected={settings.dynamicVoiceEnabled !== false}
|
||||||
onChange={(v) => setSettings((s) => ({ ...s, dynamicVoiceEnabled: v }))}
|
onChange={(v) => setSettings((s) => ({ ...s, dynamicVoiceEnabled: v }))}
|
||||||
label={<div className="flex items-center gap-2"><AudioLines size={16} /> Dynamic Voice aktiv</div>}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField>
|
<TextField>
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Card, CardContent, CardHeader } from '@heroui/react';
|
import { Card, CardContent, CardHeader, Chip } from '@heroui/react';
|
||||||
import { TrendingUp, TrendingDown, Gem, Link2, Handshake } from 'lucide-react';
|
import { TrendingUp, TrendingDown, Gem, Link2, Handshake, Users } from 'lucide-react';
|
||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { StatCard } from '../components/shared/StatCard';
|
import { StatCard } from '../components/shared/StatCard';
|
||||||
import { BarComparisonChart } from '../components/shared/BarComparisonChart';
|
import { BarComparisonChart } from '../components/shared/BarComparisonChart';
|
||||||
|
import { formatDate } from '../utils/formatters';
|
||||||
|
|
||||||
export function Growth() {
|
export function Growth() {
|
||||||
const { currentGuildId, growthStats, loadGrowthStats } = useApp();
|
const { currentGuildId, growthStats, loadGrowthStats, inviteBreakdown, recentJoins, loadInviteBreakdown } = useApp();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentGuildId) loadGrowthStats();
|
if (currentGuildId) {
|
||||||
|
loadGrowthStats();
|
||||||
|
loadInviteBreakdown();
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentGuildId]);
|
}, [currentGuildId]);
|
||||||
|
|
||||||
@@ -71,6 +75,64 @@ export function Growth() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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>
|
</SectionCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
128
frontend/src/pages/Panels.tsx
Normal file
128
frontend/src/pages/Panels.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { Card, CardContent, CardHeader, Chip, Button, Input, TextArea, Separator, TextField, Label } from '@heroui/react';
|
||||||
|
import { LayoutPanelTop, Trash2, Send, ScrollText, LifeBuoy, FileSignature, Handshake, Tags, CalendarDays, CircleHelp } from 'lucide-react';
|
||||||
|
import { useApp } from '../context/AppContext';
|
||||||
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
|
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||||
|
import { EmptyState } from '../components/shared/EmptyState';
|
||||||
|
import type { InfoPanelType } from '../types';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const TYPE_META: Record<InfoPanelType, { label: string; icon: React.ReactNode }> = {
|
||||||
|
rules: { label: 'Regeln', icon: <ScrollText size={14} /> },
|
||||||
|
support: { label: 'Support', icon: <LifeBuoy size={14} /> },
|
||||||
|
bewerbung: { label: 'Bewerbungen', icon: <FileSignature size={14} /> },
|
||||||
|
partner: { label: 'Partner', icon: <Handshake size={14} /> },
|
||||||
|
rollen: { label: 'Rollen', icon: <Tags size={14} /> },
|
||||||
|
events: { label: 'Events', icon: <CalendarDays size={14} /> },
|
||||||
|
faq: { label: 'FAQ', icon: <CircleHelp size={14} /> },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Panels() {
|
||||||
|
const { currentGuildId, infoPanels, loadInfoPanels, panelDraft, setPanelDraft, createInfoPanel, deleteInfoPanel } = useApp();
|
||||||
|
const { channels } = useGuildResources(currentGuildId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentGuildId) loadInfoPanels();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentGuildId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionCard title="Info-Panels" subtitle="Feste Panels für Regeln, Support, Bewerbungen, Partner, Rollen, Events und FAQ mit passenden Buttons.">
|
||||||
|
<div className="grid gap-5 xl:grid-cols-[1fr_420px]">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 text-base font-semibold">Bestehende Panels ({infoPanels.length})</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{infoPanels.length ? infoPanels.map((p) => (
|
||||||
|
<Card key={p.id}>
|
||||||
|
<CardContent className="flex items-center justify-between gap-3 p-4">
|
||||||
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
|
<div className="bg-accent-soft text-accent-soft-foreground flex size-9 shrink-0 items-center justify-center rounded-lg">
|
||||||
|
{TYPE_META[p.type]?.icon || <LayoutPanelTop size={14} />}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="truncate text-sm font-semibold">{p.title}</div>
|
||||||
|
<div className="text-xs text-muted">#{channels.find((c) => c.id === p.channelId)?.name || p.channelId}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
|
<Chip size="sm" variant="soft">{TYPE_META[p.type]?.label || p.type}</Chip>
|
||||||
|
<Button size="sm" variant="danger-soft" onPress={() => deleteInfoPanel(p.id)}>
|
||||||
|
<Trash2 size={14} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)) : <EmptyState message="Noch keine Panels erstellt" icon={<LayoutPanelTop size={24} />} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0">
|
||||||
|
<h3 className="text-base font-semibold">Neues Panel erstellen</h3>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4 p-5">
|
||||||
|
<TextField>
|
||||||
|
<Label>Typ</Label>
|
||||||
|
<select
|
||||||
|
aria-label="Panel-Typ"
|
||||||
|
className="w-full rounded-xl text-sm"
|
||||||
|
value={panelDraft.type}
|
||||||
|
onChange={(e) => setPanelDraft((s: any) => ({ ...s, type: e.target.value as InfoPanelType }))}
|
||||||
|
>
|
||||||
|
{Object.entries(TYPE_META).map(([key, meta]) => (
|
||||||
|
<option key={key} value={key}>{meta.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField>
|
||||||
|
<Label>Ziel-Kanal</Label>
|
||||||
|
<ChannelSelect options={channels} value={panelDraft.channelId} onChange={(id) => setPanelDraft((s: any) => ({ ...s, channelId: id }))} />
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField>
|
||||||
|
<Label>Titel</Label>
|
||||||
|
<Input value={panelDraft.title} onChange={(e) => setPanelDraft((s: any) => ({ ...s, title: e.target.value }))} />
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
{panelDraft.type !== 'faq' && (
|
||||||
|
<TextField>
|
||||||
|
<Label>Beschreibung</Label>
|
||||||
|
<TextArea rows={4} value={panelDraft.description} onChange={(e) => setPanelDraft((s: any) => ({ ...s, description: e.target.value }))} />
|
||||||
|
</TextField>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{panelDraft.type === 'faq' && (
|
||||||
|
<TextField>
|
||||||
|
<Label>Fragen (eine „Frage: Antwort“ pro Zeile)</Label>
|
||||||
|
<TextArea
|
||||||
|
rows={5}
|
||||||
|
placeholder="Wie erstelle ich ein Ticket?: Klicke auf den Support-Button."
|
||||||
|
value={panelDraft.items}
|
||||||
|
onChange={(e) => setPanelDraft((s: any) => ({ ...s, items: e.target.value }))}
|
||||||
|
/>
|
||||||
|
</TextField>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<p className="text-xs text-muted">
|
||||||
|
{panelDraft.type === 'support' && 'Erhält automatisch einen Ticket-Button.'}
|
||||||
|
{panelDraft.type === 'bewerbung' && 'Erhält automatisch einen Bewerben-Button, falls ein aktives Formular existiert.'}
|
||||||
|
{panelDraft.type === 'partner' && 'Erhält automatisch einen Partner-werden-Button.'}
|
||||||
|
{panelDraft.type === 'faq' && 'Erhält ein Dropdown mit den hinterlegten Fragen.'}
|
||||||
|
{['rules', 'rollen', 'events'].includes(panelDraft.type) && 'Reines Info-Embed ohne Button.'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button variant="primary" onPress={createInfoPanel} isDisabled={!panelDraft.channelId || !panelDraft.title}>
|
||||||
|
<Send size={16} /> Panel posten
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SectionCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
125
frontend/src/pages/Permissions.tsx
Normal file
125
frontend/src/pages/Permissions.tsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Card, CardContent, CardHeader, Chip, Button } from '@heroui/react';
|
||||||
|
import { ShieldAlert, ShieldX, Bot, Globe, MessageSquare, Users, Copy, Ban, RefreshCw, TriangleAlert } from 'lucide-react';
|
||||||
|
import { useApp } from '../context/AppContext';
|
||||||
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
|
import { StatCard } from '../components/shared/StatCard';
|
||||||
|
import { EmptyState } from '../components/shared/EmptyState';
|
||||||
|
|
||||||
|
function EntryList({ entries, emptyMessage, icon }: { entries: { id: string; name: string }[]; emptyMessage: string; icon: React.ReactNode }) {
|
||||||
|
if (!entries.length) return <p className="py-3 text-center text-xs text-muted">{emptyMessage}</p>;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
{entries.slice(0, 10).map((e) => (
|
||||||
|
<div key={e.id} className="bg-surface-tertiary flex items-center gap-2 rounded-lg px-3 py-2 text-sm">
|
||||||
|
{icon}
|
||||||
|
<span className="truncate">{e.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{entries.length > 10 && <p className="pl-1 text-xs text-muted">+{entries.length - 10} weitere</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Permissions() {
|
||||||
|
const { currentGuildId, permissionScan, permissionScanLoading, loadPermissionScan } = useApp();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentGuildId) loadPermissionScan();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentGuildId]);
|
||||||
|
|
||||||
|
const r = permissionScan;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionCard
|
||||||
|
title="Rechte-Scanner"
|
||||||
|
subtitle="Sicherheitsanalyse der Server-Rollen, Rechte und Kanäle."
|
||||||
|
action={
|
||||||
|
<Button variant="primary" onPress={() => loadPermissionScan()} isDisabled={permissionScanLoading}>
|
||||||
|
<RefreshCw size={16} className={permissionScanLoading ? 'animate-spin' : ''} /> Scan starten
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!r && !permissionScanLoading && (
|
||||||
|
<EmptyState message="Noch kein Scan durchgeführt. Klicke auf „Scan starten“." icon={<ShieldAlert size={24} />} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{r && (
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
{r.tooManyAdmins && (
|
||||||
|
<Card className="border-danger bg-danger-soft">
|
||||||
|
<CardContent className="flex items-center gap-3 p-4 text-sm">
|
||||||
|
<TriangleAlert size={18} className="shrink-0 text-danger" />
|
||||||
|
<span>Auffällig viele Administrator-Rechte vergeben ({r.adminRoles.length} Rollen, {r.adminMemberCount} Mitglieder mit Adminrechten). Prüfe, ob das notwendig ist.</span>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||||
|
<StatCard icon={<ShieldAlert size={18} />} label="Admin-Rollen" value={r.adminRoles.length} color={r.tooManyAdmins ? 'danger' : 'default'} />
|
||||||
|
<StatCard icon={<Ban size={18} />} label="Ban-Rollen" value={r.banRoles.length} />
|
||||||
|
<StatCard icon={<Bot size={18} />} label="Bots mit gefährlichen Rechten" value={r.dangerousBots.length} color={r.dangerousBots.length ? 'warning' : 'default'} />
|
||||||
|
<StatCard icon={<Globe size={18} />} label="Öffentliche Kanäle" value={r.publicChannels.length} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-5 xl:grid-cols-2">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0"><h3 className="text-base font-semibold">Admin-Rollen</h3></CardHeader>
|
||||||
|
<CardContent className="p-5"><EntryList entries={r.adminRoles} emptyMessage="Keine Admin-Rollen" icon={<ShieldAlert size={14} className="text-danger shrink-0" />} /></CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0"><h3 className="text-base font-semibold">Rollen, die bannen können</h3></CardHeader>
|
||||||
|
<CardContent className="p-5"><EntryList entries={r.banRoles} emptyMessage="Keine Ban-Rollen" icon={<Ban size={14} className="text-warning shrink-0" />} /></CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0"><h3 className="text-base font-semibold">Bots mit gefährlichen Rechten</h3></CardHeader>
|
||||||
|
<CardContent className="p-5">
|
||||||
|
{r.dangerousBots.length ? (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
{r.dangerousBots.slice(0, 10).map((b) => (
|
||||||
|
<div key={b.id} className="bg-surface-tertiary flex flex-col gap-1 rounded-lg px-3 py-2 text-sm">
|
||||||
|
<div className="flex items-center gap-2"><Bot size={14} className="text-muted shrink-0" /><span className="truncate font-medium">{b.tag}</span></div>
|
||||||
|
<div className="flex flex-wrap gap-1 pl-5">
|
||||||
|
{b.perms.map((p) => <Chip key={p} size="sm" variant="soft" color="warning">{p}</Chip>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : <p className="py-3 text-center text-xs text-muted">Keine Bots mit gefährlichen Rechten</p>}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0"><h3 className="text-base font-semibold">@everyone kann schreiben</h3></CardHeader>
|
||||||
|
<CardContent className="p-5"><EntryList entries={r.everyoneCanSendChannels} emptyMessage="Kein Kanal betroffen" icon={<MessageSquare size={14} className="text-muted shrink-0" />} /></CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0"><h3 className="text-base font-semibold">Leere Rollen</h3></CardHeader>
|
||||||
|
<CardContent className="p-5"><EntryList entries={r.emptyRoles} emptyMessage="Keine leeren Rollen" icon={<Users size={14} className="text-muted shrink-0" />} /></CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0"><h3 className="text-base font-semibold">Nutzlose Rollen</h3></CardHeader>
|
||||||
|
<CardContent className="p-5"><EntryList entries={r.uselessRoles} emptyMessage="Keine nutzlosen Rollen" icon={<ShieldX size={14} className="text-muted shrink-0" />} /></CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="xl:col-span-2">
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0"><h3 className="text-base font-semibold">Doppelte Rollen (identische Rechte)</h3></CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-2 p-5">
|
||||||
|
{r.duplicateRoleGroups.length ? r.duplicateRoleGroups.map((group, i) => (
|
||||||
|
<div key={i} className="bg-surface-tertiary flex flex-wrap items-center gap-2 rounded-lg px-3 py-2 text-sm">
|
||||||
|
<Copy size={14} className="text-muted shrink-0" />
|
||||||
|
{group.map((role) => <Chip key={role.id} size="sm" variant="soft">{role.name}</Chip>)}
|
||||||
|
</div>
|
||||||
|
)) : <p className="py-3 text-center text-xs text-muted">Keine doppelten Rollen gefunden</p>}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SectionCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +1,29 @@
|
|||||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Separator, TextField, Label } from '@heroui/react';
|
import { useState } from 'react';
|
||||||
import { Tag, Save, Hash, List } from 'lucide-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 { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||||
|
import { RoleSelect } from '../components/shared/RoleSelect';
|
||||||
import { useGuildResources } from '../hooks/useGuildResources';
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
export function ReactionRoles() {
|
export function ReactionRoles() {
|
||||||
const { reactionRoles, reactionDraft, setReactionDraft, saveReactionRole, currentGuildId } = useApp();
|
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 (
|
return (
|
||||||
<SectionCard title="Reaction Roles" subtitle="Sets anzeigen und neue Zuordnungen anlegen">
|
<SectionCard title="Reaction Roles" subtitle="Sets anzeigen und neue Zuordnungen anlegen">
|
||||||
@@ -61,20 +77,56 @@ export function ReactionRoles() {
|
|||||||
/>
|
/>
|
||||||
</TextField>
|
</TextField>
|
||||||
|
|
||||||
<TextField>
|
<div>
|
||||||
<Label>Einträge</Label>
|
<Label>Einträge</Label>
|
||||||
<TextArea
|
<div className="mt-2 flex flex-col gap-2">
|
||||||
placeholder="Emoji | Role ID | Label :emoji: | 123456789 | Rolle 1 :wave: | 987654321 | Rolle 2"
|
{reactionDraft.entries.map((entry, i) => {
|
||||||
rows={6}
|
const role = roles.find((r) => r.id === entry.roleId);
|
||||||
value={reactionDraft.entries}
|
return (
|
||||||
onChange={(e) => setReactionDraft((s) => ({ ...s, entries: e.target.value }))}
|
<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>
|
||||||
<p className="mt-1 text-xs text-muted">
|
{role?.color && <span className="size-2.5 shrink-0 rounded-full" style={{ backgroundColor: role.color }} />}
|
||||||
Pro Zeile: Emoji | Role ID | Label (optional) | Beschreibung (optional)
|
<span className="min-w-0 flex-1 truncate">{entry.label || role?.name || entry.roleId}</span>
|
||||||
</p>
|
<button
|
||||||
</TextField>
|
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
|
<Save size={16} /> Reaction Role speichern
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Card, CardContent, CardHeader, Input, Button, Chip, Separator, TextFiel
|
|||||||
import { Activity, Save, Trash2, Plus, BarChart3 } from 'lucide-react';
|
import { Activity, Save, Trash2, Plus, BarChart3 } from 'lucide-react';
|
||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||||
|
|
||||||
export function ServerStats() {
|
export function ServerStats() {
|
||||||
const { statsDraft, setStatsDraft, saveServerStats, statsItemDraft, setStatsItemDraft, addStatsItem, deleteStatsItem } = useApp();
|
const { statsDraft, setStatsDraft, saveServerStats, statsItemDraft, setStatsItemDraft, addStatsItem, deleteStatsItem } = useApp();
|
||||||
@@ -17,10 +17,12 @@ export function ServerStats() {
|
|||||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 p-5">
|
<CardContent className="flex flex-col gap-4 p-5">
|
||||||
<AppSwitch
|
<ModuleActiveToggle
|
||||||
|
icon={<BarChart3 size={16} />}
|
||||||
|
title="Server Stats aktiv"
|
||||||
|
description="Zeigt Mitglieder-/Channel-Zahlen als Voice-Statistiken an."
|
||||||
isSelected={statsDraft?.enabled === true}
|
isSelected={statsDraft?.enabled === true}
|
||||||
onChange={(v) => setStatsDraft((s) => ({ ...(s || {}), enabled: v }))}
|
onChange={(v) => setStatsDraft((s) => ({ ...(s || {}), enabled: v }))}
|
||||||
label={<div className="flex items-center gap-2"><BarChart3 size={16} /> Server Stats aktiv</div>}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField>
|
<TextField>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Card, CardContent, CardHeader, Button, Separator, TextField, Label } from '@heroui/react';
|
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 { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||||
import { RoleSelect } from '../components/shared/RoleSelect';
|
import { RoleSelect } from '../components/shared/RoleSelect';
|
||||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
import { AppSwitch } from '../components/shared/AppSwitch';
|
||||||
|
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||||
import { useGuildResources } from '../hooks/useGuildResources';
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
export function SettingsPage() {
|
export function SettingsPage() {
|
||||||
@@ -99,6 +100,34 @@ export function SettingsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useApp } from '../context/AppContext';
|
|||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import type { StatusService } from '../types';
|
import type { StatusService } from '../types';
|
||||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||||
import { useGuildResources } from '../hooks/useGuildResources';
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
export function Statuspage() {
|
export function Statuspage() {
|
||||||
@@ -21,10 +21,12 @@ export function Statuspage() {
|
|||||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 p-5">
|
<CardContent className="flex flex-col gap-4 p-5">
|
||||||
<AppSwitch
|
<ModuleActiveToggle
|
||||||
|
icon={<RadioTower size={16} />}
|
||||||
|
title="Statuspage aktiv"
|
||||||
|
description="Postet und aktualisiert die Statusseite mit deinen Services."
|
||||||
isSelected={statusDraft?.enabled !== false}
|
isSelected={statusDraft?.enabled !== false}
|
||||||
onChange={(v) => setStatusDraft((s) => ({ ...(s || {}), enabled: v }))}
|
onChange={(v) => setStatusDraft((s) => ({ ...(s || {}), enabled: v }))}
|
||||||
label={<div className="flex items-center gap-2"><RadioTower size={16} /> Statuspage aktiv</div>}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField>
|
<TextField>
|
||||||
|
|||||||
@@ -1,22 +1,31 @@
|
|||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { Card, CardContent, CardHeader, Chip, Button, Tabs, Tab, Input, TextArea, Separator, TextField, Label } from '@heroui/react';
|
import { Card, CardContent, CardHeader, Chip, Button, Tabs, Tab, Input, TextArea, Separator, TextField, Label } from '@heroui/react';
|
||||||
import { Ticket, Clock, UserRound, CheckCircle, MessageSquare, FileText, Pencil, Trash2, ChevronRight } from 'lucide-react';
|
import { Ticket, Clock, UserRound, CheckCircle, MessageSquare, FileText, Pencil, Trash2, ChevronRight, Tag, Users } from 'lucide-react';
|
||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { formatDate } from '../utils/formatters';
|
import { formatDate } from '../utils/formatters';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { StatCard } from '../components/shared/StatCard';
|
import { StatCard } from '../components/shared/StatCard';
|
||||||
import { EmptyState } from '../components/shared/EmptyState';
|
import { EmptyState } from '../components/shared/EmptyState';
|
||||||
import { TicketKanban } from '../components/shared/TicketKanban';
|
import { TicketKanban } from '../components/shared/TicketKanban';
|
||||||
|
import { RoleSelect } from '../components/shared/RoleSelect';
|
||||||
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
export function Tickets() {
|
export function Tickets() {
|
||||||
const {
|
const {
|
||||||
tickets, pipeline, sla, automations, kbArticles, ticketTab, setTicketTab,
|
currentGuildId, tickets, pipeline, sla, automations, kbArticles, ticketTab, setTicketTab,
|
||||||
ticketDetail, setTicketDetail, ticketMessages, loadTicketMessages,
|
ticketDetail, setTicketDetail, ticketMessages, loadTicketMessages,
|
||||||
updateTicketStatus, closeTicket, automationDraft, setAutomationDraft,
|
updateTicketStatus, closeTicket, automationDraft, setAutomationDraft,
|
||||||
saveAutomation, kbDraft, setKbDraft, saveKbArticle, kbEditDraft, setKbEditDraft,
|
saveAutomation, kbDraft, setKbDraft, saveKbArticle, kbEditDraft, setKbEditDraft,
|
||||||
updateKbArticle, deleteKbArticle, automationEditDraft, setAutomationEditDraft,
|
updateKbArticle, deleteKbArticle, automationEditDraft, setAutomationEditDraft,
|
||||||
updateAutomation, deleteAutomation, overview
|
updateAutomation, deleteAutomation, overview,
|
||||||
|
ticketTopics, loadTicketTopics, ticketTopicDraft, setTicketTopicDraft, saveTicketTopic
|
||||||
} = useApp();
|
} = useApp();
|
||||||
|
const { roles } = useGuildResources(currentGuildId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentGuildId) loadTicketTopics();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentGuildId]);
|
||||||
|
|
||||||
const openTickets = useMemo(() => tickets.filter((t) => t.status !== 'closed'), [tickets]);
|
const openTickets = useMemo(() => tickets.filter((t) => t.status !== 'closed'), [tickets]);
|
||||||
|
|
||||||
@@ -30,6 +39,7 @@ export function Tickets() {
|
|||||||
<Tab id="sla">SLA</Tab>
|
<Tab id="sla">SLA</Tab>
|
||||||
<Tab id="automations">Automationen</Tab>
|
<Tab id="automations">Automationen</Tab>
|
||||||
<Tab id="kb">Knowledge Base</Tab>
|
<Tab id="kb">Knowledge Base</Tab>
|
||||||
|
<Tab id="kategorien">Kategorien</Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs.ListContainer>
|
</Tabs.ListContainer>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -350,6 +360,85 @@ export function Tickets() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{ticketTab === 'kategorien' && (
|
||||||
|
<div className="mt-5 grid gap-5 xl:grid-cols-[1fr_420px]">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 text-base font-semibold">Konfigurierte Kategorien ({Object.keys(ticketTopics).length})</h3>
|
||||||
|
<p className="mb-3 text-xs text-muted">
|
||||||
|
Der Kategorie-Slug entspricht dem Ticket-Grund (z.B. <code>ban</code>, <code>help</code>, <code>feedback</code>, <code>other</code>). Ohne Konfiguration bleibt das bisherige Verhalten unverändert.
|
||||||
|
</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{Object.keys(ticketTopics).length ? Object.entries(ticketTopics).map(([topic, cfg]) => (
|
||||||
|
<Card key={topic}>
|
||||||
|
<CardContent className="flex items-center justify-between gap-3 p-4">
|
||||||
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
|
<div className="bg-accent-soft text-accent-soft-foreground flex size-9 shrink-0 items-center justify-center rounded-lg">
|
||||||
|
<Tag size={14} />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="truncate text-sm font-semibold">{topic}</div>
|
||||||
|
<div className="flex items-center gap-1 text-xs text-muted">
|
||||||
|
<Users size={12} />
|
||||||
|
{cfg.roleId ? roles.find((r) => r.id === cfg.roleId)?.name || cfg.roleId : 'Standard-Support-Rolle'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
|
<Chip size="sm" variant="soft">{cfg.questions?.length ?? 0} Fragen</Chip>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="tertiary"
|
||||||
|
onPress={() => setTicketTopicDraft({ topic, roleId: cfg.roleId || '', questions: (cfg.questions || []).join('\n') })}
|
||||||
|
>
|
||||||
|
<Pencil size={14} /> Bearbeiten
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)) : <EmptyState message="Noch keine Kategorien konfiguriert" icon={<Tag size={24} />} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="px-5 pt-5 pb-0">
|
||||||
|
<h3 className="text-base font-semibold">Kategorie {ticketTopicDraft.topic ? 'bearbeiten' : 'anlegen'}</h3>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4 p-5">
|
||||||
|
<TextField>
|
||||||
|
<Label>Kategorie-Slug</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="z.B. help, ban, feedback, other"
|
||||||
|
value={ticketTopicDraft.topic}
|
||||||
|
onChange={(e) => setTicketTopicDraft((s: any) => ({ ...s, topic: e.target.value }))}
|
||||||
|
/>
|
||||||
|
</TextField>
|
||||||
|
<TextField>
|
||||||
|
<Label>Rolle, die gepingt wird</Label>
|
||||||
|
<RoleSelect options={roles} value={ticketTopicDraft.roleId} onChange={(id) => setTicketTopicDraft((s: any) => ({ ...s, roleId: id }))} placeholder="Standard-Support-Rolle" />
|
||||||
|
</TextField>
|
||||||
|
<TextField>
|
||||||
|
<Label>Fragen-Vorlage (eine pro Zeile, max. 5)</Label>
|
||||||
|
<TextArea
|
||||||
|
rows={5}
|
||||||
|
placeholder={'Was ist passiert?\nWann ist es aufgetreten?\nHast du einen Screenshot?'}
|
||||||
|
value={ticketTopicDraft.questions}
|
||||||
|
onChange={(e) => setTicketTopicDraft((s: any) => ({ ...s, questions: e.target.value }))}
|
||||||
|
/>
|
||||||
|
</TextField>
|
||||||
|
<p className="text-xs text-muted">Wenn Fragen hinterlegt sind, öffnet sich beim Ticket-Erstellen ein Formular statt sofort ein Kanal zu erstellen.</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="primary" onPress={saveTicketTopic} isDisabled={!ticketTopicDraft.topic.trim()}>Speichern</Button>
|
||||||
|
{ticketTopicDraft.topic && (
|
||||||
|
<Button variant="tertiary" onPress={() => setTicketTopicDraft({ topic: '', roleId: '', questions: '' })}>Abbrechen</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,34 @@
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, TextArea, Button, Separator, TextField, Label } from '@heroui/react';
|
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 { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
import { DiscordPreview } from '../components/shared/DiscordPreview';
|
import { DiscordPreview } from '../components/shared/DiscordPreview';
|
||||||
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
import { ChannelSelect } from '../components/shared/ChannelSelect';
|
||||||
import { AppSwitch } from '../components/shared/AppSwitch';
|
import { ModuleActiveToggle } from '../components/shared/ModuleActiveToggle';
|
||||||
import { useGuildResources } from '../hooks/useGuildResources';
|
import { useGuildResources } from '../hooks/useGuildResources';
|
||||||
|
|
||||||
|
const MAX_IMAGE_BYTES = 3 * 1024 * 1024;
|
||||||
|
|
||||||
export function Welcome() {
|
export function Welcome() {
|
||||||
const { settings, setSettings, saveSettingsPayload, currentGuildId } = useApp();
|
const { settings, setSettings, saveSettingsPayload, currentGuildId } = useApp();
|
||||||
const { channels } = useGuildResources(currentGuildId);
|
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 (
|
return (
|
||||||
<SectionCard title="Willkommen" subtitle="Welcome-Embeds und Join-Nachrichten">
|
<SectionCard title="Willkommen" subtitle="Welcome-Embeds und Join-Nachrichten">
|
||||||
<div className="grid gap-5 xl:grid-cols-2">
|
<div className="grid gap-5 xl:grid-cols-2">
|
||||||
@@ -22,10 +40,12 @@ export function Welcome() {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<AppSwitch
|
<ModuleActiveToggle
|
||||||
|
icon={<Sparkles size={16} />}
|
||||||
|
title="Welcome aktiv"
|
||||||
|
description="Sendet ein Begrüßungs-Embed, wenn neue Mitglieder beitreten."
|
||||||
isSelected={settings.welcomeConfig?.enabled !== false}
|
isSelected={settings.welcomeConfig?.enabled !== false}
|
||||||
onChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), enabled: v } }))}
|
onChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), enabled: v } }))}
|
||||||
label={<div className="flex items-center gap-2"><Sparkles size={16} /> Welcome aktiv</div>}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField>
|
<TextField>
|
||||||
@@ -65,6 +85,38 @@ export function Welcome() {
|
|||||||
/>
|
/>
|
||||||
</TextField>
|
</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 />
|
<Separator />
|
||||||
|
|
||||||
<Button size="lg" variant="primary" onPress={() => saveSettingsPayload({ welcomeConfig: settings.welcomeConfig || {} }, 'Welcome gespeichert')}>
|
<Button size="lg" variant="primary" onPress={() => saveSettingsPayload({ welcomeConfig: settings.welcomeConfig || {} }, 'Welcome gespeichert')}>
|
||||||
@@ -85,6 +137,7 @@ export function Welcome() {
|
|||||||
title={settings.welcomeConfig?.embedTitle || 'Willkommen!'}
|
title={settings.welcomeConfig?.embedTitle || 'Willkommen!'}
|
||||||
description={settings.welcomeConfig?.embedDescription || 'Willkommen auf dem Server!'}
|
description={settings.welcomeConfig?.embedDescription || 'Willkommen auf dem Server!'}
|
||||||
footer={settings.welcomeConfig?.embedFooter}
|
footer={settings.welcomeConfig?.embedFooter}
|
||||||
|
image={imagePreview}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className="text-sm text-muted">
|
<p className="text-sm text-muted">
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export type NavKey =
|
|||||||
| 'watchlist'
|
| 'watchlist'
|
||||||
| 'branding'
|
| 'branding'
|
||||||
| 'growth'
|
| 'growth'
|
||||||
|
| 'permissions'
|
||||||
|
| 'panels'
|
||||||
| 'admin';
|
| 'admin';
|
||||||
|
|
||||||
export type TicketRecord = {
|
export type TicketRecord = {
|
||||||
@@ -179,6 +181,55 @@ export type GrowthStats = {
|
|||||||
dailyJoins: { day: string; count: number }[];
|
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 PermissionScanResult = {
|
||||||
|
adminRoles: { id: string; name: string }[];
|
||||||
|
banRoles: { id: string; name: string }[];
|
||||||
|
dangerousBots: { id: string; tag: string; perms: string[] }[];
|
||||||
|
publicChannels: { id: string; name: string }[];
|
||||||
|
everyoneCanSendChannels: { id: string; name: string }[];
|
||||||
|
emptyRoles: { id: string; name: string }[];
|
||||||
|
duplicateRoleGroups: { id: string; name: string }[][];
|
||||||
|
uselessRoles: { id: string; name: string }[];
|
||||||
|
tooManyAdmins: boolean;
|
||||||
|
adminMemberCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InfoPanelType = 'rules' | 'support' | 'bewerbung' | 'partner' | 'rollen' | 'events' | 'faq';
|
||||||
|
|
||||||
|
export type InfoPanel = {
|
||||||
|
id: string;
|
||||||
|
guildId: string;
|
||||||
|
channelId: string;
|
||||||
|
messageId?: string | null;
|
||||||
|
type: InfoPanelType;
|
||||||
|
title: string;
|
||||||
|
description?: string | null;
|
||||||
|
items?: { question: string; answer: string }[] | null;
|
||||||
|
createdAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TicketTopicConfig = {
|
||||||
|
roleId?: string;
|
||||||
|
questions?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type MusicSession = {
|
export type MusicSession = {
|
||||||
guildId: string;
|
guildId: string;
|
||||||
nowPlaying?: { title: string; url: string } | null;
|
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
|
../acorn/bin/acorn
|
||||||
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
|
|
||||||
17
node_modules/.bin/mime
generated
vendored
17
node_modules/.bin/mime
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../mime/cli.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/mkdirp
generated
vendored
17
node_modules/.bin/mkdirp
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../mkdirp/bin/cmd.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/prisma
generated
vendored
17
node_modules/.bin/prisma
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../prisma/build/index.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/resolve
generated
vendored
17
node_modules/.bin/resolve
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../resolve/bin/resolve
|
||||||
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
|
|
||||||
17
node_modules/.bin/rimraf
generated
vendored
17
node_modules/.bin/rimraf
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../rimraf/bin.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/tree-kill
generated
vendored
17
node_modules/.bin/tree-kill
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../tree-kill/cli.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/ts-node
generated
vendored
17
node_modules/.bin/ts-node
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../ts-node/dist/bin.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/ts-node-cwd
generated
vendored
17
node_modules/.bin/ts-node-cwd
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../ts-node/dist/bin-cwd.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/ts-node-dev
generated
vendored
17
node_modules/.bin/ts-node-dev
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../ts-node-dev/lib/bin.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/ts-node-esm
generated
vendored
17
node_modules/.bin/ts-node-esm
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../ts-node/dist/bin-esm.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/ts-node-script
generated
vendored
17
node_modules/.bin/ts-node-script
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../ts-node/dist/bin-script.js
|
||||||
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
|
|
||||||
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
|
../ts-node/dist/bin-transpile.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/ts-script
generated
vendored
17
node_modules/.bin/ts-script
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../ts-node/dist/bin-script-deprecated.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/tsc
generated
vendored
17
node_modules/.bin/tsc
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../typescript/bin/tsc
|
||||||
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
|
|
||||||
17
node_modules/.bin/tsnd
generated
vendored
17
node_modules/.bin/tsnd
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../ts-node-dev/lib/bin.js
|
||||||
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
|
|
||||||
17
node_modules/.bin/tsserver
generated
vendored
17
node_modules/.bin/tsserver
generated
vendored
@@ -1,16 +1 @@
|
|||||||
#!/bin/sh
|
../typescript/bin/tsserver
|
||||||
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
|
|
||||||
823
node_modules/.package-lock.json
generated
vendored
823
node_modules/.package-lock.json
generated
vendored
@@ -4,6 +4,16 @@
|
|||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"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": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@@ -264,6 +274,430 @@
|
|||||||
"scripts/actions/documentation"
|
"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": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"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"
|
"@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",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@snazzah/davey-win32-x64-msvc/-/davey-win32-x64-msvc-0.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-x64-gnu/-/davey-linux-x64-gnu-0.1.8.tgz",
|
||||||
"integrity": "sha512-JKIco1miwtM4NgVwU/H9TJdUaSlJ+kdtydy3+tiV9cmJv0u1SM2NpwjV85H44xAQWW2zcRBHP0ZDriciHw09qQ==",
|
"integrity": "sha512-yghgG7iXZUHy734Cq3PcgrbRnLhhB233JNTX5VPRxRqdwFAg2MzAJ2iSWpP12K6hSqKq9hw0sdt8CNpr0mEXjQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"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": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.12",
|
"version": "1.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||||
@@ -724,6 +1220,12 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
@@ -771,6 +1273,15 @@
|
|||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -831,6 +1342,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.3",
|
"version": "1.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
@@ -1279,6 +1796,11 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/express": {
|
||||||
"version": "4.21.2",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
@@ -1381,6 +1903,24 @@
|
|||||||
"node": ">=16"
|
"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": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -1527,6 +2067,16 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
@@ -1682,6 +2232,41 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
@@ -1779,6 +2364,56 @@
|
|||||||
"node": ">=0.12.0"
|
"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": {
|
"node_modules/libsodium": {
|
||||||
"version": "0.7.15",
|
"version": "0.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz",
|
||||||
@@ -2080,6 +2715,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
@@ -2110,6 +2751,34 @@
|
|||||||
"wrappy": "1"
|
"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": {
|
"node_modules/parse-cache-control": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
|
"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"
|
"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": {
|
"node_modules/play-audio": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
|
||||||
@@ -2177,6 +2867,15 @@
|
|||||||
"node": ">=16.0.0"
|
"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": {
|
"node_modules/prism-media": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
|
||||||
@@ -2393,6 +3092,15 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.3",
|
"version": "7.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||||
@@ -2549,6 +3257,15 @@
|
|||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/sodium-native": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz",
|
||||||
@@ -2643,6 +3360,22 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
@@ -2673,6 +3406,12 @@
|
|||||||
"node": ">=10"
|
"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": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
@@ -2695,6 +3434,24 @@
|
|||||||
"node": ">=0.6"
|
"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": {
|
"node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
@@ -2860,6 +3617,18 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/undici": {
|
||||||
"version": "6.21.3",
|
"version": "6.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||||
@@ -2884,6 +3653,15 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"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": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
@@ -2992,6 +3798,15 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"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',
|
brandingConfig: 'brandingConfig',
|
||||||
partnerConfig: 'partnerConfig',
|
partnerConfig: 'partnerConfig',
|
||||||
galleryConfig: 'galleryConfig',
|
galleryConfig: 'galleryConfig',
|
||||||
|
imageModerationConfig: 'imageModerationConfig',
|
||||||
|
ticketConfig: 'ticketConfig',
|
||||||
supportRoleId: 'supportRoleId',
|
supportRoleId: 'supportRoleId',
|
||||||
updatedAt: 'updatedAt',
|
updatedAt: 'updatedAt',
|
||||||
createdAt: 'createdAt'
|
createdAt: 'createdAt'
|
||||||
@@ -469,6 +471,20 @@ exports.Prisma.GuildGrowthEventScalarFieldEnum = {
|
|||||||
type: 'type',
|
type: 'type',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
inviteCode: 'inviteCode',
|
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'
|
createdAt: 'createdAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -534,7 +550,8 @@ exports.Prisma.ModelName = {
|
|||||||
PartnerRequest: 'PartnerRequest',
|
PartnerRequest: 'PartnerRequest',
|
||||||
GalleryPost: 'GalleryPost',
|
GalleryPost: 'GalleryPost',
|
||||||
GalleryVote: 'GalleryVote',
|
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',
|
brandingConfig: 'brandingConfig',
|
||||||
partnerConfig: 'partnerConfig',
|
partnerConfig: 'partnerConfig',
|
||||||
galleryConfig: 'galleryConfig',
|
galleryConfig: 'galleryConfig',
|
||||||
|
imageModerationConfig: 'imageModerationConfig',
|
||||||
|
ticketConfig: 'ticketConfig',
|
||||||
supportRoleId: 'supportRoleId',
|
supportRoleId: 'supportRoleId',
|
||||||
updatedAt: 'updatedAt',
|
updatedAt: 'updatedAt',
|
||||||
createdAt: 'createdAt'
|
createdAt: 'createdAt'
|
||||||
@@ -469,6 +471,20 @@ exports.Prisma.GuildGrowthEventScalarFieldEnum = {
|
|||||||
type: 'type',
|
type: 'type',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
inviteCode: 'inviteCode',
|
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'
|
createdAt: 'createdAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -534,7 +550,8 @@ exports.Prisma.ModelName = {
|
|||||||
PartnerRequest: 'PartnerRequest',
|
PartnerRequest: 'PartnerRequest',
|
||||||
GalleryPost: 'GalleryPost',
|
GalleryPost: 'GalleryPost',
|
||||||
GalleryVote: 'GalleryVote',
|
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": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"ffmpeg-static": "^5.2.0",
|
"ffmpeg-static": "^5.2.0",
|
||||||
|
"jimp": "^1.6.1",
|
||||||
|
"jsqr": "^1.4.0",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"play-dl": "^1.9.7",
|
"play-dl": "^1.9.7",
|
||||||
"sodium-native": "^4.0.4"
|
"sodium-native": "^4.0.4"
|
||||||
@@ -34,6 +36,16 @@
|
|||||||
"typescript": "^5.2.2"
|
"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": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@@ -325,6 +337,430 @@
|
|||||||
"tslib": "^2.4.0"
|
"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": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
@@ -718,6 +1154,52 @@
|
|||||||
"node": ">= 10"
|
"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": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.12",
|
"version": "1.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||||
@@ -1015,6 +1497,12 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
@@ -1062,6 +1550,15 @@
|
|||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -1122,6 +1619,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.3",
|
"version": "1.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
@@ -1570,6 +2073,11 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/express": {
|
||||||
"version": "4.21.2",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
@@ -1672,6 +2180,24 @@
|
|||||||
"node": ">=16"
|
"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": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -1755,7 +2281,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -1833,6 +2358,16 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
@@ -1988,6 +2523,41 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
@@ -2085,6 +2655,56 @@
|
|||||||
"node": ">=0.12.0"
|
"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": {
|
"node_modules/libsodium": {
|
||||||
"version": "0.7.15",
|
"version": "0.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz",
|
||||||
@@ -2386,6 +3006,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
@@ -2416,6 +3042,34 @@
|
|||||||
"wrappy": "1"
|
"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": {
|
"node_modules/parse-cache-control": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
|
"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"
|
"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": {
|
"node_modules/play-audio": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
|
||||||
@@ -2483,6 +3158,15 @@
|
|||||||
"node": ">=16.0.0"
|
"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": {
|
"node_modules/prism-media": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
|
||||||
@@ -2699,6 +3383,15 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.3",
|
"version": "7.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||||
@@ -2855,6 +3548,15 @@
|
|||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/sodium-native": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz",
|
||||||
@@ -2949,6 +3651,22 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
@@ -2979,6 +3697,12 @@
|
|||||||
"node": ">=10"
|
"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": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
@@ -3001,6 +3725,24 @@
|
|||||||
"node": ">=0.6"
|
"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": {
|
"node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
@@ -3166,6 +3908,18 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/undici": {
|
||||||
"version": "6.21.3",
|
"version": "6.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||||
@@ -3190,6 +3944,15 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"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": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
@@ -3298,6 +4089,15 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"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"
|
"start": "node dist/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@discordjs/opus": "^0.9.0",
|
||||||
"@discordjs/rest": "^2.2.0",
|
"@discordjs/rest": "^2.2.0",
|
||||||
"@discordjs/voice": "^0.19.0",
|
"@discordjs/voice": "^0.19.0",
|
||||||
"@discordjs/opus": "^0.9.0",
|
|
||||||
"@prisma/client": "^5.4.2",
|
"@prisma/client": "^5.4.2",
|
||||||
"@snazzah/davey": "^0.1.8",
|
"@snazzah/davey": "^0.1.8",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
@@ -24,10 +24,12 @@
|
|||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
|
"ffmpeg-static": "^5.2.0",
|
||||||
|
"jimp": "^1.6.1",
|
||||||
|
"jsqr": "^1.4.0",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"play-dl": "^1.9.7",
|
"play-dl": "^1.9.7",
|
||||||
"sodium-native": "^4.0.4",
|
"sodium-native": "^4.0.4"
|
||||||
"ffmpeg-static": "^5.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@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 { AltAccountService } from '../services/altAccountService';
|
||||||
import { GalleryService } from '../services/galleryService';
|
import { GalleryService } from '../services/galleryService';
|
||||||
import { GrowthService } from '../services/growthService';
|
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 logging = new LoggingService();
|
||||||
const moduleService = new BotModuleService();
|
const moduleService = new BotModuleService();
|
||||||
@@ -74,7 +77,10 @@ export const context = {
|
|||||||
partners: new PartnerService(),
|
partners: new PartnerService(),
|
||||||
altAccounts: new AltAccountService(),
|
altAccounts: new AltAccountService(),
|
||||||
gallery: new GalleryService(),
|
gallery: new GalleryService(),
|
||||||
growth: new GrowthService()
|
growth: new GrowthService(),
|
||||||
|
permissionScan: new PermissionScanService(),
|
||||||
|
infoPanels: new InfoPanelService(),
|
||||||
|
imageModeration: new ImageModerationService()
|
||||||
};
|
};
|
||||||
|
|
||||||
birthdayService.setBadgeService(badgeService);
|
birthdayService.setBadgeService(badgeService);
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ export interface GuildSettings {
|
|||||||
artistRoleId?: string;
|
artistRoleId?: string;
|
||||||
lastWinnerAt?: string;
|
lastWinnerAt?: string;
|
||||||
};
|
};
|
||||||
|
imageModerationConfig?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
alertOnly?: boolean;
|
||||||
|
};
|
||||||
|
ticketConfig?: {
|
||||||
|
topics?: Record<string, { roleId?: string; questions?: string[] }>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsStore {
|
class SettingsStore {
|
||||||
@@ -170,6 +177,8 @@ class SettingsStore {
|
|||||||
brandingConfig: (row as any).brandingConfig ?? undefined,
|
brandingConfig: (row as any).brandingConfig ?? undefined,
|
||||||
partnerConfig: (row as any).partnerConfig ?? undefined,
|
partnerConfig: (row as any).partnerConfig ?? undefined,
|
||||||
galleryConfig: (row as any).galleryConfig ?? undefined,
|
galleryConfig: (row as any).galleryConfig ?? undefined,
|
||||||
|
imageModerationConfig: (row as any).imageModerationConfig ?? undefined,
|
||||||
|
ticketConfig: (row as any).ticketConfig ?? undefined,
|
||||||
supportRoleId: row.supportRoleId ?? undefined
|
supportRoleId: row.supportRoleId ?? undefined
|
||||||
} satisfies GuildSettings;
|
} satisfies GuildSettings;
|
||||||
this.cache.set(row.guildId, this.applyModuleDefaults(cfg));
|
this.cache.set(row.guildId, this.applyModuleDefaults(cfg));
|
||||||
@@ -237,6 +246,16 @@ class SettingsStore {
|
|||||||
if (partial.galleryConfig) {
|
if (partial.galleryConfig) {
|
||||||
merged.galleryConfig = { ...(merged.galleryConfig ?? {}), ...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.automodConfig = { ...mergedAutomod, supportLoginConfig: merged.supportLoginConfig ?? mergedAutomod['supportLoginConfig'] };
|
||||||
merged.statuspageEnabled = mergedAutomod.statuspageEnabled;
|
merged.statuspageEnabled = mergedAutomod.statuspageEnabled;
|
||||||
merged.statuspageConfig = mergedAutomod.statuspageConfig;
|
merged.statuspageConfig = mergedAutomod.statuspageConfig;
|
||||||
@@ -272,6 +291,8 @@ class SettingsStore {
|
|||||||
brandingConfig: merged.brandingConfig ?? Prisma.JsonNull,
|
brandingConfig: merged.brandingConfig ?? Prisma.JsonNull,
|
||||||
partnerConfig: merged.partnerConfig ?? Prisma.JsonNull,
|
partnerConfig: merged.partnerConfig ?? Prisma.JsonNull,
|
||||||
galleryConfig: merged.galleryConfig ?? Prisma.JsonNull,
|
galleryConfig: merged.galleryConfig ?? Prisma.JsonNull,
|
||||||
|
imageModerationConfig: merged.imageModerationConfig ?? Prisma.JsonNull,
|
||||||
|
ticketConfig: merged.ticketConfig ?? Prisma.JsonNull,
|
||||||
supportRoleId: merged.supportRoleId ?? null
|
supportRoleId: merged.supportRoleId ?? null
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
@@ -302,6 +323,8 @@ class SettingsStore {
|
|||||||
brandingConfig: merged.brandingConfig ?? Prisma.JsonNull,
|
brandingConfig: merged.brandingConfig ?? Prisma.JsonNull,
|
||||||
partnerConfig: merged.partnerConfig ?? Prisma.JsonNull,
|
partnerConfig: merged.partnerConfig ?? Prisma.JsonNull,
|
||||||
galleryConfig: merged.galleryConfig ?? Prisma.JsonNull,
|
galleryConfig: merged.galleryConfig ?? Prisma.JsonNull,
|
||||||
|
imageModerationConfig: merged.imageModerationConfig ?? Prisma.JsonNull,
|
||||||
|
ticketConfig: merged.ticketConfig ?? Prisma.JsonNull,
|
||||||
supportRoleId: merged.supportRoleId ?? null
|
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?
|
brandingConfig Json?
|
||||||
partnerConfig Json?
|
partnerConfig Json?
|
||||||
galleryConfig Json?
|
galleryConfig Json?
|
||||||
|
imageModerationConfig Json?
|
||||||
|
ticketConfig Json?
|
||||||
supportRoleId String?
|
supportRoleId String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -442,7 +444,23 @@ model GuildGrowthEvent {
|
|||||||
type String
|
type String
|
||||||
userId String
|
userId String
|
||||||
inviteCode String?
|
inviteCode String?
|
||||||
|
inviterId String?
|
||||||
|
suspicious Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
@@index([guildId, type, createdAt])
|
@@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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.altAccounts.check(member).then(({ score, reasons }) => {
|
Promise.all([context.altAccounts.check(member), context.growth.resolveUsedInvite(member.guild)])
|
||||||
if (context.altAccounts.isSuspicious(score)) context.logging.logSuspiciousJoin(member, reasons);
|
.then(([{ score, reasons }, invite]) => {
|
||||||
}).catch(() => undefined);
|
const suspicious = context.altAccounts.isSuspicious(score);
|
||||||
|
if (suspicious) context.logging.logSuspiciousJoin(member, reasons);
|
||||||
context.growth.resolveUsedInvite(member.guild).then((inviteCode) => {
|
context.growth.recordJoin(member.guild.id, member.id, invite, suspicious);
|
||||||
context.growth.recordJoin(member.guild.id, member.id, inviteCode);
|
})
|
||||||
}).catch(() => undefined);
|
.catch(() => undefined);
|
||||||
|
|
||||||
const guildConfig = settingsStore.get(member.guild.id);
|
const guildConfig = settingsStore.get(member.guild.id);
|
||||||
const welcomeCfg = guildConfig?.welcomeConfig || guildConfig?.automodConfig?.welcomeConfig;
|
const welcomeCfg = guildConfig?.welcomeConfig || guildConfig?.automodConfig?.welcomeConfig;
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ const event: EventHandler = {
|
|||||||
await context.gallery.handleComponent(interaction);
|
await context.gallery.handleComponent(interaction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (interaction.customId.startsWith('panel:')) {
|
||||||
|
await context.infoPanels.handleComponent(interaction as any);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.isButton()) {
|
if (interaction.isButton()) {
|
||||||
@@ -87,6 +91,14 @@ const event: EventHandler = {
|
|||||||
await context.partners.handleModal(interaction);
|
await context.partners.handleModal(interaction);
|
||||||
return;
|
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?.automodEnabled === true) context.automod.checkMessage(message, cfg);
|
||||||
if (cfg?.levelingEnabled === true) context.leveling.handleMessage(message);
|
if (cfg?.levelingEnabled === true) context.leveling.handleMessage(message);
|
||||||
if (cfg?.badgesEnabled !== false) context.badges.trackMessage(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
|
// Ticket SLA + KB
|
||||||
await context.tickets.trackFirstResponse(message);
|
await context.tickets.trackFirstResponse(message);
|
||||||
await context.tickets.suggestKnowledgeBase(message);
|
await context.tickets.suggestKnowledgeBase(message);
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { Guild } from 'discord.js';
|
|||||||
import { prisma } from '../database';
|
import { prisma } from '../database';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
|
interface UsedInvite {
|
||||||
|
code: string;
|
||||||
|
inviterId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class GrowthService {
|
export class GrowthService {
|
||||||
private inviteCache = new Map<string, Map<string, number>>();
|
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);
|
const before = this.inviteCache.get(guild.id);
|
||||||
try {
|
try {
|
||||||
const invites = await guild.invites.fetch();
|
const invites = await guild.invites.fetch();
|
||||||
let usedCode: string | null = null;
|
let used: UsedInvite | null = null;
|
||||||
invites.forEach((inv) => {
|
invites.forEach((inv) => {
|
||||||
const prevUses = before?.get(inv.code) ?? 0;
|
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()) {
|
for (const code of before.keys()) {
|
||||||
if (!invites.has(code)) {
|
if (!invites.has(code)) {
|
||||||
usedCode = code;
|
used = { code };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,15 +41,17 @@ export class GrowthService {
|
|||||||
const map = new Map<string, number>();
|
const map = new Map<string, number>();
|
||||||
invites.forEach((inv) => map.set(inv.code, inv.uses ?? 0));
|
invites.forEach((inv) => map.set(inv.code, inv.uses ?? 0));
|
||||||
this.inviteCache.set(guild.id, map);
|
this.inviteCache.set(guild.id, map);
|
||||||
return usedCode;
|
return used;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(`Failed to resolve used invite for ${guild.id}: ${err}`);
|
logger.warn(`Failed to resolve used invite for ${guild.id}: ${err}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async recordJoin(guildId: string, userId: string, inviteCode: string | null) {
|
public async recordJoin(guildId: string, userId: string, invite: UsedInvite | null, suspicious = false) {
|
||||||
await prisma.guildGrowthEvent.create({ data: { guildId, userId, type: 'join', inviteCode: inviteCode ?? undefined } }).catch(() => undefined);
|
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) {
|
public async recordLeave(guildId: string, userId: string) {
|
||||||
@@ -93,4 +100,42 @@ export class GrowthService {
|
|||||||
.sort((a, b) => a.day.localeCompare(b.day))
|
.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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
161
src/services/infoPanelService.ts
Normal file
161
src/services/infoPanelService.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonInteraction,
|
||||||
|
ButtonStyle,
|
||||||
|
ChatInputCommandInteraction,
|
||||||
|
Client,
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const panel = await this.createPanel(interaction.guildId, channelId, type, title, description, items, interaction.client);
|
||||||
|
await interaction.reply({ content: `Panel wurde in <#${panel.channelId}> gepostet.`, ephemeral: true });
|
||||||
|
} catch {
|
||||||
|
await interaction.reply({ content: 'Zielkanal nicht gefunden.', ephemeral: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createPanel(
|
||||||
|
guildId: string,
|
||||||
|
channelId: string,
|
||||||
|
type: PanelType,
|
||||||
|
title: string,
|
||||||
|
description: string | undefined,
|
||||||
|
items: FaqItem[] | undefined,
|
||||||
|
client: Client
|
||||||
|
) {
|
||||||
|
const channel = await client.channels.fetch(channelId).catch(() => null);
|
||||||
|
if (!channel || !channel.isTextBased()) throw new Error('channel not found');
|
||||||
|
|
||||||
|
const panel = await prisma.infoPanel.create({
|
||||||
|
data: { guildId, channelId, type, title, description, items: (items as any) ?? undefined }
|
||||||
|
});
|
||||||
|
|
||||||
|
const { embed, components } = await this.render(panel.id, type, title, description, items, guildId);
|
||||||
|
const sent = await (channel as any).send({ embeds: [embed], components });
|
||||||
|
return prisma.infoPanel.update({ where: { id: panel.id }, data: { messageId: sent.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listPanels(guildId: string) {
|
||||||
|
return prisma.infoPanel.findMany({ where: { guildId }, orderBy: { createdAt: 'desc' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deletePanel(guildId: string, id: string) {
|
||||||
|
await prisma.infoPanel.deleteMany({ where: { id, guildId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
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' });
|
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[]) {
|
logRoleUpdate(member: GuildMember, added: string[], removed: string[]) {
|
||||||
const guildId = member.guild.id;
|
const guildId = member.guild.id;
|
||||||
adminSink?.pushGuildLog({
|
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,
|
EmbedBuilder,
|
||||||
Guild,
|
Guild,
|
||||||
GuildMember,
|
GuildMember,
|
||||||
|
ModalBuilder,
|
||||||
|
ModalSubmitInteraction,
|
||||||
PermissionsBitField,
|
PermissionsBitField,
|
||||||
TextChannel,
|
TextChannel,
|
||||||
|
TextInputBuilder,
|
||||||
|
TextInputStyle,
|
||||||
Client,
|
Client,
|
||||||
Message
|
Message
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
@@ -88,6 +92,21 @@ export class TicketService {
|
|||||||
return;
|
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);
|
const record = await this.openTicket(interaction.guild, interaction.member as GuildMember, topic);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: record ? 'Ticket erstellt! Schau im neuen Kanal nach.' : 'Ticket konnte nicht erstellt werden.',
|
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) {
|
public async claimTicket(interaction: ChatInputCommandInteraction) {
|
||||||
const channel = interaction.channel as TextChannel;
|
const channel = interaction.channel as TextChannel;
|
||||||
const ticket = await prisma.ticket.findFirst({ where: { channelId: channel.id } });
|
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> {
|
private async openTicket(guild: Guild, member: GuildMember, topic: string): Promise<TicketRecord | null> {
|
||||||
if (!this.isEnabled(guild.id)) return null;
|
if (!this.isEnabled(guild.id)) return null;
|
||||||
const category = await this.ensureCategory(guild);
|
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[] = [
|
const overwrites: any[] = [
|
||||||
{
|
{
|
||||||
id: guild.id,
|
id: guild.id,
|
||||||
|
|||||||
@@ -102,6 +102,75 @@ router.get('/growth', requireAuth, async (req, res) => {
|
|||||||
res.json({ stats });
|
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('/permissions/scan', 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 result = await context.permissionScan.scan(guild);
|
||||||
|
res.json({ result });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/panels', 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 panels = await context.infoPanels.listPanels(guildId);
|
||||||
|
res.json({ panels });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/panels', requireAuth, async (req, res) => {
|
||||||
|
const { guildId, channelId, type, title, description, items } = req.body || {};
|
||||||
|
if (!guildId || !channelId || !type || !title) return res.status(400).json({ error: 'guildId, channelId, type, title required' });
|
||||||
|
if (!context.client) return res.status(503).json({ error: 'bot not ready' });
|
||||||
|
try {
|
||||||
|
const panel = await context.infoPanels.createPanel(guildId, channelId, type, title, description || undefined, items || undefined, context.client);
|
||||||
|
res.json({ panel });
|
||||||
|
} catch {
|
||||||
|
res.status(404).json({ error: 'Zielkanal nicht gefunden' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/panels/:id', 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' });
|
||||||
|
await context.infoPanels.deletePanel(guildId, req.params.id);
|
||||||
|
res.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/ticketconfig', 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 topics = settingsStore.get(guildId)?.ticketConfig?.topics || {};
|
||||||
|
res.json({ topics });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/ticketconfig', requireAuth, async (req, res) => {
|
||||||
|
const { guildId, topic, roleId, questions } = req.body || {};
|
||||||
|
if (!guildId || !topic) return res.status(400).json({ error: 'guildId, topic required' });
|
||||||
|
const key = String(topic).trim().toLowerCase();
|
||||||
|
const current = settingsStore.get(guildId)?.ticketConfig?.topics?.[key] || {};
|
||||||
|
await settingsStore.set(guildId, {
|
||||||
|
ticketConfig: {
|
||||||
|
topics: {
|
||||||
|
[key]: {
|
||||||
|
roleId: roleId !== undefined ? (roleId || undefined) : current.roleId,
|
||||||
|
questions: Array.isArray(questions) ? questions.filter(Boolean).slice(0, 5) : current.questions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/guild/activity', requireAuth, (req, res) => {
|
router.get('/guild/activity', requireAuth, (req, res) => {
|
||||||
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
||||||
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
@@ -959,7 +1028,13 @@ router.post('/settings', requireAuth, async (req, res) => {
|
|||||||
registerEnabled,
|
registerEnabled,
|
||||||
registerConfig,
|
registerConfig,
|
||||||
serverStatsEnabled,
|
serverStatsEnabled,
|
||||||
serverStatsConfig
|
serverStatsConfig,
|
||||||
|
lockdownConfig,
|
||||||
|
brandingConfig,
|
||||||
|
partnerConfig,
|
||||||
|
galleryConfig,
|
||||||
|
imageModerationConfig,
|
||||||
|
ticketConfig
|
||||||
} = req.body;
|
} = req.body;
|
||||||
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
const normalizeArray = (val: any) =>
|
const normalizeArray = (val: any) =>
|
||||||
@@ -1097,7 +1172,13 @@ router.post('/settings', requireAuth, async (req, res) => {
|
|||||||
registerEnabled: parsedRegister.enabled,
|
registerEnabled: parsedRegister.enabled,
|
||||||
registerConfig: parsedRegister,
|
registerConfig: parsedRegister,
|
||||||
serverStatsEnabled: typeof serverStatsEnabled === 'string' ? serverStatsEnabled === 'true' : serverStatsEnabled,
|
serverStatsEnabled: typeof serverStatsEnabled === 'string' ? serverStatsEnabled === 'true' : serverStatsEnabled,
|
||||||
serverStatsConfig: serverStatsConfig
|
serverStatsConfig: serverStatsConfig,
|
||||||
|
lockdownConfig: lockdownConfig ?? (current as any).lockdownConfig,
|
||||||
|
brandingConfig: brandingConfig ?? (current as any).brandingConfig,
|
||||||
|
partnerConfig: partnerConfig ?? (current as any).partnerConfig,
|
||||||
|
galleryConfig: galleryConfig ?? (current as any).galleryConfig,
|
||||||
|
imageModerationConfig: imageModerationConfig ?? (current as any).imageModerationConfig,
|
||||||
|
ticketConfig: ticketConfig ?? (current as any).ticketConfig
|
||||||
});
|
});
|
||||||
// Live update logging target
|
// Live update logging target
|
||||||
context.logging = new LoggingService(updated.logChannelId);
|
context.logging = new LoggingService(updated.logChannelId);
|
||||||
|
|||||||
Reference in New Issue
Block a user