Huge refactor, submission system addition & security improvements. +Implementation of moderation cmds.

This commit is contained in:
unknown
2026-05-22 21:46:06 +02:00
parent 12a0035699
commit 2129081599
32 changed files with 3426 additions and 106 deletions

View File

@@ -0,0 +1,92 @@
<script>
import { detectLanguage } from '../lib/lang.js'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
let { src, rawUrl = '', fileName = '' } = $props()
let text = $state('')
let loading = $state(true)
let lang = $derived(detectLanguage(fileName))
$effect(() => {
fetch(src)
.then(r => r.text())
.then(t => {
text = t
loading = false
if (lang) {
requestAnimationFrame(() => {
const block = document.querySelector('.code-viewer code')
if (block) hljs.highlightElement(block)
})
}
})
.catch(() => {
text = 'Failed to load code content.'
loading = false
})
})
</script>
<div class="code-viewer">
{#if fileName || rawUrl}
<div class="header">
{#if fileName}<span class="label">{fileName}</span>{/if}
{#if rawUrl}<a class="raw-btn" href={rawUrl} target="_blank">[ Raw ]</a>{/if}
</div>
{/if}
{#if loading}
<p>Loading code...</p>
{:else}
<pre><code class={lang ? `language-${lang}` : ''}>{text}</code></pre>
{/if}
</div>
<style>
.code-viewer {
max-width: 1000px;
margin: 24px auto;
padding: 16px;
background: var(--retro-panel);
border: 3px solid var(--retro-border);
box-shadow: 6px 6px 0px var(--retro-shadow);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid var(--retro-border);
}
.label {
font-family: 'Press Start 2P', cursive;
font-size: 0.6rem;
color: var(--retro-green);
}
.raw-btn {
font-family: 'Press Start 2P', cursive;
font-size: 0.5rem;
padding: 6px 10px;
border: 2px solid var(--retro-border);
background: var(--retro-panel);
color: var(--retro-fg);
text-decoration: none;
box-shadow: 2px 2px 0px rgba(0,0,0,0.15);
}
.raw-btn:hover {
background: var(--retro-green);
color: #fff;
}
pre {
margin: 0;
overflow-x: auto;
background: #1e1e1e;
padding: 12px;
border: 2px solid var(--retro-border);
}
code {
font-family: 'Courier New', Courier, monospace;
font-size: 0.9rem;
}
</style>

View File

@@ -0,0 +1,98 @@
<script>
let { src, downloadUrl, file } = $props()
let html = $state('')
let loading = $state(true)
let error = $state('')
$effect(() => {
fetch(src)
.then(r => r.arrayBuffer())
.then(buf => import('mammoth').then(m => m.default.convertToHtml({ arrayBuffer: buf })))
.then(result => {
html = result.value
loading = false
})
.catch(() => {
error = 'Failed to render DOCX.'
loading = false
})
})
</script>
<div class="docx-viewer">
<div class="header">
<span class="label">[ DOCX ]</span>
<a class="raw-btn" href={downloadUrl} download={file.name}>Download</a>
</div>
{#if loading}
<p>Loading DOCX...</p>
{:else if error}
<p class="error">{error}</p>
<a class="btn" href={downloadUrl} download={file.name}>Download</a>
{:else}
<div class="docx-content">{@html html}</div>
{/if}
</div>
<style>
.docx-viewer {
max-width: 900px;
margin: 24px auto;
padding: 24px;
background: var(--retro-panel);
border: 3px solid var(--retro-border);
box-shadow: 6px 6px 0px var(--retro-shadow);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid var(--retro-border);
}
.label {
font-family: 'Press Start 2P', cursive;
font-size: 0.6rem;
color: var(--retro-green);
}
.raw-btn {
font-family: 'Press Start 2P', cursive;
font-size: 0.5rem;
padding: 6px 10px;
border: 2px solid var(--retro-border);
background: var(--retro-panel);
color: var(--retro-fg);
text-decoration: none;
box-shadow: 2px 2px 0px rgba(0,0,0,0.15);
}
.raw-btn:hover {
background: var(--retro-green);
color: #fff;
}
.docx-content {
font-size: 1rem;
line-height: 1.6;
}
.docx-content :global(p) { margin: 0.8em 0; }
.docx-content :global(table) { border-collapse: collapse; width: 100%; }
.docx-content :global(td), .docx-content :global(th) { border: 1px solid #ccc; padding: 6px; }
.error { color: var(--retro-danger); }
.btn {
display: inline-block;
font-family: 'Press Start 2P', cursive;
font-size: 0.55rem;
padding: 10px 14px;
border: 3px solid var(--retro-border);
background: var(--retro-panel);
color: var(--retro-fg);
text-decoration: none;
text-align: center;
box-shadow: 3px 3px 0px rgba(0,0,0,0.15);
margin-top: 8px;
}
.btn:hover {
background: var(--retro-green);
color: #fff;
}
</style>

View File

@@ -1,9 +1,14 @@
<script>
let { src, name } = $props()
let failed = $state(false)
</script>
<div class="image-viewer">
<img {src} alt={name} decoding="async" loading="eager" />
{#if failed}
<div class="image-error">[ Failed to load image ]</div>
{:else}
<img {src} alt={name} decoding="async" loading="eager" onerror={() => failed = true} />
{/if}
</div>
<style>
@@ -19,4 +24,10 @@
box-shadow: 6px 6px 0px var(--retro-shadow);
image-rendering: auto;
}
.image-error {
color: var(--retro-danger);
font-family: var(--retro-font, monospace);
padding: 24px;
text-align: center;
}
</style>

View File

@@ -1,12 +1,17 @@
<script>
import { fileUrl } from '../lib/api.js'
import { fileUrl, rawUrl } from '../lib/api.js'
import { detectLanguage } from '../lib/lang.js'
import ImageViewer from './ImageViewer.svelte'
import VideoPlayer from './VideoPlayer.svelte'
import AudioPlayer from './AudioPlayer.svelte'
import MarkdownRenderer from './MarkdownRenderer.svelte'
import TextViewer from './TextViewer.svelte'
import CodeViewer from './CodeViewer.svelte'
import DocumentCard from './DocumentCard.svelte'
import ExecutableWarning from './ExecutableWarning.svelte'
import SensitiveWarning from './SensitiveWarning.svelte'
import PdfViewer from './PdfViewer.svelte'
import DocxViewer from './DocxViewer.svelte'
let { files, cxid, password = '' } = $props()
@@ -17,7 +22,11 @@
if (flags & 4) return 'audio'
if (flags & 8) return 'markdown'
if (flags & 16) return 'text'
if (flags & 32) {
return file.mime === 'application/pdf' ? 'pdf' : 'docx'
}
if (flags & 64 || flags & 128) return 'dangerous'
if (flags & 512) return 'sensitive'
return 'document'
}
</script>
@@ -39,9 +48,19 @@
{:else if viewer === 'markdown'}
<MarkdownRenderer src={fileUrl(cxid, file.idx, false, password)} />
{:else if viewer === 'text'}
<TextViewer src={fileUrl(cxid, file.idx, false, password)} />
{#if detectLanguage(file.name)}
<CodeViewer src={fileUrl(cxid, file.idx, false, password)} rawUrl={rawUrl(cxid, file.idx, password)} fileName={file.name} />
{:else}
<TextViewer src={fileUrl(cxid, file.idx, false, password)} rawUrl={rawUrl(cxid, file.idx, password)} fileName={file.name} />
{/if}
{:else if viewer === 'pdf'}
<PdfViewer src={fileUrl(cxid, file.idx, false, password)} />
{:else if viewer === 'docx'}
<DocxViewer src={fileUrl(cxid, file.idx, false, password)} downloadUrl={fileUrl(cxid, file.idx, true, password)} {file} />
{:else if viewer === 'dangerous'}
<ExecutableWarning {file} downloadUrl={fileUrl(cxid, file.idx, true, password)} />
{:else if viewer === 'sensitive'}
<SensitiveWarning {file} downloadUrl={fileUrl(cxid, file.idx, true, password)} />
{:else}
<DocumentCard {file} downloadUrl={fileUrl(cxid, file.idx, true, password)} />
{/if}

View File

@@ -0,0 +1,23 @@
<script>
let { src } = $props()
</script>
<div class="pdf-viewer">
<embed src={src} type="application/pdf" />
</div>
<style>
.pdf-viewer {
max-width: 1000px;
margin: 24px auto;
padding: 16px;
background: var(--retro-panel);
border: 3px solid var(--retro-border);
box-shadow: 6px 6px 0px var(--retro-shadow);
}
embed {
width: 100%;
height: 80vh;
border: 2px solid var(--retro-border);
}
</style>

View File

@@ -0,0 +1,56 @@
<script>
import { formatSize } from '../lib/api.js'
let { file, downloadUrl } = $props()
</script>
<div class="warning-card">
<div class="badge">[ Sensitive Data ]</div>
<p class="name">{file.name}</p>
<p class="meta">{file.mime}{formatSize(file.size)}</p>
<p class="notice">
This file may contain sensitive data. Be careful when handling it.
</p>
<a class="btn" href={downloadUrl} download={file.name}>Download</a>
</div>
<style>
.warning-card {
max-width: 600px;
margin: 24px auto;
padding: 24px;
background: #fffdf5;
border: 3px solid #c78000;
box-shadow: 6px 6px 0px rgba(199,128,0,0.15);
text-align: center;
}
.badge {
font-family: 'Press Start 2P', cursive;
font-size: 0.6rem;
color: #c78000;
margin-bottom: 12px;
}
.name {
font-family: 'Press Start 2P', cursive;
font-size: 0.7rem;
word-break: break-all;
}
.meta {
font-size: 0.9rem;
color: #666;
margin: 8px 0;
}
.notice {
font-size: 1rem;
color: #555;
margin: 16px 0;
}
.btn {
border-color: #c78000;
color: #c78000;
}
.btn:hover {
background: #c78000;
color: #fff;
}
</style>

View File

@@ -1,5 +1,5 @@
<script>
let { src } = $props()
let { src, rawUrl = '', fileName = '' } = $props()
let text = $state('')
let loading = $state(true)
@@ -18,6 +18,12 @@
</script>
<div class="text-viewer">
{#if fileName || rawUrl}
<div class="header">
{#if fileName}<span class="label">{fileName}</span>{/if}
{#if rawUrl}<a class="raw-btn" href={rawUrl} target="_blank">[ Raw ]</a>{/if}
</div>
{/if}
{#if loading}
<p>Loading text...</p>
{:else}
@@ -34,6 +40,33 @@
border: 3px solid var(--retro-border);
box-shadow: 6px 6px 0px var(--retro-shadow);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid var(--retro-border);
}
.label {
font-family: 'Press Start 2P', cursive;
font-size: 0.6rem;
color: var(--retro-green);
}
.raw-btn {
font-family: 'Press Start 2P', cursive;
font-size: 0.5rem;
padding: 6px 10px;
border: 2px solid var(--retro-border);
background: var(--retro-panel);
color: var(--retro-fg);
text-decoration: none;
box-shadow: 2px 2px 0px rgba(0,0,0,0.15);
}
.raw-btn:hover {
background: var(--retro-green);
color: #fff;
}
pre {
white-space: pre-wrap;
word-break: break-word;

View File

@@ -4,7 +4,9 @@
<div class="video-player">
<!-- svelte-ignore a11y_media_has_caption -->
<video controls preload="metadata" {src}></video>
<video controls preload="metadata" {src}>
<source src={src} type={mime} />
</video>
</div>
<style>

View File

@@ -1,10 +1,10 @@
// "window.location.origin"
const API_BASE = "http://127.0.0.1:8090";
export async function fetchMetadata(cxid) {
const res = await fetch(
`${API_BASE}/api/content/${encodeURIComponent(cxid)}`,
);
export async function fetchMetadata(cxid, password = "") {
let url = `${API_BASE}/api/content/${encodeURIComponent(cxid)}`;
if (password) url += `?sc=${encodeURIComponent(password)}`;
const res = await fetch(url);
if (!res.ok) {
const err = new Error(await res.text());
err.status = res.status;
@@ -34,6 +34,12 @@ export function fileUrl(cxid, fileIdx, download = false, password = "") {
return url;
}
export function rawUrl(cxid, fileIdx, password = "") {
let url = `${API_BASE}/api/content/${encodeURIComponent(cxid)}/file/${fileIdx}/raw`;
if (password) url += `?sc=${encodeURIComponent(password)}`;
return url;
}
export function formatSize(bytes) {
if (bytes === 0) return "0 B";
const units = ["B", "KB", "MB", "GB"];

16
frontend/src/lib/lang.js Normal file
View File

@@ -0,0 +1,16 @@
const EXT_TO_LANG = {
py: 'python', rs: 'rust', js: 'javascript', ts: 'typescript',
jsx: 'javascript', tsx: 'typescript', c: 'c', cpp: 'cpp', cc: 'cpp',
h: 'c', hpp: 'cpp', go: 'go', java: 'java', kt: 'kotlin',
swift: 'swift', rb: 'ruby', php: 'php', cs: 'csharp', scala: 'scala',
r: 'r', m: 'objectivec', mm: 'objectivec', pl: 'perl', lua: 'lua',
json: 'json', xml: 'xml', yaml: 'yaml', yml: 'yaml', toml: 'toml',
ini: 'ini', cfg: 'ini', sh: 'bash', bash: 'bash', ps1: 'powershell',
bat: 'batch', cmd: 'batch', sql: 'sql', dockerfile: 'dockerfile',
makefile: 'makefile', cmake: 'cmake',
};
export function detectLanguage(fileName) {
const ext = fileName.split('.').pop()?.toLowerCase();
return ext ? (EXT_TO_LANG[ext] || null) : null;
}

View File

@@ -64,6 +64,13 @@
<button onclick={submit} disabled={loading}>
{loading ? 'Loading...' : '[ Unlock ]'}
</button>
<details class="misc-section">
<summary>[ Misc ]</summary>
<div class="misc-content">
<a href="https://t.me/harmfulmeowbot?start=report" target="_blank" rel="noopener">Report Content</a>
</div>
</details>
</div>
<footer>
@@ -135,4 +142,31 @@
margin-top: 12px;
color: #777;
}
.misc-section {
margin-top: 8px;
border: 2px solid var(--retro-border);
padding: 8px 12px;
background: #f5f5f5;
}
.misc-section summary {
font-family: 'Press Start 2P', cursive;
font-size: 0.55rem;
color: var(--retro-green);
cursor: pointer;
user-select: none;
}
.misc-content {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.misc-content a {
font-size: 0.9rem;
color: var(--retro-green);
text-decoration: none;
}
.misc-content a:hover {
text-decoration: underline;
}
</style>

View File

@@ -1,12 +1,17 @@
<script>
import { fetchMetadata, verifyPassword, fileUrl } from '../lib/api.js'
import { fetchMetadata, verifyPassword, fileUrl, rawUrl } from '../lib/api.js'
import { detectLanguage } from '../lib/lang.js'
import ImageViewer from '../components/ImageViewer.svelte'
import VideoPlayer from '../components/VideoPlayer.svelte'
import AudioPlayer from '../components/AudioPlayer.svelte'
import MarkdownRenderer from '../components/MarkdownRenderer.svelte'
import TextViewer from '../components/TextViewer.svelte'
import CodeViewer from '../components/CodeViewer.svelte'
import DocumentCard from '../components/DocumentCard.svelte'
import ExecutableWarning from '../components/ExecutableWarning.svelte'
import SensitiveWarning from '../components/SensitiveWarning.svelte'
import PdfViewer from '../components/PdfViewer.svelte'
import DocxViewer from '../components/DocxViewer.svelte'
import MixedGallery from '../components/MixedGallery.svelte'
let { cxid, sc } = $props()
@@ -28,28 +33,18 @@
phase = 'loading_meta'
error = ''
try {
const meta = await fetchMetadata(cxid)
const meta = await fetchMetadata(cxid, password)
metadata = meta
if (meta.has_password && !password) {
phase = 'rendering'
} catch (e) {
const status = e.status || 0
if (status === 401) {
phase = 'password_required'
return
}
if (meta.has_password) {
const ok = await verifyPassword(cxid, password)
if (!ok) {
phase = 'password_required'
error = 'Incorrect password.'
return
}
}
phase = 'rendering'
} catch (e) {
phase = 'error'
const status = e.status || 0
if (status === 404) {
error = '[ Not Found ] This content does not exist or has been removed.'
} else if (status === 401) {
error = '[ Unauthorized ] This content requires a password.'
} else if (status === 429) {
error = '[ Rate Limited ] Too many requests. Please wait.'
} else if (status >= 500) {
@@ -65,6 +60,7 @@
if (!password) return
const ok = await verifyPassword(cxid, password)
if (ok) {
metadata = await fetchMetadata(cxid, password)
phase = 'rendering'
} else {
error = 'Incorrect password.'
@@ -83,8 +79,12 @@
if (flags & 4) return 'audio'
if (flags & 8) return 'markdown'
if (flags & 16) return 'text'
if (flags & 32) {
return file.mime === 'application/pdf' ? 'pdf' : 'docx'
}
if (flags & 64) return 'executable'
if (flags & 128) return 'dangerous'
if (flags & 512) return 'sensitive'
return 'document'
}
</script>
@@ -133,9 +133,19 @@
{:else if viewer === 'markdown'}
<MarkdownRenderer src={fileUrl(cxid, file.idx, false, password)} />
{:else if viewer === 'text'}
<TextViewer src={fileUrl(cxid, file.idx, false, password)} />
{#if detectLanguage(file.name)}
<CodeViewer src={fileUrl(cxid, file.idx, false, password)} rawUrl={rawUrl(cxid, file.idx, password)} fileName={file.name} />
{:else}
<TextViewer src={fileUrl(cxid, file.idx, false, password)} rawUrl={rawUrl(cxid, file.idx, password)} fileName={file.name} />
{/if}
{:else if viewer === 'pdf'}
<PdfViewer src={fileUrl(cxid, file.idx, false, password)} />
{:else if viewer === 'docx'}
<DocxViewer src={fileUrl(cxid, file.idx, false, password)} downloadUrl={fileUrl(cxid, file.idx, true, password)} {file} />
{:else if viewer === 'executable' || viewer === 'dangerous'}
<ExecutableWarning {file} downloadUrl={fileUrl(cxid, file.idx, true, password)} />
{:else if viewer === 'sensitive'}
<SensitiveWarning {file} downloadUrl={fileUrl(cxid, file.idx, true, password)} />
{:else}
<DocumentCard {file} downloadUrl={fileUrl(cxid, file.idx, true, password)} />
{/if}