APRK01 commited on
Commit ·
5fb7488
1
Parent(s): c35213b
Premium Redesign: System Modules and Surgical Layout
Browse files- WSW/src/app/cracks/page.tsx +58 -36
- WSW/src/app/globals.css +8 -0
- WSW/src/app/page.tsx +50 -28
- WSW/src/app/scripts/page.tsx +58 -36
- WSW/src/app/sources/page.tsx +58 -36
- WSW/src/app/tools/page.tsx +58 -36
- WSW/src/app/vip/page.tsx +21 -7
- WSW/src/components/AnimatedRow.tsx +27 -17
- package-lock.json +96 -0
- package.json +1 -0
- src/commands/deleteDrop.js +3 -3
- src/commands/editDrop.js +3 -3
- src/commands/ticketStats.js +1 -1
- src/database.js +164 -104
- src/events/messageCreate.js +5 -5
- src/systems/drops.js +9 -8
- src/systems/logger.js +3 -3
- src/systems/massdrop.js +4 -3
- src/systems/tickets.js +11 -11
- src/systems/verification.js +7 -5
WSW/src/app/cracks/page.tsx
CHANGED
|
@@ -38,37 +38,59 @@ export default async function Cracks() {
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
-
<nav className="w-full md:w-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
</Link>
|
| 48 |
-
|
| 49 |
-
{[
|
| 50 |
-
{ id: 'sources', label: 'Sources', active: false },
|
| 51 |
-
{ id: 'cracks', label: 'Cracks', active: true },
|
| 52 |
-
{ id: 'scripts', label: 'Scripts', active: false },
|
| 53 |
-
{ id: 'tools', label: 'Tools', active: false },
|
| 54 |
-
].map((mod) => (
|
| 55 |
-
<Link
|
| 56 |
-
key={mod.id}
|
| 57 |
-
href={`/${mod.id}`}
|
| 58 |
-
className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
|
| 59 |
-
mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
|
| 60 |
-
}`}
|
| 61 |
-
>
|
| 62 |
-
<span className={`w-1.5 h-1.5 rounded-full transition-all ${
|
| 63 |
-
mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
|
| 64 |
-
}`} />
|
| 65 |
-
{mod.label}
|
| 66 |
</Link>
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
<div className="mt-
|
| 70 |
-
<Link href="/vip" className="
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</Link>
|
| 73 |
</div>
|
| 74 |
</nav>
|
|
@@ -79,13 +101,13 @@ export default async function Cracks() {
|
|
| 79 |
{/* Top Edge Decoration Removed */}
|
| 80 |
|
| 81 |
{/* Ledger Table Header (Desktop) */}
|
| 82 |
-
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-
|
| 83 |
-
<div className="col-span-2">
|
| 84 |
-
<div className="col-span-1">
|
| 85 |
-
<div className="col-span-4">
|
| 86 |
-
<div className="col-span-2">
|
| 87 |
-
<div className="col-span-2">
|
| 88 |
-
<div className="col-span-1 text-right">
|
| 89 |
</div>
|
| 90 |
|
| 91 |
{/* Ledger Content */}
|
|
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
+
<nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
|
| 42 |
+
|
| 43 |
+
<div className="mb-10 hidden md:block">
|
| 44 |
+
<Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
|
| 45 |
+
<span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
|
| 46 |
+
RETURN TO BASE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</Link>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div className="mb-6 flex flex-col gap-1">
|
| 51 |
+
<span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
|
| 52 |
+
<span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
|
| 56 |
+
{[
|
| 57 |
+
{ id: 'sources', label: 'Sources', active: false, index: '01' },
|
| 58 |
+
{ id: 'cracks', label: 'Cracks', active: true, index: '02' },
|
| 59 |
+
{ id: 'scripts', label: 'Scripts', active: false, index: '03' },
|
| 60 |
+
{ id: 'tools', label: 'Tools', active: false, index: '04' },
|
| 61 |
+
].map((mod) => (
|
| 62 |
+
<Link
|
| 63 |
+
key={mod.id}
|
| 64 |
+
href={`/${mod.id}`}
|
| 65 |
+
className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
|
| 66 |
+
mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
|
| 67 |
+
}`}
|
| 68 |
+
>
|
| 69 |
+
<div className="flex items-center gap-4">
|
| 70 |
+
<span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
|
| 71 |
+
{mod.index}
|
| 72 |
+
</span>
|
| 73 |
+
<span className="font-black text-[11px] tracking-widest uppercase italic">
|
| 74 |
+
{mod.label}
|
| 75 |
+
</span>
|
| 76 |
+
</div>
|
| 77 |
+
{mod.active && (
|
| 78 |
+
<div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
|
| 79 |
+
)}
|
| 80 |
+
{!mod.active && (
|
| 81 |
+
<span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
|
| 82 |
+
)}
|
| 83 |
+
</Link>
|
| 84 |
+
))}
|
| 85 |
+
</div>
|
| 86 |
|
| 87 |
+
<div className="mt-auto pt-12 hidden md:block">
|
| 88 |
+
<Link href="/vip" className="relative group block overflow-hidden">
|
| 89 |
+
<div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
|
| 90 |
+
<div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
|
| 91 |
+
<span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
|
| 92 |
+
<span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
|
| 93 |
+
</div>
|
| 94 |
</Link>
|
| 95 |
</div>
|
| 96 |
</nav>
|
|
|
|
| 101 |
{/* Top Edge Decoration Removed */}
|
| 102 |
|
| 103 |
{/* Ledger Table Header (Desktop) */}
|
| 104 |
+
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
|
| 105 |
+
<div className="col-span-2">TIMESTAMP</div>
|
| 106 |
+
<div className="col-span-1">REF</div>
|
| 107 |
+
<div className="col-span-4">IDENTIFIER</div>
|
| 108 |
+
<div className="col-span-2">LATENCY / AVAIL</div>
|
| 109 |
+
<div className="col-span-2">ENCRYPTION / STATUS</div>
|
| 110 |
+
<div className="col-span-1 text-right">ACTION</div>
|
| 111 |
</div>
|
| 112 |
|
| 113 |
{/* Ledger Content */}
|
WSW/src/app/globals.css
CHANGED
|
@@ -38,3 +38,11 @@ body {
|
|
| 38 |
animation: noise 8s steps(10) infinite;
|
| 39 |
z-index: 50;
|
| 40 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
animation: noise 8s steps(10) infinite;
|
| 39 |
z-index: 50;
|
| 40 |
}
|
| 41 |
+
|
| 42 |
+
.no-scrollbar::-webkit-scrollbar {
|
| 43 |
+
display: none;
|
| 44 |
+
}
|
| 45 |
+
.no-scrollbar {
|
| 46 |
+
-ms-overflow-style: none;
|
| 47 |
+
scrollbar-width: none;
|
| 48 |
+
}
|
WSW/src/app/page.tsx
CHANGED
|
@@ -30,38 +30,60 @@ export default function Home() {
|
|
| 30 |
</div>
|
| 31 |
|
| 32 |
{/* Right Side: Navigation Grid (4 columns) */}
|
| 33 |
-
<div className="lg:col-span-4 bg-black flex flex-col lg:h-full lg:overflow-y-auto">
|
| 34 |
|
| 35 |
-
<div className="p-
|
| 36 |
-
<
|
| 37 |
-
|
| 38 |
-
|
|
|
|
| 39 |
</div>
|
| 40 |
|
| 41 |
-
<div className="flex-1 flex flex-col
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
<
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
-
<Link href="/vip" className="flex-1
|
| 63 |
-
<
|
| 64 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
</Link>
|
| 66 |
</div>
|
| 67 |
|
|
|
|
| 30 |
</div>
|
| 31 |
|
| 32 |
{/* Right Side: Navigation Grid (4 columns) */}
|
| 33 |
+
<div className="lg:col-span-4 bg-black flex flex-col lg:h-full lg:overflow-y-auto border-t lg:border-t-0 border-zinc-900">
|
| 34 |
|
| 35 |
+
<div className="p-8 border-b border-zinc-900 shrink-0 bg-black sticky top-0 z-10">
|
| 36 |
+
<div className="flex flex-col gap-1">
|
| 37 |
+
<span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
|
| 38 |
+
<span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
|
| 39 |
+
</div>
|
| 40 |
</div>
|
| 41 |
|
| 42 |
+
<div className="flex-1 flex flex-col">
|
| 43 |
+
{[
|
| 44 |
+
{ id: 'sources', label: 'Sources', index: '01', desc: 'Resource Directory' },
|
| 45 |
+
{ id: 'cracks', label: 'Cracks', index: '02', desc: 'Bypass Protocols' },
|
| 46 |
+
{ id: 'scripts', label: 'Scripts', index: '03', desc: 'Automated Logic' },
|
| 47 |
+
{ id: 'tools', label: 'Tools', index: '04', desc: 'Utility Suite' },
|
| 48 |
+
].map((mod) => (
|
| 49 |
+
<Link
|
| 50 |
+
key={mod.id}
|
| 51 |
+
href={`/${mod.id}`}
|
| 52 |
+
className="flex-1 min-h-[100px] p-8 border-b border-zinc-900 flex flex-col justify-center gap-2 group hover:bg-white transition-all duration-500"
|
| 53 |
+
>
|
| 54 |
+
<div className="flex items-center justify-between">
|
| 55 |
+
<div className="flex items-center gap-4">
|
| 56 |
+
<span className="font-mono text-[10px] text-zinc-800 group-hover:text-black/30 transition-colors">
|
| 57 |
+
{mod.index}
|
| 58 |
+
</span>
|
| 59 |
+
<span className="font-black text-xl tracking-tighter uppercase italic group-hover:text-black transition-colors">
|
| 60 |
+
{mod.label}
|
| 61 |
+
</span>
|
| 62 |
+
</div>
|
| 63 |
+
<span className="font-mono text-zinc-800 group-hover:text-black transition-colors">→</span>
|
| 64 |
+
</div>
|
| 65 |
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase group-hover:text-black/60 transition-colors">
|
| 66 |
+
{mod.desc}
|
| 67 |
+
</p>
|
| 68 |
+
</Link>
|
| 69 |
+
))}
|
| 70 |
|
| 71 |
+
<Link href="/vip" className="flex-1 min-h-[120px] p-8 bg-zinc-950 flex flex-col justify-center gap-2 group hover:bg-white transition-all duration-500 relative overflow-hidden">
|
| 72 |
+
<div className="absolute top-0 left-0 w-2 h-full bg-white -translate-x-full group-hover:translate-x-0 transition-transform duration-500" />
|
| 73 |
+
<div className="flex items-center justify-between">
|
| 74 |
+
<div className="flex items-center gap-4">
|
| 75 |
+
<span className="font-mono text-[10px] text-zinc-800 group-hover:text-black/30 transition-colors">
|
| 76 |
+
EXP
|
| 77 |
+
</span>
|
| 78 |
+
<span className="font-black text-xl tracking-tighter uppercase text-white group-hover:text-black transition-colors italic">
|
| 79 |
+
VIP Access
|
| 80 |
+
</span>
|
| 81 |
+
</div>
|
| 82 |
+
<div className="w-2 h-2 bg-white rounded-full group-hover:bg-black transition-colors shadow-[0_0_10px_rgba(255,255,255,0.5)] group-hover:shadow-none animate-pulse" />
|
| 83 |
+
</div>
|
| 84 |
+
<p className="font-mono text-[9px] tracking-widest text-zinc-500 uppercase group-hover:text-black/60 transition-colors">
|
| 85 |
+
Clearance Elevation Required
|
| 86 |
+
</p>
|
| 87 |
</Link>
|
| 88 |
</div>
|
| 89 |
|
WSW/src/app/scripts/page.tsx
CHANGED
|
@@ -38,37 +38,59 @@ export default async function Scripts() {
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
-
<nav className="w-full md:w-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
</Link>
|
| 48 |
-
|
| 49 |
-
{[
|
| 50 |
-
{ id: 'sources', label: 'Sources', active: false },
|
| 51 |
-
{ id: 'cracks', label: 'Cracks', active: false },
|
| 52 |
-
{ id: 'scripts', label: 'Scripts', active: true },
|
| 53 |
-
{ id: 'tools', label: 'Tools', active: false },
|
| 54 |
-
].map((mod) => (
|
| 55 |
-
<Link
|
| 56 |
-
key={mod.id}
|
| 57 |
-
href={`/${mod.id}`}
|
| 58 |
-
className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
|
| 59 |
-
mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
|
| 60 |
-
}`}
|
| 61 |
-
>
|
| 62 |
-
<span className={`w-1.5 h-1.5 rounded-full transition-all ${
|
| 63 |
-
mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
|
| 64 |
-
}`} />
|
| 65 |
-
{mod.label}
|
| 66 |
</Link>
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
<div className="mt-
|
| 70 |
-
<Link href="/vip" className="
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</Link>
|
| 73 |
</div>
|
| 74 |
</nav>
|
|
@@ -79,13 +101,13 @@ export default async function Scripts() {
|
|
| 79 |
{/* Top Edge Decoration Removed */}
|
| 80 |
|
| 81 |
{/* Ledger Table Header (Desktop) */}
|
| 82 |
-
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-
|
| 83 |
-
<div className="col-span-2">
|
| 84 |
-
<div className="col-span-1">
|
| 85 |
-
<div className="col-span-4">
|
| 86 |
-
<div className="col-span-2">
|
| 87 |
-
<div className="col-span-2">
|
| 88 |
-
<div className="col-span-1 text-right">
|
| 89 |
</div>
|
| 90 |
|
| 91 |
{/* Ledger Content */}
|
|
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
+
<nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
|
| 42 |
+
|
| 43 |
+
<div className="mb-10 hidden md:block">
|
| 44 |
+
<Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
|
| 45 |
+
<span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
|
| 46 |
+
RETURN TO BASE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</Link>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div className="mb-6 flex flex-col gap-1">
|
| 51 |
+
<span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
|
| 52 |
+
<span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
|
| 56 |
+
{[
|
| 57 |
+
{ id: 'sources', label: 'Sources', active: false, index: '01' },
|
| 58 |
+
{ id: 'cracks', label: 'Cracks', active: false, index: '02' },
|
| 59 |
+
{ id: 'scripts', label: 'Scripts', active: true, index: '03' },
|
| 60 |
+
{ id: 'tools', label: 'Tools', active: false, index: '04' },
|
| 61 |
+
].map((mod) => (
|
| 62 |
+
<Link
|
| 63 |
+
key={mod.id}
|
| 64 |
+
href={`/${mod.id}`}
|
| 65 |
+
className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
|
| 66 |
+
mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
|
| 67 |
+
}`}
|
| 68 |
+
>
|
| 69 |
+
<div className="flex items-center gap-4">
|
| 70 |
+
<span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
|
| 71 |
+
{mod.index}
|
| 72 |
+
</span>
|
| 73 |
+
<span className="font-black text-[11px] tracking-widest uppercase italic">
|
| 74 |
+
{mod.label}
|
| 75 |
+
</span>
|
| 76 |
+
</div>
|
| 77 |
+
{mod.active && (
|
| 78 |
+
<div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
|
| 79 |
+
)}
|
| 80 |
+
{!mod.active && (
|
| 81 |
+
<span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
|
| 82 |
+
)}
|
| 83 |
+
</Link>
|
| 84 |
+
))}
|
| 85 |
+
</div>
|
| 86 |
|
| 87 |
+
<div className="mt-auto pt-12 hidden md:block">
|
| 88 |
+
<Link href="/vip" className="relative group block overflow-hidden">
|
| 89 |
+
<div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
|
| 90 |
+
<div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
|
| 91 |
+
<span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
|
| 92 |
+
<span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
|
| 93 |
+
</div>
|
| 94 |
</Link>
|
| 95 |
</div>
|
| 96 |
</nav>
|
|
|
|
| 101 |
{/* Top Edge Decoration Removed */}
|
| 102 |
|
| 103 |
{/* Ledger Table Header (Desktop) */}
|
| 104 |
+
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
|
| 105 |
+
<div className="col-span-2">TIMESTAMP</div>
|
| 106 |
+
<div className="col-span-1">REF</div>
|
| 107 |
+
<div className="col-span-4">IDENTIFIER</div>
|
| 108 |
+
<div className="col-span-2">LATENCY / AVAIL</div>
|
| 109 |
+
<div className="col-span-2">ENCRYPTION / STATUS</div>
|
| 110 |
+
<div className="col-span-1 text-right">ACTION</div>
|
| 111 |
</div>
|
| 112 |
|
| 113 |
{/* Ledger Content */}
|
WSW/src/app/sources/page.tsx
CHANGED
|
@@ -38,37 +38,59 @@ export default async function Sources() {
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
-
<nav className="w-full md:w-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
</Link>
|
| 48 |
-
|
| 49 |
-
{[
|
| 50 |
-
{ id: 'sources', label: 'Sources', active: true },
|
| 51 |
-
{ id: 'cracks', label: 'Cracks', active: false },
|
| 52 |
-
{ id: 'scripts', label: 'Scripts', active: false },
|
| 53 |
-
{ id: 'tools', label: 'Tools', active: false },
|
| 54 |
-
].map((mod) => (
|
| 55 |
-
<Link
|
| 56 |
-
key={mod.id}
|
| 57 |
-
href={`/${mod.id}`}
|
| 58 |
-
className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
|
| 59 |
-
mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
|
| 60 |
-
}`}
|
| 61 |
-
>
|
| 62 |
-
<span className={`w-1.5 h-1.5 rounded-full transition-all ${
|
| 63 |
-
mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
|
| 64 |
-
}`} />
|
| 65 |
-
{mod.label}
|
| 66 |
</Link>
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
<div className="mt-
|
| 70 |
-
<Link href="/vip" className="
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</Link>
|
| 73 |
</div>
|
| 74 |
</nav>
|
|
@@ -79,13 +101,13 @@ export default async function Sources() {
|
|
| 79 |
{/* Top Edge Decoration Removed */}
|
| 80 |
|
| 81 |
{/* Ledger Table Header (Desktop) */}
|
| 82 |
-
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-
|
| 83 |
-
<div className="col-span-2">
|
| 84 |
-
<div className="col-span-1">
|
| 85 |
-
<div className="col-span-4">
|
| 86 |
-
<div className="col-span-2">
|
| 87 |
-
<div className="col-span-2">
|
| 88 |
-
<div className="col-span-1 text-right">
|
| 89 |
</div>
|
| 90 |
|
| 91 |
{/* Ledger Content */}
|
|
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
+
<nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
|
| 42 |
+
|
| 43 |
+
<div className="mb-10 hidden md:block">
|
| 44 |
+
<Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
|
| 45 |
+
<span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
|
| 46 |
+
RETURN TO BASE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</Link>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div className="mb-6 flex flex-col gap-1">
|
| 51 |
+
<span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
|
| 52 |
+
<span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
|
| 56 |
+
{[
|
| 57 |
+
{ id: 'sources', label: 'Sources', active: true, index: '01' },
|
| 58 |
+
{ id: 'cracks', label: 'Cracks', active: false, index: '02' },
|
| 59 |
+
{ id: 'scripts', label: 'Scripts', active: false, index: '03' },
|
| 60 |
+
{ id: 'tools', label: 'Tools', active: false, index: '04' },
|
| 61 |
+
].map((mod) => (
|
| 62 |
+
<Link
|
| 63 |
+
key={mod.id}
|
| 64 |
+
href={`/${mod.id}`}
|
| 65 |
+
className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
|
| 66 |
+
mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
|
| 67 |
+
}`}
|
| 68 |
+
>
|
| 69 |
+
<div className="flex items-center gap-4">
|
| 70 |
+
<span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
|
| 71 |
+
{mod.index}
|
| 72 |
+
</span>
|
| 73 |
+
<span className="font-black text-[11px] tracking-widest uppercase italic">
|
| 74 |
+
{mod.label}
|
| 75 |
+
</span>
|
| 76 |
+
</div>
|
| 77 |
+
{mod.active && (
|
| 78 |
+
<div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
|
| 79 |
+
)}
|
| 80 |
+
{!mod.active && (
|
| 81 |
+
<span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
|
| 82 |
+
)}
|
| 83 |
+
</Link>
|
| 84 |
+
))}
|
| 85 |
+
</div>
|
| 86 |
|
| 87 |
+
<div className="mt-auto pt-12 hidden md:block">
|
| 88 |
+
<Link href="/vip" className="relative group block overflow-hidden">
|
| 89 |
+
<div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
|
| 90 |
+
<div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
|
| 91 |
+
<span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
|
| 92 |
+
<span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
|
| 93 |
+
</div>
|
| 94 |
</Link>
|
| 95 |
</div>
|
| 96 |
</nav>
|
|
|
|
| 101 |
{/* Top Edge Decoration Removed */}
|
| 102 |
|
| 103 |
{/* Ledger Table Header (Desktop) */}
|
| 104 |
+
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
|
| 105 |
+
<div className="col-span-2">TIMESTAMP</div>
|
| 106 |
+
<div className="col-span-1">REF</div>
|
| 107 |
+
<div className="col-span-4">IDENTIFIER</div>
|
| 108 |
+
<div className="col-span-2">LATENCY / AVAIL</div>
|
| 109 |
+
<div className="col-span-2">ENCRYPTION / STATUS</div>
|
| 110 |
+
<div className="col-span-1 text-right">ACTION</div>
|
| 111 |
</div>
|
| 112 |
|
| 113 |
{/* Ledger Content */}
|
WSW/src/app/tools/page.tsx
CHANGED
|
@@ -38,37 +38,59 @@ export default async function Tools() {
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
-
<nav className="w-full md:w-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
</Link>
|
| 48 |
-
|
| 49 |
-
{[
|
| 50 |
-
{ id: 'sources', label: 'Sources', active: false },
|
| 51 |
-
{ id: 'cracks', label: 'Cracks', active: false },
|
| 52 |
-
{ id: 'scripts', label: 'Scripts', active: false },
|
| 53 |
-
{ id: 'tools', label: 'Tools', active: true },
|
| 54 |
-
].map((mod) => (
|
| 55 |
-
<Link
|
| 56 |
-
key={mod.id}
|
| 57 |
-
href={`/${mod.id}`}
|
| 58 |
-
className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
|
| 59 |
-
mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
|
| 60 |
-
}`}
|
| 61 |
-
>
|
| 62 |
-
<span className={`w-1.5 h-1.5 rounded-full transition-all ${
|
| 63 |
-
mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
|
| 64 |
-
}`} />
|
| 65 |
-
{mod.label}
|
| 66 |
</Link>
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
<div className="mt-
|
| 70 |
-
<Link href="/vip" className="
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</Link>
|
| 73 |
</div>
|
| 74 |
</nav>
|
|
@@ -79,13 +101,13 @@ export default async function Tools() {
|
|
| 79 |
{/* Top Edge Decoration Removed */}
|
| 80 |
|
| 81 |
{/* Ledger Table Header (Desktop) */}
|
| 82 |
-
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-
|
| 83 |
-
<div className="col-span-2">
|
| 84 |
-
<div className="col-span-1">
|
| 85 |
-
<div className="col-span-4">
|
| 86 |
-
<div className="col-span-2">
|
| 87 |
-
<div className="col-span-2">
|
| 88 |
-
<div className="col-span-1 text-right">
|
| 89 |
</div>
|
| 90 |
|
| 91 |
{/* Ledger Content */}
|
|
|
|
| 38 |
<div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
|
| 39 |
|
| 40 |
{/* Module Sidebar */}
|
| 41 |
+
<nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
|
| 42 |
+
|
| 43 |
+
<div className="mb-10 hidden md:block">
|
| 44 |
+
<Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
|
| 45 |
+
<span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
|
| 46 |
+
RETURN TO BASE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</Link>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div className="mb-6 flex flex-col gap-1">
|
| 51 |
+
<span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
|
| 52 |
+
<span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
|
| 56 |
+
{[
|
| 57 |
+
{ id: 'sources', label: 'Sources', active: false, index: '01' },
|
| 58 |
+
{ id: 'cracks', label: 'Cracks', active: false, index: '02' },
|
| 59 |
+
{ id: 'scripts', label: 'Scripts', active: false, index: '03' },
|
| 60 |
+
{ id: 'tools', label: 'Tools', active: true, index: '04' },
|
| 61 |
+
].map((mod) => (
|
| 62 |
+
<Link
|
| 63 |
+
key={mod.id}
|
| 64 |
+
href={`/${mod.id}`}
|
| 65 |
+
className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
|
| 66 |
+
mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
|
| 67 |
+
}`}
|
| 68 |
+
>
|
| 69 |
+
<div className="flex items-center gap-4">
|
| 70 |
+
<span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
|
| 71 |
+
{mod.index}
|
| 72 |
+
</span>
|
| 73 |
+
<span className="font-black text-[11px] tracking-widest uppercase italic">
|
| 74 |
+
{mod.label}
|
| 75 |
+
</span>
|
| 76 |
+
</div>
|
| 77 |
+
{mod.active && (
|
| 78 |
+
<div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
|
| 79 |
+
)}
|
| 80 |
+
{!mod.active && (
|
| 81 |
+
<span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
|
| 82 |
+
)}
|
| 83 |
+
</Link>
|
| 84 |
+
))}
|
| 85 |
+
</div>
|
| 86 |
|
| 87 |
+
<div className="mt-auto pt-12 hidden md:block">
|
| 88 |
+
<Link href="/vip" className="relative group block overflow-hidden">
|
| 89 |
+
<div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
|
| 90 |
+
<div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
|
| 91 |
+
<span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
|
| 92 |
+
<span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
|
| 93 |
+
</div>
|
| 94 |
</Link>
|
| 95 |
</div>
|
| 96 |
</nav>
|
|
|
|
| 101 |
{/* Top Edge Decoration Removed */}
|
| 102 |
|
| 103 |
{/* Ledger Table Header (Desktop) */}
|
| 104 |
+
<div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
|
| 105 |
+
<div className="col-span-2">TIMESTAMP</div>
|
| 106 |
+
<div className="col-span-1">REF</div>
|
| 107 |
+
<div className="col-span-4">IDENTIFIER</div>
|
| 108 |
+
<div className="col-span-2">LATENCY / AVAIL</div>
|
| 109 |
+
<div className="col-span-2">ENCRYPTION / STATUS</div>
|
| 110 |
+
<div className="col-span-1 text-right">ACTION</div>
|
| 111 |
</div>
|
| 112 |
|
| 113 |
{/* Ledger Content */}
|
WSW/src/app/vip/page.tsx
CHANGED
|
@@ -90,13 +90,27 @@ export default async function VIPPage() {
|
|
| 90 |
<p className="font-mono text-[10px] text-zinc-400 tracking-widest leading-relaxed mb-6">
|
| 91 |
Your security clearance is elevated. You now have unrestricted access to all highly classified directory index protocols.
|
| 92 |
</p>
|
| 93 |
-
<div className="grid grid-cols-2 gap-4">
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
</div>
|
| 101 |
</div>
|
| 102 |
) : (
|
|
|
|
| 90 |
<p className="font-mono text-[10px] text-zinc-400 tracking-widest leading-relaxed mb-6">
|
| 91 |
Your security clearance is elevated. You now have unrestricted access to all highly classified directory index protocols.
|
| 92 |
</p>
|
| 93 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
| 94 |
+
{[
|
| 95 |
+
{ id: 'sources', label: 'Sources', index: '01' },
|
| 96 |
+
{ id: 'cracks', label: 'Cracks', index: '02' },
|
| 97 |
+
{ id: 'scripts', label: 'Scripts', index: '03' },
|
| 98 |
+
{ id: 'tools', label: 'Tools', index: '04' },
|
| 99 |
+
].map((mod) => (
|
| 100 |
+
<Link
|
| 101 |
+
key={mod.id}
|
| 102 |
+
href={`/${mod.id}`}
|
| 103 |
+
className="p-6 border border-zinc-800 hover:border-white transition-all bg-zinc-900/50 group flex flex-col gap-2"
|
| 104 |
+
>
|
| 105 |
+
<div className="flex justify-between items-center">
|
| 106 |
+
<span className="font-mono text-[9px] text-zinc-600 group-hover:text-zinc-400 transition-colors uppercase tracking-widest">{mod.index}</span>
|
| 107 |
+
<span className="text-zinc-700 group-hover:text-white transition-colors">→</span>
|
| 108 |
+
</div>
|
| 109 |
+
<span className="font-black text-sm tracking-widest text-white uppercase italic group-hover:pl-2 transition-all">
|
| 110 |
+
{mod.label}
|
| 111 |
+
</span>
|
| 112 |
+
</Link>
|
| 113 |
+
))}
|
| 114 |
</div>
|
| 115 |
</div>
|
| 116 |
) : (
|
WSW/src/components/AnimatedRow.tsx
CHANGED
|
@@ -22,38 +22,48 @@ export default function AnimatedRow({
|
|
| 22 |
delay: index * 0.05,
|
| 23 |
ease: [0.22, 1, 0.36, 1],
|
| 24 |
}}
|
| 25 |
-
className="grid grid-cols-1 md:grid-cols-12 gap-2 md:gap-4 px-6 md:px-8 py-
|
| 26 |
>
|
| 27 |
-
<div className="col-span-1
|
| 28 |
-
<div className="md:col-span-2 group-hover:text-zinc-400 transition-colors">
|
| 29 |
{new Date(drop.published_at).toLocaleDateString('en-GB', {
|
| 30 |
day: '2-digit',
|
| 31 |
month: '2-digit',
|
| 32 |
year: 'numeric',
|
| 33 |
})}
|
| 34 |
</div>
|
| 35 |
-
<div className="md:col-span-1 text-zinc-
|
| 36 |
-
{prefix}
|
| 37 |
</div>
|
| 38 |
</div>
|
| 39 |
|
| 40 |
-
<div className="col-span-1 md:col-span-4 text-white font-
|
| 41 |
-
{drop.title
|
| 42 |
</div>
|
| 43 |
|
| 44 |
-
<div className="col-span-1 md:col-span-5 flex md:contents justify-between items-center
|
| 45 |
-
<div className="md:col-span-2 text-zinc-
|
| 46 |
-
{drop.file_url ?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</div>
|
| 48 |
|
| 49 |
<div
|
| 50 |
className={`md:col-span-2 ${
|
| 51 |
drop.status === 'checked'
|
| 52 |
-
? 'text-white
|
| 53 |
-
: 'text-zinc-
|
| 54 |
} transition-all duration-300`}
|
| 55 |
>
|
| 56 |
-
|
| 57 |
</div>
|
| 58 |
|
| 59 |
<div className="md:col-span-1 text-right">
|
|
@@ -63,13 +73,13 @@ export default function AnimatedRow({
|
|
| 63 |
href={drop.file_url}
|
| 64 |
target="_blank"
|
| 65 |
rel="noopener noreferrer"
|
| 66 |
-
className="
|
| 67 |
>
|
| 68 |
-
|
| 69 |
</a>
|
| 70 |
) : (
|
| 71 |
-
<span className="text-zinc-
|
| 72 |
-
|
| 73 |
</span>
|
| 74 |
)
|
| 75 |
)}
|
|
|
|
| 22 |
delay: index * 0.05,
|
| 23 |
ease: [0.22, 1, 0.36, 1],
|
| 24 |
}}
|
| 25 |
+
className="grid grid-cols-1 md:grid-cols-12 gap-2 md:gap-4 px-6 md:px-8 py-5 border-b border-zinc-900/50 hover:bg-white/[0.02] transition-all duration-300 font-mono text-[10px] tracking-widest group items-center relative overflow-hidden"
|
| 26 |
>
|
| 27 |
+
<div className="col-span-1 md:col-span-3 flex md:contents justify-between text-zinc-600">
|
| 28 |
+
<div className="md:col-span-2 group-hover:text-zinc-400 transition-colors font-mono tabular-nums">
|
| 29 |
{new Date(drop.published_at).toLocaleDateString('en-GB', {
|
| 30 |
day: '2-digit',
|
| 31 |
month: '2-digit',
|
| 32 |
year: 'numeric',
|
| 33 |
})}
|
| 34 |
</div>
|
| 35 |
+
<div className="md:col-span-1 text-zinc-700 group-hover:text-zinc-500 transition-colors">
|
| 36 |
+
{prefix}{drop.id.toString().padStart(3, '0')}
|
| 37 |
</div>
|
| 38 |
</div>
|
| 39 |
|
| 40 |
+
<div className="col-span-1 md:col-span-4 text-white font-black tracking-tighter uppercase text-xs italic group-hover:pl-2 transition-all duration-300">
|
| 41 |
+
{drop.title}
|
| 42 |
</div>
|
| 43 |
|
| 44 |
+
<div className="col-span-1 md:col-span-5 flex md:contents justify-between items-center text-[9px] tracking-[0.2em]">
|
| 45 |
+
<div className="md:col-span-2 text-zinc-500 group-hover:text-zinc-300 transition-colors flex items-center gap-2">
|
| 46 |
+
{drop.file_url ? (
|
| 47 |
+
<>
|
| 48 |
+
<span className="w-1 h-1 bg-green-500 rounded-full shadow-[0_0_5px_rgba(34,197,94,0.5)]" />
|
| 49 |
+
AVAILABLE
|
| 50 |
+
</>
|
| 51 |
+
) : (
|
| 52 |
+
<>
|
| 53 |
+
<span className="w-1 h-1 bg-zinc-800 rounded-full" />
|
| 54 |
+
N/A
|
| 55 |
+
</>
|
| 56 |
+
)}
|
| 57 |
</div>
|
| 58 |
|
| 59 |
<div
|
| 60 |
className={`md:col-span-2 ${
|
| 61 |
drop.status === 'checked'
|
| 62 |
+
? 'text-white'
|
| 63 |
+
: 'text-zinc-600 animate-pulse'
|
| 64 |
} transition-all duration-300`}
|
| 65 |
>
|
| 66 |
+
{drop.status === 'checked' ? '• SECURE' : '• UNVERIFIED'}
|
| 67 |
</div>
|
| 68 |
|
| 69 |
<div className="md:col-span-1 text-right">
|
|
|
|
| 73 |
href={drop.file_url}
|
| 74 |
target="_blank"
|
| 75 |
rel="noopener noreferrer"
|
| 76 |
+
className="bg-white text-black px-3 py-1.5 font-black text-[9px] tracking-widest hover:bg-zinc-200 transition-colors"
|
| 77 |
>
|
| 78 |
+
FETCH
|
| 79 |
</a>
|
| 80 |
) : (
|
| 81 |
+
<span className="text-zinc-800 border border-zinc-900 px-3 py-1.5 cursor-not-allowed">
|
| 82 |
+
LOCKED
|
| 83 |
</span>
|
| 84 |
)
|
| 85 |
)}
|
package-lock.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
| 9 |
"version": "1.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"@octokit/rest": "^22.0.1",
|
|
|
|
| 12 |
"better-sqlite3": "^11.7.0",
|
| 13 |
"discord.js": "^14.16.0",
|
| 14 |
"dotenv": "^16.4.0",
|
|
@@ -334,6 +335,86 @@
|
|
| 334 |
"npm": ">=7.0.0"
|
| 335 |
}
|
| 336 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
"node_modules/@types/node": {
|
| 338 |
"version": "25.3.3",
|
| 339 |
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
|
|
@@ -343,6 +424,12 @@
|
|
| 343 |
"undici-types": "~7.18.0"
|
| 344 |
}
|
| 345 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
"node_modules/@types/ws": {
|
| 347 |
"version": "8.18.1",
|
| 348 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
@@ -588,6 +675,15 @@
|
|
| 588 |
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
| 589 |
"license": "MIT"
|
| 590 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 591 |
"node_modules/ieee754": {
|
| 592 |
"version": "1.2.1",
|
| 593 |
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
|
|
|
| 9 |
"version": "1.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"@octokit/rest": "^22.0.1",
|
| 12 |
+
"@supabase/supabase-js": "^2.99.3",
|
| 13 |
"better-sqlite3": "^11.7.0",
|
| 14 |
"discord.js": "^14.16.0",
|
| 15 |
"dotenv": "^16.4.0",
|
|
|
|
| 335 |
"npm": ">=7.0.0"
|
| 336 |
}
|
| 337 |
},
|
| 338 |
+
"node_modules/@supabase/auth-js": {
|
| 339 |
+
"version": "2.99.3",
|
| 340 |
+
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.3.tgz",
|
| 341 |
+
"integrity": "sha512-vMEVLA1kGGYd/kdsJSwtjiFUZM1nGfrz2DWmgMBZtocV48qL+L2+4QpIkueXyBEumMQZFEyhz57i/5zGHjvdBw==",
|
| 342 |
+
"license": "MIT",
|
| 343 |
+
"dependencies": {
|
| 344 |
+
"tslib": "2.8.1"
|
| 345 |
+
},
|
| 346 |
+
"engines": {
|
| 347 |
+
"node": ">=20.0.0"
|
| 348 |
+
}
|
| 349 |
+
},
|
| 350 |
+
"node_modules/@supabase/functions-js": {
|
| 351 |
+
"version": "2.99.3",
|
| 352 |
+
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.3.tgz",
|
| 353 |
+
"integrity": "sha512-6tk2zrcBkzKaaBXPOG5nshn30uJNFGOH9LxOnE8i850eQmsX+jVm7vql9kTPyvUzEHwU4zdjSOkXS9M+9ukMVA==",
|
| 354 |
+
"license": "MIT",
|
| 355 |
+
"dependencies": {
|
| 356 |
+
"tslib": "2.8.1"
|
| 357 |
+
},
|
| 358 |
+
"engines": {
|
| 359 |
+
"node": ">=20.0.0"
|
| 360 |
+
}
|
| 361 |
+
},
|
| 362 |
+
"node_modules/@supabase/postgrest-js": {
|
| 363 |
+
"version": "2.99.3",
|
| 364 |
+
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.3.tgz",
|
| 365 |
+
"integrity": "sha512-8HxEf+zNycj7Z8+ONhhlu+7J7Ha+L6weyCtdEeK2mN5OWJbh6n4LPU4iuJ5UlCvvNnbSXMoutY7piITEEAgl2g==",
|
| 366 |
+
"license": "MIT",
|
| 367 |
+
"dependencies": {
|
| 368 |
+
"tslib": "2.8.1"
|
| 369 |
+
},
|
| 370 |
+
"engines": {
|
| 371 |
+
"node": ">=20.0.0"
|
| 372 |
+
}
|
| 373 |
+
},
|
| 374 |
+
"node_modules/@supabase/realtime-js": {
|
| 375 |
+
"version": "2.99.3",
|
| 376 |
+
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.3.tgz",
|
| 377 |
+
"integrity": "sha512-c1azgZ2nZPczbY5k5u5iFrk1InpxN81IvNE+UBAkjrBz3yc5ALLJNkeTQwbJZT4PZBuYXEzqYGLMuh9fdTtTMg==",
|
| 378 |
+
"license": "MIT",
|
| 379 |
+
"dependencies": {
|
| 380 |
+
"@types/phoenix": "^1.6.6",
|
| 381 |
+
"@types/ws": "^8.18.1",
|
| 382 |
+
"tslib": "2.8.1",
|
| 383 |
+
"ws": "^8.18.2"
|
| 384 |
+
},
|
| 385 |
+
"engines": {
|
| 386 |
+
"node": ">=20.0.0"
|
| 387 |
+
}
|
| 388 |
+
},
|
| 389 |
+
"node_modules/@supabase/storage-js": {
|
| 390 |
+
"version": "2.99.3",
|
| 391 |
+
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.3.tgz",
|
| 392 |
+
"integrity": "sha512-lOfIm4hInNcd8x0i1LWphnLKxec42wwbjs+vhaVAvR801Vda0UAMbTooUY6gfqgQb8v29GofqKuQMMTAsl6w/w==",
|
| 393 |
+
"license": "MIT",
|
| 394 |
+
"dependencies": {
|
| 395 |
+
"iceberg-js": "^0.8.1",
|
| 396 |
+
"tslib": "2.8.1"
|
| 397 |
+
},
|
| 398 |
+
"engines": {
|
| 399 |
+
"node": ">=20.0.0"
|
| 400 |
+
}
|
| 401 |
+
},
|
| 402 |
+
"node_modules/@supabase/supabase-js": {
|
| 403 |
+
"version": "2.99.3",
|
| 404 |
+
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.3.tgz",
|
| 405 |
+
"integrity": "sha512-GuPbzoEaI51AkLw9VGhLNvnzw4PHbS3p8j2/JlvLeZNQMKwZw4aEYQIDBRtFwL5Nv7/275n9m4DHtakY8nCvgg==",
|
| 406 |
+
"license": "MIT",
|
| 407 |
+
"dependencies": {
|
| 408 |
+
"@supabase/auth-js": "2.99.3",
|
| 409 |
+
"@supabase/functions-js": "2.99.3",
|
| 410 |
+
"@supabase/postgrest-js": "2.99.3",
|
| 411 |
+
"@supabase/realtime-js": "2.99.3",
|
| 412 |
+
"@supabase/storage-js": "2.99.3"
|
| 413 |
+
},
|
| 414 |
+
"engines": {
|
| 415 |
+
"node": ">=20.0.0"
|
| 416 |
+
}
|
| 417 |
+
},
|
| 418 |
"node_modules/@types/node": {
|
| 419 |
"version": "25.3.3",
|
| 420 |
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
|
|
|
|
| 424 |
"undici-types": "~7.18.0"
|
| 425 |
}
|
| 426 |
},
|
| 427 |
+
"node_modules/@types/phoenix": {
|
| 428 |
+
"version": "1.6.7",
|
| 429 |
+
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
|
| 430 |
+
"integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
|
| 431 |
+
"license": "MIT"
|
| 432 |
+
},
|
| 433 |
"node_modules/@types/ws": {
|
| 434 |
"version": "8.18.1",
|
| 435 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
|
|
| 675 |
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
| 676 |
"license": "MIT"
|
| 677 |
},
|
| 678 |
+
"node_modules/iceberg-js": {
|
| 679 |
+
"version": "0.8.1",
|
| 680 |
+
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
|
| 681 |
+
"integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
|
| 682 |
+
"license": "MIT",
|
| 683 |
+
"engines": {
|
| 684 |
+
"node": ">=20.0.0"
|
| 685 |
+
}
|
| 686 |
+
},
|
| 687 |
"node_modules/ieee754": {
|
| 688 |
"version": "1.2.1",
|
| 689 |
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
package.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
},
|
| 9 |
"dependencies": {
|
| 10 |
"@octokit/rest": "^22.0.1",
|
|
|
|
| 11 |
"better-sqlite3": "^11.7.0",
|
| 12 |
"discord.js": "^14.16.0",
|
| 13 |
"dotenv": "^16.4.0",
|
|
|
|
| 8 |
},
|
| 9 |
"dependencies": {
|
| 10 |
"@octokit/rest": "^22.0.1",
|
| 11 |
+
"@supabase/supabase-js": "^2.99.3",
|
| 12 |
"better-sqlite3": "^11.7.0",
|
| 13 |
"discord.js": "^14.16.0",
|
| 14 |
"dotenv": "^16.4.0",
|
src/commands/deleteDrop.js
CHANGED
|
@@ -15,14 +15,14 @@ module.exports = {
|
|
| 15 |
const id = parseInt(args[0]);
|
| 16 |
if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
|
| 17 |
|
| 18 |
-
const drop = stmts.getWebDrop
|
| 19 |
if (!drop) {
|
| 20 |
return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
|
| 21 |
}
|
| 22 |
|
| 23 |
try {
|
| 24 |
-
// 1. Delete
|
| 25 |
-
stmts.deleteWebDrop
|
| 26 |
|
| 27 |
// 2. Send DELETE request to Website Backend API
|
| 28 |
const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
|
|
|
|
| 15 |
const id = parseInt(args[0]);
|
| 16 |
if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
|
| 17 |
|
| 18 |
+
const drop = await stmts.getWebDrop(id);
|
| 19 |
if (!drop) {
|
| 20 |
return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
|
| 21 |
}
|
| 22 |
|
| 23 |
try {
|
| 24 |
+
// 1. Delete from Supabase
|
| 25 |
+
await stmts.deleteWebDrop(id);
|
| 26 |
|
| 27 |
// 2. Send DELETE request to Website Backend API
|
| 28 |
const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
|
src/commands/editDrop.js
CHANGED
|
@@ -20,7 +20,7 @@ module.exports = {
|
|
| 20 |
|
| 21 |
if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
|
| 22 |
|
| 23 |
-
const drop = stmts.getWebDrop
|
| 24 |
if (!drop) {
|
| 25 |
return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
|
| 26 |
}
|
|
@@ -31,9 +31,9 @@ module.exports = {
|
|
| 31 |
}
|
| 32 |
|
| 33 |
try {
|
| 34 |
-
// 1. Update
|
| 35 |
const { db } = require('../database');
|
| 36 |
-
db.
|
| 37 |
|
| 38 |
// 2. Send update request to Website Backend API
|
| 39 |
const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
|
|
|
|
| 20 |
|
| 21 |
if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
|
| 22 |
|
| 23 |
+
const drop = await stmts.getWebDrop(id);
|
| 24 |
if (!drop) {
|
| 25 |
return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
|
| 26 |
}
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
try {
|
| 34 |
+
// 1. Update Supabase
|
| 35 |
const { db } = require('../database');
|
| 36 |
+
await db.from('web_drops').update({ [property]: newValue }).eq('id', id);
|
| 37 |
|
| 38 |
// 2. Send update request to Website Backend API
|
| 39 |
const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
|
src/commands/ticketStats.js
CHANGED
|
@@ -5,7 +5,7 @@ const { stmts } = require('../database');
|
|
| 5 |
module.exports = {
|
| 6 |
name: 'ticket stats',
|
| 7 |
async execute(client, message) {
|
| 8 |
-
const stats = stmts.ticketStats
|
| 9 |
|
| 10 |
const embed = createEmbed({
|
| 11 |
title: '🎫 Ticket Statistics',
|
|
|
|
| 5 |
module.exports = {
|
| 6 |
name: 'ticket stats',
|
| 7 |
async execute(client, message) {
|
| 8 |
+
const stats = await stmts.ticketStats();
|
| 9 |
|
| 10 |
const embed = createEmbed({
|
| 11 |
title: '🎫 Ticket Statistics',
|
src/database.js
CHANGED
|
@@ -1,119 +1,179 @@
|
|
| 1 |
-
const
|
| 2 |
-
const Database = require('better-sqlite3');
|
| 3 |
-
const fs = require('fs');
|
| 4 |
|
| 5 |
-
const
|
| 6 |
-
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
// Enable WAL mode for better concurrency
|
| 11 |
-
db.pragma('journal_mode = WAL');
|
| 12 |
-
|
| 13 |
-
// ── Schema ────────────────────────────────────────────────────
|
| 14 |
-
db.exec(`
|
| 15 |
-
CREATE TABLE IF NOT EXISTS tickets (
|
| 16 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 17 |
-
user_id TEXT NOT NULL,
|
| 18 |
-
username TEXT NOT NULL,
|
| 19 |
-
channel_id TEXT,
|
| 20 |
-
status TEXT DEFAULT 'open',
|
| 21 |
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 22 |
-
closed_at DATETIME
|
| 23 |
-
);
|
| 24 |
-
|
| 25 |
-
CREATE TABLE IF NOT EXISTS verification_log (
|
| 26 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 27 |
-
user_id TEXT NOT NULL,
|
| 28 |
-
username TEXT NOT NULL,
|
| 29 |
-
action TEXT NOT NULL,
|
| 30 |
-
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 31 |
-
);
|
| 32 |
-
|
| 33 |
-
CREATE TABLE IF NOT EXISTS bot_state (
|
| 34 |
-
key TEXT PRIMARY KEY,
|
| 35 |
-
value TEXT NOT NULL
|
| 36 |
-
);
|
| 37 |
-
|
| 38 |
-
CREATE TABLE IF NOT EXISTS whitelist (
|
| 39 |
-
user_id TEXT PRIMARY KEY,
|
| 40 |
-
max_drops INTEGER DEFAULT 3,
|
| 41 |
-
added_by TEXT NOT NULL,
|
| 42 |
-
added_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 43 |
-
);
|
| 44 |
-
|
| 45 |
-
CREATE TABLE IF NOT EXISTS drop_log (
|
| 46 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 47 |
-
user_id TEXT NOT NULL,
|
| 48 |
-
title TEXT NOT NULL,
|
| 49 |
-
channel_id TEXT NOT NULL,
|
| 50 |
-
dropped_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 51 |
-
);
|
| 52 |
-
|
| 53 |
-
CREATE TABLE IF NOT EXISTS web_drops (
|
| 54 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 55 |
-
user_id TEXT NOT NULL,
|
| 56 |
-
category TEXT NOT NULL DEFAULT 'sources',
|
| 57 |
-
title TEXT NOT NULL,
|
| 58 |
-
description TEXT,
|
| 59 |
-
status TEXT,
|
| 60 |
-
is_external INTEGER DEFAULT 0,
|
| 61 |
-
asset_id TEXT,
|
| 62 |
-
file_url TEXT,
|
| 63 |
-
image_url TEXT,
|
| 64 |
-
published_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 65 |
-
);
|
| 66 |
-
|
| 67 |
-
CREATE TABLE IF NOT EXISTS vip_users (
|
| 68 |
-
discord_id TEXT PRIMARY KEY,
|
| 69 |
-
purchased_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 70 |
-
expires_at DATETIME
|
| 71 |
-
);
|
| 72 |
-
`);
|
| 73 |
-
|
| 74 |
-
// ── Prepared Statements ───────────────────────────────────────
|
| 75 |
const stmts = {
|
| 76 |
// Tickets
|
| 77 |
-
createTicket:
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
// Verification log
|
| 92 |
-
logVerification:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
// Bot state (key-value)
|
| 95 |
-
setState:
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
// Whitelist
|
| 100 |
-
addWhitelist:
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
// Drops array
|
| 106 |
-
logDrop:
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
addWebDrop:
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
};
|
| 118 |
|
| 119 |
-
module.exports = { db, stmts };
|
|
|
|
| 1 |
+
const { createClient } = require('@supabase/supabase-js');
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
const supabase = createClient(
|
| 4 |
+
process.env.SUPABASE_URL,
|
| 5 |
+
process.env.SUPABASE_SERVICE_ROLE_KEY
|
| 6 |
+
);
|
| 7 |
|
| 8 |
+
// ── Prepared Statements (Wrappers for Supabase) ─────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
const stmts = {
|
| 10 |
// Tickets
|
| 11 |
+
createTicket: async (userId, username, channelId) => {
|
| 12 |
+
return await supabase
|
| 13 |
+
.from('tickets')
|
| 14 |
+
.insert({ user_id: userId, username, channel_id: channelId });
|
| 15 |
+
},
|
| 16 |
+
closeTicket: async (status, channelId) => {
|
| 17 |
+
return await supabase
|
| 18 |
+
.from('tickets')
|
| 19 |
+
.update({ status, closed_at: new Date().toISOString() })
|
| 20 |
+
.eq('channel_id', channelId);
|
| 21 |
+
},
|
| 22 |
+
getTicket: async (channelId) => {
|
| 23 |
+
const { data } = await supabase
|
| 24 |
+
.from('tickets')
|
| 25 |
+
.select('*')
|
| 26 |
+
.eq('channel_id', channelId)
|
| 27 |
+
.single();
|
| 28 |
+
return data;
|
| 29 |
+
},
|
| 30 |
+
getOpenTickets: async (status) => {
|
| 31 |
+
const { data } = await supabase
|
| 32 |
+
.from('tickets')
|
| 33 |
+
.select('*')
|
| 34 |
+
.eq('status', status);
|
| 35 |
+
return data || [];
|
| 36 |
+
},
|
| 37 |
+
getUserTicket: async (userId, status) => {
|
| 38 |
+
const { data } = await supabase
|
| 39 |
+
.from('tickets')
|
| 40 |
+
.select('*')
|
| 41 |
+
.eq('user_id', userId)
|
| 42 |
+
.eq('status', status)
|
| 43 |
+
.single();
|
| 44 |
+
return data;
|
| 45 |
+
},
|
| 46 |
+
ticketStats: async () => {
|
| 47 |
+
const { data, error } = await supabase.rpc('get_ticket_stats');
|
| 48 |
+
if (data) return data;
|
| 49 |
+
|
| 50 |
+
// Manual fallback if RPC not setup
|
| 51 |
+
const { count: total } = await supabase.from('tickets').select('*', { count: 'exact', head: true });
|
| 52 |
+
const { count: open } = await supabase.from('tickets').select('*', { count: 'exact', head: true }).eq('status', 'open');
|
| 53 |
+
const { count: closed } = await supabase.from('tickets').select('*', { count: 'exact', head: true }).eq('status', 'closed');
|
| 54 |
+
const { count: deleted } = await supabase.from('tickets').select('*', { count: 'exact', head: true }).eq('status', 'deleted');
|
| 55 |
+
|
| 56 |
+
return { total, open_count: open, closed_count: closed, deleted_count: deleted };
|
| 57 |
+
},
|
| 58 |
|
| 59 |
// Verification log
|
| 60 |
+
logVerification: async (userId, username, action) => {
|
| 61 |
+
return await supabase
|
| 62 |
+
.from('verification_log')
|
| 63 |
+
.insert({ user_id: userId, username, action });
|
| 64 |
+
},
|
| 65 |
|
| 66 |
// Bot state (key-value)
|
| 67 |
+
setState: async (key, value) => {
|
| 68 |
+
return await supabase
|
| 69 |
+
.from('bot_state')
|
| 70 |
+
.upsert({ key, value });
|
| 71 |
+
},
|
| 72 |
+
getState: async (key) => {
|
| 73 |
+
const { data } = await supabase
|
| 74 |
+
.from('bot_state')
|
| 75 |
+
.select('value')
|
| 76 |
+
.eq('key', key)
|
| 77 |
+
.single();
|
| 78 |
+
return data ? data.value : null;
|
| 79 |
+
},
|
| 80 |
+
delState: async (key) => {
|
| 81 |
+
return await supabase
|
| 82 |
+
.from('bot_state')
|
| 83 |
+
.delete()
|
| 84 |
+
.eq('key', key);
|
| 85 |
+
},
|
| 86 |
|
| 87 |
// Whitelist
|
| 88 |
+
addWhitelist: async (userId, maxDrops, addedBy) => {
|
| 89 |
+
return await supabase
|
| 90 |
+
.from('whitelist')
|
| 91 |
+
.upsert({ user_id: userId, max_drops: maxDrops, added_by: addedBy });
|
| 92 |
+
},
|
| 93 |
+
removeWhitelist: async (userId) => {
|
| 94 |
+
return await supabase
|
| 95 |
+
.from('whitelist')
|
| 96 |
+
.delete()
|
| 97 |
+
.eq('user_id', userId);
|
| 98 |
+
},
|
| 99 |
+
getWhitelist: async (userId) => {
|
| 100 |
+
const { data } = await supabase
|
| 101 |
+
.from('whitelist')
|
| 102 |
+
.select('*')
|
| 103 |
+
.eq('user_id', userId)
|
| 104 |
+
.single();
|
| 105 |
+
return data;
|
| 106 |
+
},
|
| 107 |
+
getAllWhitelist: async () => {
|
| 108 |
+
const { data } = await supabase
|
| 109 |
+
.from('whitelist')
|
| 110 |
+
.select('*');
|
| 111 |
+
return data || [];
|
| 112 |
+
},
|
| 113 |
|
| 114 |
// Drops array
|
| 115 |
+
logDrop: async (userId, title, channelId) => {
|
| 116 |
+
return await supabase
|
| 117 |
+
.from('drop_log')
|
| 118 |
+
.insert({ user_id: userId, title, channel_id: channelId });
|
| 119 |
+
},
|
| 120 |
+
getDropCount24h: async (userId) => {
|
| 121 |
+
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
| 122 |
+
const { count } = await supabase
|
| 123 |
+
.from('drop_log')
|
| 124 |
+
.select('*', { count: 'exact', head: true })
|
| 125 |
+
.eq('user_id', userId)
|
| 126 |
+
.gt('dropped_at', twentyFourHoursAgo);
|
| 127 |
+
return { count: count || 0 };
|
| 128 |
+
},
|
| 129 |
+
getLastDrop: async (userId) => {
|
| 130 |
+
const { data } = await supabase
|
| 131 |
+
.from('drop_log')
|
| 132 |
+
.select('dropped_at')
|
| 133 |
+
.eq('user_id', userId)
|
| 134 |
+
.order('dropped_at', { ascending: false })
|
| 135 |
+
.limit(1)
|
| 136 |
+
.single();
|
| 137 |
+
return data;
|
| 138 |
+
},
|
| 139 |
|
| 140 |
+
addWebDrop: async (userId, category, title, description, status, isExternal, assetId, fileUrl, imageUrl) => {
|
| 141 |
+
return await supabase
|
| 142 |
+
.from('web_drops')
|
| 143 |
+
.insert({
|
| 144 |
+
user_id: userId,
|
| 145 |
+
category,
|
| 146 |
+
title,
|
| 147 |
+
description,
|
| 148 |
+
status,
|
| 149 |
+
is_external: isExternal,
|
| 150 |
+
asset_id: assetId,
|
| 151 |
+
file_url: fileUrl,
|
| 152 |
+
image_url: imageUrl
|
| 153 |
+
});
|
| 154 |
+
},
|
| 155 |
+
getWebDrop: async (id) => {
|
| 156 |
+
const { data } = await supabase
|
| 157 |
+
.from('web_drops')
|
| 158 |
+
.select('*')
|
| 159 |
+
.eq('id', id)
|
| 160 |
+
.single();
|
| 161 |
+
return data;
|
| 162 |
+
},
|
| 163 |
+
deleteWebDrop: async (id) => {
|
| 164 |
+
return await supabase
|
| 165 |
+
.from('web_drops')
|
| 166 |
+
.delete()
|
| 167 |
+
.eq('id', id);
|
| 168 |
+
},
|
| 169 |
+
getAllWebDrops: async () => {
|
| 170 |
+
const { data } = await supabase
|
| 171 |
+
.from('web_drops')
|
| 172 |
+
.select('*')
|
| 173 |
+
.order('published_at', { ascending: false })
|
| 174 |
+
.limit(50);
|
| 175 |
+
return data || [];
|
| 176 |
+
},
|
| 177 |
};
|
| 178 |
|
| 179 |
+
module.exports = { db: supabase, stmts };
|
src/events/messageCreate.js
CHANGED
|
@@ -77,7 +77,7 @@ module.exports = {
|
|
| 77 |
// ── Whitelisted user: only allow "drop" ──
|
| 78 |
if (userId !== OWNER_ID) {
|
| 79 |
if (content === 'drop' || content === 'linkdrop') {
|
| 80 |
-
const check = canDrop(userId);
|
| 81 |
if (!check.allowed) {
|
| 82 |
if (check.reason === 'not_whitelisted') {
|
| 83 |
return; // Silently ignore non-whitelisted users
|
|
@@ -128,7 +128,7 @@ module.exports = {
|
|
| 128 |
if (!targetId || !/^\d{17,20}$/.test(targetId)) {
|
| 129 |
return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `whitelist <user_id> [limit]`\nExample: `whitelist 123456789 5`')] });
|
| 130 |
}
|
| 131 |
-
stmts.addWhitelist
|
| 132 |
const user = await client.users.fetch(targetId).catch(() => null);
|
| 133 |
const name = user ? `**${user.tag}**` : `ID \`${targetId}\``;
|
| 134 |
return message.reply({ embeds: [successEmbed('✅ Whitelisted', `${name} can now use \`drop\` (**${limit}** per day)`)] });
|
|
@@ -137,7 +137,7 @@ module.exports = {
|
|
| 137 |
if (content.startsWith('unwhitelist ')) {
|
| 138 |
const targetId = content.split(' ')[1]?.trim();
|
| 139 |
if (!targetId) return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `unwhitelist <user_id>`')] });
|
| 140 |
-
stmts.removeWhitelist
|
| 141 |
return message.reply({ embeds: [successEmbed('✅ Removed', `User \`${targetId}\` removed from whitelist`)] });
|
| 142 |
}
|
| 143 |
|
|
@@ -171,7 +171,7 @@ module.exports = {
|
|
| 171 |
}
|
| 172 |
|
| 173 |
if (content === 'whitelist') {
|
| 174 |
-
const all = stmts.getAllWhitelist
|
| 175 |
if (all.length === 0) {
|
| 176 |
return message.reply({ embeds: [infoEmbed('Whitelist', 'No users whitelisted.\n\nUse `whitelist <user_id>` to add.')] });
|
| 177 |
}
|
|
@@ -179,7 +179,7 @@ module.exports = {
|
|
| 179 |
for (const w of all) {
|
| 180 |
const user = await client.users.fetch(w.user_id).catch(() => null);
|
| 181 |
const name = user ? user.tag : 'Unknown';
|
| 182 |
-
const drops = stmts.getDropCount24h
|
| 183 |
lines.push(`• **${name}** (\`${w.user_id}\`) — ${drops.count}/${w.max_drops} drops today`);
|
| 184 |
}
|
| 185 |
return message.reply({ embeds: [infoEmbed('📋 Whitelist', lines.join('\n'))] });
|
|
|
|
| 77 |
// ── Whitelisted user: only allow "drop" ──
|
| 78 |
if (userId !== OWNER_ID) {
|
| 79 |
if (content === 'drop' || content === 'linkdrop') {
|
| 80 |
+
const check = await canDrop(userId);
|
| 81 |
if (!check.allowed) {
|
| 82 |
if (check.reason === 'not_whitelisted') {
|
| 83 |
return; // Silently ignore non-whitelisted users
|
|
|
|
| 128 |
if (!targetId || !/^\d{17,20}$/.test(targetId)) {
|
| 129 |
return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `whitelist <user_id> [limit]`\nExample: `whitelist 123456789 5`')] });
|
| 130 |
}
|
| 131 |
+
await stmts.addWhitelist(targetId, limit, userId);
|
| 132 |
const user = await client.users.fetch(targetId).catch(() => null);
|
| 133 |
const name = user ? `**${user.tag}**` : `ID \`${targetId}\``;
|
| 134 |
return message.reply({ embeds: [successEmbed('✅ Whitelisted', `${name} can now use \`drop\` (**${limit}** per day)`)] });
|
|
|
|
| 137 |
if (content.startsWith('unwhitelist ')) {
|
| 138 |
const targetId = content.split(' ')[1]?.trim();
|
| 139 |
if (!targetId) return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `unwhitelist <user_id>`')] });
|
| 140 |
+
await stmts.removeWhitelist(targetId);
|
| 141 |
return message.reply({ embeds: [successEmbed('✅ Removed', `User \`${targetId}\` removed from whitelist`)] });
|
| 142 |
}
|
| 143 |
|
|
|
|
| 171 |
}
|
| 172 |
|
| 173 |
if (content === 'whitelist') {
|
| 174 |
+
const all = await stmts.getAllWhitelist();
|
| 175 |
if (all.length === 0) {
|
| 176 |
return message.reply({ embeds: [infoEmbed('Whitelist', 'No users whitelisted.\n\nUse `whitelist <user_id>` to add.')] });
|
| 177 |
}
|
|
|
|
| 179 |
for (const w of all) {
|
| 180 |
const user = await client.users.fetch(w.user_id).catch(() => null);
|
| 181 |
const name = user ? user.tag : 'Unknown';
|
| 182 |
+
const drops = await stmts.getDropCount24h(w.user_id);
|
| 183 |
lines.push(`• **${name}** (\`${w.user_id}\`) — ${drops.count}/${w.max_drops} drops today`);
|
| 184 |
}
|
| 185 |
return message.reply({ embeds: [infoEmbed('📋 Whitelist', lines.join('\n'))] });
|
src/systems/drops.js
CHANGED
|
@@ -22,17 +22,17 @@ const STEPS = ['category', 'title', 'file', 'warnings', 'status', 'about', 'imag
|
|
| 22 |
* Check if a user can drop (owner = unlimited, whitelisted = custom limit).
|
| 23 |
* Returns { allowed: boolean, remaining?: number, resetIn?: string, limit?: number }
|
| 24 |
*/
|
| 25 |
-
function canDrop(userId) {
|
| 26 |
if (userId === OWNER_ID) return { allowed: true, remaining: Infinity };
|
| 27 |
|
| 28 |
-
const wl = stmts.getWhitelist
|
| 29 |
if (!wl) return { allowed: false, reason: 'not_whitelisted' };
|
| 30 |
|
| 31 |
const limit = wl.max_drops;
|
| 32 |
-
const { count } = stmts.getDropCount24h
|
| 33 |
if (count >= limit) {
|
| 34 |
-
const last = stmts.getLastDrop
|
| 35 |
-
const resetTime = new Date(last.dropped_at
|
| 36 |
resetTime.setHours(resetTime.getHours() + 24);
|
| 37 |
const diff = resetTime - new Date();
|
| 38 |
const hours = Math.floor(diff / 3600000);
|
|
@@ -444,8 +444,8 @@ async function handleDropButton(interaction) {
|
|
| 444 |
}).catch(() => {});
|
| 445 |
} catch(e) {}
|
| 446 |
|
| 447 |
-
// 4. Save to
|
| 448 |
-
stmts.addWebDrop
|
| 449 |
userId,
|
| 450 |
session.category,
|
| 451 |
payload.title,
|
|
@@ -458,7 +458,7 @@ async function handleDropButton(interaction) {
|
|
| 458 |
);
|
| 459 |
|
| 460 |
// Log drop for Discord rate limits
|
| 461 |
-
stmts.logDrop
|
| 462 |
|
| 463 |
activeSessions.delete(userId);
|
| 464 |
|
|
@@ -575,4 +575,5 @@ module.exports = {
|
|
| 575 |
handleDownloadButton,
|
| 576 |
buildDropEmbed,
|
| 577 |
canDrop,
|
|
|
|
| 578 |
};
|
|
|
|
| 22 |
* Check if a user can drop (owner = unlimited, whitelisted = custom limit).
|
| 23 |
* Returns { allowed: boolean, remaining?: number, resetIn?: string, limit?: number }
|
| 24 |
*/
|
| 25 |
+
async function canDrop(userId) {
|
| 26 |
if (userId === OWNER_ID) return { allowed: true, remaining: Infinity };
|
| 27 |
|
| 28 |
+
const wl = await stmts.getWhitelist(userId);
|
| 29 |
if (!wl) return { allowed: false, reason: 'not_whitelisted' };
|
| 30 |
|
| 31 |
const limit = wl.max_drops;
|
| 32 |
+
const { count } = await stmts.getDropCount24h(userId);
|
| 33 |
if (count >= limit) {
|
| 34 |
+
const last = await stmts.getLastDrop(userId);
|
| 35 |
+
const resetTime = new Date(last.dropped_at);
|
| 36 |
resetTime.setHours(resetTime.getHours() + 24);
|
| 37 |
const diff = resetTime - new Date();
|
| 38 |
const hours = Math.floor(diff / 3600000);
|
|
|
|
| 444 |
}).catch(() => {});
|
| 445 |
} catch(e) {}
|
| 446 |
|
| 447 |
+
// 4. Save to Supabase Web Tracking
|
| 448 |
+
await stmts.addWebDrop(
|
| 449 |
userId,
|
| 450 |
session.category,
|
| 451 |
payload.title,
|
|
|
|
| 458 |
);
|
| 459 |
|
| 460 |
// Log drop for Discord rate limits
|
| 461 |
+
await stmts.logDrop(userId, `[${session.category.toUpperCase()}] ${session.title}`, 'website');
|
| 462 |
|
| 463 |
activeSessions.delete(userId);
|
| 464 |
|
|
|
|
| 575 |
handleDownloadButton,
|
| 576 |
buildDropEmbed,
|
| 577 |
canDrop,
|
| 578 |
+
activeSessions,
|
| 579 |
};
|
src/systems/logger.js
CHANGED
|
@@ -7,10 +7,10 @@ const { Colors } = require('../config');
|
|
| 7 |
*/
|
| 8 |
async function log(client, { title, description, fields = [], color = Colors.MUTED }) {
|
| 9 |
try {
|
| 10 |
-
const row = stmts.getState
|
| 11 |
if (!row) return;
|
| 12 |
|
| 13 |
-
const channelId = row
|
| 14 |
const channel = await client.channels.fetch(channelId).catch(() => null);
|
| 15 |
if (!channel) return;
|
| 16 |
|
|
@@ -23,7 +23,7 @@ async function log(client, { title, description, fields = [], color = Colors.MUT
|
|
| 23 |
|
| 24 |
// Convenience wrappers
|
| 25 |
async function logVerification(client, user, action) {
|
| 26 |
-
stmts.logVerification
|
| 27 |
await log(client, {
|
| 28 |
title: '🔐 Verification',
|
| 29 |
description: `**${user.tag}** (${user.id})`,
|
|
|
|
| 7 |
*/
|
| 8 |
async function log(client, { title, description, fields = [], color = Colors.MUTED }) {
|
| 9 |
try {
|
| 10 |
+
const row = await stmts.getState('channel_staff-logs');
|
| 11 |
if (!row) return;
|
| 12 |
|
| 13 |
+
const channelId = row;
|
| 14 |
const channel = await client.channels.fetch(channelId).catch(() => null);
|
| 15 |
if (!channel) return;
|
| 16 |
|
|
|
|
| 23 |
|
| 24 |
// Convenience wrappers
|
| 25 |
async function logVerification(client, user, action) {
|
| 26 |
+
await stmts.logVerification(user.id, user.tag, action);
|
| 27 |
await log(client, {
|
| 28 |
title: '🔐 Verification',
|
| 29 |
description: `**${user.tag}** (${user.id})`,
|
src/systems/massdrop.js
CHANGED
|
@@ -328,9 +328,10 @@ async function handleMassDropMessage(message) {
|
|
| 328 |
}).catch(() => {});
|
| 329 |
} catch(e) {}
|
| 330 |
|
| 331 |
-
// 4. Save to
|
| 332 |
-
stmts.addWebDrop
|
| 333 |
userId,
|
|
|
|
| 334 |
payload.title,
|
| 335 |
payload.description,
|
| 336 |
payload.status,
|
|
@@ -341,7 +342,7 @@ async function handleMassDropMessage(message) {
|
|
| 341 |
);
|
| 342 |
|
| 343 |
successCount++;
|
| 344 |
-
stmts.logDrop
|
| 345 |
|
| 346 |
} catch (pushErr) {
|
| 347 |
console.error(`[Mass Drop Deploy Error] ${fileConf.title}:`, pushErr);
|
|
|
|
| 328 |
}).catch(() => {});
|
| 329 |
} catch(e) {}
|
| 330 |
|
| 331 |
+
// 4. Save to Supabase Web Tracking
|
| 332 |
+
await stmts.addWebDrop(
|
| 333 |
userId,
|
| 334 |
+
'sources', // Defaulting to sources for mass drop for now, or use from config
|
| 335 |
payload.title,
|
| 336 |
payload.description,
|
| 337 |
payload.status,
|
|
|
|
| 342 |
);
|
| 343 |
|
| 344 |
successCount++;
|
| 345 |
+
await stmts.logDrop(userId, fileConf.title, 'website-mass');
|
| 346 |
|
| 347 |
} catch (pushErr) {
|
| 348 |
console.error(`[Mass Drop Deploy Error] ${fileConf.title}:`, pushErr);
|
src/systems/tickets.js
CHANGED
|
@@ -14,10 +14,10 @@ const { logTicket } = require('./logger');
|
|
| 14 |
* Send the ticket embed with a "Create a Ticket" button to 🎫・open-ticket channel.
|
| 15 |
*/
|
| 16 |
async function sendTicketEmbed(client) {
|
| 17 |
-
const row = stmts.getState
|
| 18 |
if (!row) throw new Error('Ticket channel not found in bot state.');
|
| 19 |
|
| 20 |
-
const channel = await client.channels.fetch(row
|
| 21 |
if (!channel) throw new Error('Could not fetch ticket channel.');
|
| 22 |
|
| 23 |
const embed = createEmbed({
|
|
@@ -46,8 +46,8 @@ async function sendTicketEmbed(client) {
|
|
| 46 |
|
| 47 |
const msg = await channel.send({ embeds: [embed], components: [actionRow] });
|
| 48 |
|
| 49 |
-
stmts.setState
|
| 50 |
-
stmts.setState
|
| 51 |
|
| 52 |
return msg;
|
| 53 |
}
|
|
@@ -57,7 +57,7 @@ async function sendTicketEmbed(client) {
|
|
| 57 |
*/
|
| 58 |
async function createTicket(guild, user, client) {
|
| 59 |
// Check if user already has an open ticket
|
| 60 |
-
const existing = stmts.getUserTicket
|
| 61 |
if (existing) return null;
|
| 62 |
|
| 63 |
const staffRole = guild.roles.cache.find(r => r.name === '@@ Staff');
|
|
@@ -85,7 +85,7 @@ async function createTicket(guild, user, client) {
|
|
| 85 |
});
|
| 86 |
|
| 87 |
// Save to database
|
| 88 |
-
stmts.createTicket
|
| 89 |
|
| 90 |
// Send welcome embed with action buttons
|
| 91 |
const embed = createEmbed({
|
|
@@ -172,7 +172,7 @@ async function handleTicketButton(interaction, client) {
|
|
| 172 |
|
| 173 |
if (!['ticket_close', 'ticket_delete', 'ticket_transcript'].includes(customId)) return false;
|
| 174 |
|
| 175 |
-
const ticket = stmts.getTicket
|
| 176 |
if (!ticket) {
|
| 177 |
await interaction.reply({ content: '❌ This is not a ticket channel.', ephemeral: true });
|
| 178 |
return true;
|
|
@@ -198,13 +198,13 @@ async function handleTicketButton(interaction, client) {
|
|
| 198 |
}
|
| 199 |
|
| 200 |
if (customId === 'ticket_close') {
|
| 201 |
-
stmts.closeTicket
|
| 202 |
|
| 203 |
// Save transcript to ticket-logs
|
| 204 |
const transcript = await generateTranscript(channel);
|
| 205 |
-
const logsRow = stmts.getState
|
| 206 |
if (logsRow) {
|
| 207 |
-
const logsChannel = await client.channels.fetch(logsRow
|
| 208 |
if (logsChannel) {
|
| 209 |
const embed = createEmbed({
|
| 210 |
title: '📂 Ticket Closed',
|
|
@@ -233,7 +233,7 @@ async function handleTicketButton(interaction, client) {
|
|
| 233 |
}
|
| 234 |
|
| 235 |
if (customId === 'ticket_delete') {
|
| 236 |
-
stmts.closeTicket
|
| 237 |
await logTicket(client, { user: { tag: ticket.username, id: ticket.user_id }, action: 'deleted', channelName: channel.name });
|
| 238 |
await interaction.reply({ content: '🗑️ Deleting ticket...' });
|
| 239 |
setTimeout(() => channel.delete().catch(() => { }), 1000);
|
|
|
|
| 14 |
* Send the ticket embed with a "Create a Ticket" button to 🎫・open-ticket channel.
|
| 15 |
*/
|
| 16 |
async function sendTicketEmbed(client) {
|
| 17 |
+
const row = await stmts.getState('channel_🎫・open-ticket');
|
| 18 |
if (!row) throw new Error('Ticket channel not found in bot state.');
|
| 19 |
|
| 20 |
+
const channel = await client.channels.fetch(row);
|
| 21 |
if (!channel) throw new Error('Could not fetch ticket channel.');
|
| 22 |
|
| 23 |
const embed = createEmbed({
|
|
|
|
| 46 |
|
| 47 |
const msg = await channel.send({ embeds: [embed], components: [actionRow] });
|
| 48 |
|
| 49 |
+
await stmts.setState('ticket_message_id', msg.id);
|
| 50 |
+
await stmts.setState('ticket_channel_id', channel.id);
|
| 51 |
|
| 52 |
return msg;
|
| 53 |
}
|
|
|
|
| 57 |
*/
|
| 58 |
async function createTicket(guild, user, client) {
|
| 59 |
// Check if user already has an open ticket
|
| 60 |
+
const existing = await stmts.getUserTicket(user.id, 'open');
|
| 61 |
if (existing) return null;
|
| 62 |
|
| 63 |
const staffRole = guild.roles.cache.find(r => r.name === '@@ Staff');
|
|
|
|
| 85 |
});
|
| 86 |
|
| 87 |
// Save to database
|
| 88 |
+
await stmts.createTicket(user.id, user.tag, ticketChannel.id);
|
| 89 |
|
| 90 |
// Send welcome embed with action buttons
|
| 91 |
const embed = createEmbed({
|
|
|
|
| 172 |
|
| 173 |
if (!['ticket_close', 'ticket_delete', 'ticket_transcript'].includes(customId)) return false;
|
| 174 |
|
| 175 |
+
const ticket = await stmts.getTicket(channel.id);
|
| 176 |
if (!ticket) {
|
| 177 |
await interaction.reply({ content: '❌ This is not a ticket channel.', ephemeral: true });
|
| 178 |
return true;
|
|
|
|
| 198 |
}
|
| 199 |
|
| 200 |
if (customId === 'ticket_close') {
|
| 201 |
+
await stmts.closeTicket('closed', channel.id);
|
| 202 |
|
| 203 |
// Save transcript to ticket-logs
|
| 204 |
const transcript = await generateTranscript(channel);
|
| 205 |
+
const logsRow = await stmts.getState('channel_📂・ticket-logs');
|
| 206 |
if (logsRow) {
|
| 207 |
+
const logsChannel = await client.channels.fetch(logsRow).catch(() => null);
|
| 208 |
if (logsChannel) {
|
| 209 |
const embed = createEmbed({
|
| 210 |
title: '📂 Ticket Closed',
|
|
|
|
| 233 |
}
|
| 234 |
|
| 235 |
if (customId === 'ticket_delete') {
|
| 236 |
+
await stmts.closeTicket('deleted', channel.id);
|
| 237 |
await logTicket(client, { user: { tag: ticket.username, id: ticket.user_id }, action: 'deleted', channelName: channel.name });
|
| 238 |
await interaction.reply({ content: '🗑️ Deleting ticket...' });
|
| 239 |
setTimeout(() => channel.delete().catch(() => { }), 1000);
|
src/systems/verification.js
CHANGED
|
@@ -7,7 +7,7 @@ const { logVerification } = require('./logger');
|
|
| 7 |
* Send the verification embed to the ✅・verify channel.
|
| 8 |
*/
|
| 9 |
async function sendVerificationEmbed(client) {
|
| 10 |
-
const row = stmts.getState
|
| 11 |
if (!row) throw new Error('Verify channel not found in bot state.');
|
| 12 |
|
| 13 |
const channel = await client.channels.fetch(row.value);
|
|
@@ -32,8 +32,8 @@ async function sendVerificationEmbed(client) {
|
|
| 32 |
await msg.react('✅');
|
| 33 |
|
| 34 |
// Store the message ID so we can listen for reactions
|
| 35 |
-
stmts.setState
|
| 36 |
-
stmts.setState
|
| 37 |
|
| 38 |
return msg;
|
| 39 |
}
|
|
@@ -49,7 +49,8 @@ async function handleVerifyReaction(reaction, user, client) {
|
|
| 49 |
const isBotMessage = reaction.message.author?.id === client.user.id;
|
| 50 |
|
| 51 |
// Also check stored message ID as fallback
|
| 52 |
-
const
|
|
|
|
| 53 |
const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
|
| 54 |
|
| 55 |
if (!isVerifyChannel && !isStoredMsg) return false;
|
|
@@ -76,7 +77,8 @@ async function handleVerifyReactionRemove(reaction, user, client) {
|
|
| 76 |
const isVerifyChannel = channel.name === '✅・verify';
|
| 77 |
const isBotMessage = reaction.message.author?.id === client.user.id;
|
| 78 |
|
| 79 |
-
const
|
|
|
|
| 80 |
const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
|
| 81 |
|
| 82 |
if (!isVerifyChannel && !isStoredMsg) return false;
|
|
|
|
| 7 |
* Send the verification embed to the ✅・verify channel.
|
| 8 |
*/
|
| 9 |
async function sendVerificationEmbed(client) {
|
| 10 |
+
const row = await stmts.getState('channel_✅・verify');
|
| 11 |
if (!row) throw new Error('Verify channel not found in bot state.');
|
| 12 |
|
| 13 |
const channel = await client.channels.fetch(row.value);
|
|
|
|
| 32 |
await msg.react('✅');
|
| 33 |
|
| 34 |
// Store the message ID so we can listen for reactions
|
| 35 |
+
await stmts.setState('verify_message_id', msg.id);
|
| 36 |
+
await stmts.setState('verify_channel_id', channel.id);
|
| 37 |
|
| 38 |
return msg;
|
| 39 |
}
|
|
|
|
| 49 |
const isBotMessage = reaction.message.author?.id === client.user.id;
|
| 50 |
|
| 51 |
// Also check stored message ID as fallback
|
| 52 |
+
const stateVal = await stmts.getState('verify_message_id');
|
| 53 |
+
const verifyMsgId = stateVal;
|
| 54 |
const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
|
| 55 |
|
| 56 |
if (!isVerifyChannel && !isStoredMsg) return false;
|
|
|
|
| 77 |
const isVerifyChannel = channel.name === '✅・verify';
|
| 78 |
const isBotMessage = reaction.message.author?.id === client.user.id;
|
| 79 |
|
| 80 |
+
const stateVal = await stmts.getState('verify_message_id');
|
| 81 |
+
const verifyMsgId = stateVal;
|
| 82 |
const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
|
| 83 |
|
| 84 |
if (!isVerifyChannel && !isStoredMsg) return false;
|