Initial commit

This commit is contained in:
unknown
2026-05-19 01:41:07 +02:00
commit 033d0ffcb7
19 changed files with 2532 additions and 0 deletions

View File

0
commands.py Normal file
View File

54
config/configuration.ini Normal file
View File

@@ -0,0 +1,54 @@
# =========================
# CONFIGURATION
# =========================
[DIRECT_SETTINGS]
BOT_USERNAME = "blood_linksbot" # the username of your Telegram bot (without @)
BOT_TOKEN = "8649631256:AAHDQgDhpd8ymxACdNni1HomLuMzcm_W2Lg" # the token for your Telegram bot (get it from @BotFather), cannot be changed using commands and completely hidden for security reasons
TELEGRAM_PUBLIC_GROUP_ID = -1003571704137 # the ID of the public group where user-uploaded content will be exposed (if chosen)
TELEGRAM_ADMINISTRATION_GROUP_ID = -1003737694563 # the ID of the private group where admin-uploaded content will be reviewed/generated
OWNER_TELEGRAM_USER_ID = 8615679055 # the Telegram user ID of the bot owner (for managing critical settings), cannot be changed using commands
[BOT_SETTINGS]
ALLOW_LINKS_TO_TEXT = true # whether to allow users to link their text messages (true = feature enabled & allowed, false = feature disabled & not allowed)
ALLOW_LINKS_TO_MEDIA = true # whether to allow users to link their media from messages personally and receive custom links (true = feature enabled & allowed, false = feature disabled & not allowed)
WARNING_MESSAGE_ENABLED = true # whether to require user to confirm rules/ usage plan before interacting with the bot, true = enabled, false = disabled
MAXIMUM_CHAR_LIMIT_FOR_TEXT_MSGS = 9000 # the maximum character limit for text messages that users can link, set to a reasonable number to prevent abuse (e.g., 12000 characters)
REPORT_FEATURE_ENABLED = true # whether to allow users to report inappropriate content, true = enabled, false = disabled
DELETE_TELEGRAM_ACTIONS = true # whether to delete all basic telegram report messages/warnings such as user left, joined, message x was pinned, ect. in the groups to keep them clean, true = enabled, false = disabled
ALLOW_USERS_TO_DELETE_CONTENT = true # whether to allow users to delete their own content/links, making all links/content ID's immediately ineffective, true = enabled, false = disabled
ACTUALLY_DELETE_CONTENT = false # THIS OPTION IS NOT SHOWN; whether to actually the content that gets burned, deleted by users or administrators, true = fully deletes all media if that was the intended action & only saves content after accepting submissions/when neccessary, false = saves ALL contents, regardless whether they were accepted by administrators or not & does not delete them under all circumstances
DELETE_SUBMISSIONS_AFTER_REVIEW = moderated # whether to delete the original Telegram messages of user submissions after they have been reviewed by administrators, true = enabled, false = disabled, moderated = only delete if the content was moderated directly, i.e. the user banned for posting it
ALLOW_CUSTOM_PASSWORDS_FOR_LINKS = false # whether to allow users to choose their own custom passwords for accessing their content links, true = enabled, false = disabled (if disabled, the bot will generate random passwords for all links)
# if chosen direct, a randomized string is generated and directly embedded within URL for automatic loading, if chosen external the user has to enter the password given manually before accessing contents, to avoid long, complex strings uses a wordlist of possible passwords and chooses randomly from it
PASSWORD_METHOD_TYPE = "direct" # options: "direct", "external"
EXTERNAL_PASSWORD_LIST_TXT = "./resources/password_access_list.txt"
[STORAGE_SETTINGS]
TEXT_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json"
BAN_LOG_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json"
FILE_HASHES_KEY_WRAPPED_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json"
PREVIOUS_USERS_FOR_ADVERTISEMENTS_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json"
USER_UPLOADED_CONTENT_EXPOSED_METHOD = "web" # options: "web", "telegram", "both"
ADMIN_UPLOADED_CONTENT_EXPOSED_METHOD = "web" # options: "web", "telegram", "both"
# DO NOT ENTER ANYTHING HERE MANUALLY, SOFTWARE SETS ENCRYPTION KEYS AUTOMATICALLY
EPHEMERAL_AES_ENVELOPE_KEY = "" # This feature uses xchacha20-poly1305 alongside AES-KW (RFC 5649) for wrapping chacha keys - randomized wrapper key to securely encrypt/decrypt every file/textblob from traffic, cannot be changed using commands and completely hidden for security reasons
SUPPORTED_MEDIA_TYPES_STRING = "image, video, audio, document" # the media types that are allowed to be linked by users, will show up as 'X is not an allowed media type, only the following media types are allowed: X, Y, Z' if user tries to link a media type that is not in this list, shown but cannot be changed using commands
DATABASE_FOLDER_PATH = "./databases" # the folder path where all databases (if using sqlite3) or json files (if using json) will be stored, make sure the bot has read/write permissions for this folder, shown yet cannot be changed using commands
[WEB_SETTINGS]
PORT_EXPOSED = 9465 # the port that will be exposed for the web server (if enabled)
GENERATE_SECURE_KEY_FOR_CONTENT_LINKS = true # whether to generate secure, random keys for accessing web content instead of allowing direct API access to anyone, true = enabled, false = disabled
ALLOW_COMMENTS_ON_WEB_CONTENT_PAGES = true # whether to allow users to comment anonymously (cloudflare-integration important!) on content pages on the web interface, true = enabled, false = disabled
[MISC]
DEVELOPER_TELEGRAM_USERNAME = "forgecadrape" # credits for making this bot (plain Username without @), cannot be changed using commands
FORBIDDEN_CONTENT_TYPES_STRING = "CP, necriphilia, ect." # will shows up as 'X is strictly forbidden and leads to permanent deletion of links as well as exclusion from bot.'
MEDIA_TOPIC_FOR_BOT = "gore" # the topic that is generally seen and accepted by administrators for media generation, crucial for explicitly stating to new users which content will likely get approved.
CONTACT_TELEGERAM_LINK = "harmfulmeowbot?start=submit" # can be either BOT_NAME?start=submit OR TELEGRAM_USERNAME

0
configloader.py Normal file
View File

0
cryptographic_methods.py Normal file
View File

0
db_handler.py Normal file
View File

0
db_init.py Normal file
View File

62
main.py Normal file
View File

@@ -0,0 +1,62 @@
from aiogram import Bot, Dispatcher, F
from aiogram.enums import ParseMode
from aiogram.filters import Command
from aiogram.types import Message
from aiogram.client.default import DefaultBotProperties
import asyncio
bot = Bot(
token=BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
)
dp = Dispatcher()
def is_admin(user_id: int) -> bool:
return user_id in ADMINS
@dp.message(Command("help"))
async def help_command(message: Message):
if not is_admin(message.from_user.id):
return
help_text = """
<b>Bold text</b>
<i>Italic text</i>
<u>Underlined text</u>
<s>Strikethrough text</s>
<tg-spoiler>Spoiler text</tg-spoiler>
<code>Mono font / inline code</code>
<pre>
Multi-line
mono block
</pre>
<b><i>Bold + Italic</i></b>
Line 1
Line 2
Line 3
<a href="https://telegram.org">Clickable URL</a>
"""
await message.answer(help_text)
@dp.message(Command("start"))
async def start_command(message: Message):
await message.answer("Bot is running.")
async def main():
print("Bot started.")
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())

70
msic_bot.py Normal file
View File

@@ -0,0 +1,70 @@
HELP_MESSAGE = f"""<b><u>t.me/{BOT_USERNAME}</u></b>
<b>--- ADMINISTRATION CMDS ---</b>
<code>/add_admin</code> ID or reply to msg - adds an administrator which provides them with the ability of interacting with the bot in administrative ways
<code>/rm_admin</code> ID or reply to msg - removes an administrator which restricts them from interacting with the bot in any administrative way
<code>/add_coown</code> ID or reply to msg - adds a co-owner which have nearly full permissions over the bot and also the ability to edit configurations
<code>/rm_coown</code> ID or reply to msg - removes a co-owner which have nearly full permissions over the bot and also the ability to edit configurations
(An Owner/Co-Owner can add other Administrators.)
<b>--- LINK/USE CMDS ---</b>
<code>/create_link</code> reply to msg with vids/pics /submit in dms and approve by yourself - generates a simple link users can click one to view your uploaded media.
<code>/create_full_link</code> - takes all attachments of this channel and generates link to it, use carefully, high overload potential + pulls all media, even if not gore.
<code>/adv_group</code> TG_INV_LINK - sends a telegram group link provided to all users who previously interacted with the bot
<code>/help</code> - shows this msg
<b>--- LINK MISC CMDS ---</b>
<code>/show_links</code> - shows all links
<code>/del_link</code> LINK_ID - deletes a link by link id from /show_links
<code>/push_web</code> WEB_URL ID or reply to msg - adds content replied to/referenced to the already existing web accessor provided
<b>--- CONFIG CMDS ---</b>
<code>/config show</code> - shows the current configuration alongside keys & all values except for the bot token, which is hidden for security reasons
<code>/config reload</code> - reloads the configuration file, i.e. after changes useful
<code>/config set</code> KEY VALUE - sets a value for a certain key
<b>--- EXPLAINATION ---</b>
Any user can submit/upload gore or any other type of content via dms (<tg-spoiler><i>t.me/{BOT_USERNAME}?start=submit</i></tg-spoiler>), it gets sent here. if you click Approve & send group, the link to the media gets generated and automatically sent to your public channel.
If you click approve, the link to the media gets generated and you can share it.
If you click deny, the submission gets deleted and no link is generated.
If you click deny & ban, the submission gets deleted, the user gets banned from using the bot again & also banned from all channels.
<tg-spoiler><u>Bot programmed by @{DEVELOPER_TELEGRAM_USERNAME}.</u></tg-spoiler>"""
WARNING_MESSAGE = f"""The topic of this Bot is <code><b>{MEDIA_TOPIC_FOR_BOT.upper()}</b></code>. <b>Only submit media regarding that topic.</b>
<blockquote>
{FORBIDDEN_CONTENT_TYPES_STRING} is <b>strictly forbidden</b> and leads to <b>permanent deletion of links</b> as well as <b>exclusion from bot</b>.
</blockquote>
<code>Enter content ID</code> -> enter your content ID/link and the bot will upload all the videos/photos/texts to you in this chat, exposing the redirected hidden content
&
<code>Create Link To Media</code> -> upload your own content and generate link to it
&
<code>Submit Media to Admins</code> -> upload your own content and have admins review it, they will take action depending on what and how you submitted.
&
<code>Create Link To Text</code> -> Asks you to share contents (max. <code>{MAXIMUM_CHAR_LIMIT_FOR_TEXT_MSGS}</code> chars), then asks after how many views/usages your link shall get burned/data deleted.
&
<code>My links</code> -> shows all links you have created & their management options
<b><u>Do you understand that by clicking proceed, you will see multiple follow up options, and possibly engage with extreme content?</u></b>"""
START_MESSAGE = f"""Hey, this bot is used to upload, store and create links to all sorts of cruel media. Please choose from options below.
<b><u>The prior message explains what the below options do pretty well.</u></b>
For contacting administrators, please use t.me/{CONTACT_TELEGERAM_LINK}."""

View File

8
roadmap.txt Normal file
View File

@@ -0,0 +1,8 @@
1. Init the basic log
2. Init all the databases if not existing, add schemes, tables ect.
3. Init/online web server if used by users/admins
4. Start the telegram bot
Signoff/web credits
"Productions by t.me/forgecadrape
Business inquiries 056834db96cedde6012da9d5402c683c0eb260ed866da145a8f86e7f329bf77222 on Session"

0
utils.py Normal file
View File

0
web_files/files.js Normal file
View File

286
web_files/index.html Normal file
View File

@@ -0,0 +1,286 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CANNIBAL GIRLS // ARCHIVE</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=VT323&family=Space+Mono:ital,wght@0,400;0,700;1,400&family=Courier+Prime:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- CRT SCANLINE OVERLAY -->
<div class="scanlines"></div>
<!-- NOISE TEXTURE OVERLAY -->
<div class="noise-overlay"></div>
<!-- VIGNETTE -->
<div class="vignette"></div>
<!-- LOADING SCREEN -->
<section id="loading-screen" class="loading-screen">
<div class="loading-content">
<div class="loading-header">
<div class="status-line">
<span class="status-indicator"></span>
<span class="status-text">SYSTEM ONLINE</span>
</div>
<div class="datetime" id="loading-datetime"></div>
</div>
<div class="loading-center">
<h1 class="loading-title" id="loading-title">CANNIBAL GIRLS</h1>
<div class="loading-separator">
<span class="sep-line"></span>
<span class="sep-diamond">&#9660;</span>
<span class="sep-line"></span>
</div>
<p class="loading-subtitle" id="loading-subtitle">ARCHIVE TERMINAL v2.4.1</p>
<button id="load-files-btn" class="load-btn">
<span class="btn-bracket">[</span>
<span class="btn-text">LOAD FILES</span>
<span class="btn-bracket">]</span>
</button>
</div>
<div class="loading-footer">
<div class="footer-top">
<span class="footer-label">PRODUCER:</span>
<span class="footer-value">t.me/forgecadrape</span>
</div>
<div class="footer-bottom">
<span class="footer-label">SESSION:</span>
<span class="footer-value session-id">056834db96cedde6012da9d5402c683c0eb260ed866da145a8f86e7f329bf77222</span>
</div>
</div>
</div>
</section>
<!-- LOADING PHASE OVERLAY -->
<section id="loading-phase" class="loading-phase hidden">
<div class="phase-content">
<div class="phase-header">
<span class="phase-icon">&#9654;</span>
<span class="phase-title">INITIALIZING ARCHIVE PROTOCOL</span>
</div>
<div class="phase-terminal">
<div class="terminal-output" id="terminal-output"></div>
<div class="terminal-cursor">_</div>
</div>
<div class="phase-progress">
<div class="progress-label">
<span id="progress-text">MOUNTING VOLUMES...</span>
<span id="progress-percent">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progress-fill"></div>
</div>
</div>
</div>
</section>
<!-- ARCHIVE BROWSER -->
<main id="archive-browser" class="archive-browser hidden">
<!-- TOP BAR -->
<header class="archive-header">
<div class="header-left">
<h1 class="archive-title">CANNIBAL_GIRLS</h1>
<span class="header-separator">//</span>
<span class="header-subtitle">ARCHIVE_BROWSER</span>
</div>
<div class="header-right">
<div class="header-stats">
<span class="stat-item">
<span class="stat-label">FILES:</span>
<span class="stat-value" id="file-count">0</span>
</span>
<span class="stat-item">
<span class="stat-label">SIZE:</span>
<span class="stat-value" id="total-size">0 GB</span>
</span>
</div>
<div class="header-time" id="header-time"></div>
</div>
</header>
<!-- MAIN CONTENT AREA -->
<div class="archive-body">
<!-- SIDEBAR -->
<aside class="archive-sidebar">
<div class="sidebar-section">
<h3 class="sidebar-title">CATALOG</h3>
<ul class="sidebar-menu" id="sidebar-menu">
<li class="sidebar-item active" data-filter="all">
<span class="item-icon">&#9632;</span>
<span class="item-label">ALL_FILES</span>
<span class="item-count" id="count-all">0</span>
</li>
<li class="sidebar-item" data-filter="video">
<span class="item-icon">&#9654;</span>
<span class="item-label">VIDEO</span>
<span class="item-count" id="count-video">0</span>
</li>
<li class="sidebar-item" data-filter="image">
<span class="item-icon">&#9632;</span>
<span class="item-label">IMAGES</span>
<span class="item-count" id="count-image">0</span>
</li>
<li class="sidebar-item" data-filter="audio">
<span class="item-icon">&#9836;</span>
<span class="item-label">AUDIO</span>
<span class="item-count" id="count-audio">0</span>
</li>
<li class="sidebar-item" data-filter="document">
<span class="item-icon">&#9776;</span>
<span class="item-label">DOCS</span>
<span class="item-count" id="count-document">0</span>
</li>
<li class="sidebar-item" data-filter="archive">
<span class="item-icon">&#9632;</span>
<span class="item-label">ARCHIVES</span>
<span class="item-count" id="count-archive">0</span>
</li>
</ul>
</div>
<div class="sidebar-section">
<h3 class="sidebar-title">SYSTEM</h3>
<div class="system-info">
<div class="sys-row">
<span class="sys-label">STATUS:</span>
<span class="sys-value status-ok">ONLINE</span>
</div>
<div class="sys-row">
<span class="sys-label">UPTIME:</span>
<span class="sys-value" id="uptime">00:00:00</span>
</div>
<div class="sys-row">
<span class="sys-label">VERSION:</span>
<span class="sys-value">2.4.1-b</span>
</div>
</div>
</div>
</aside>
<!-- FILE LIST AREA -->
<div class="archive-content">
<!-- TOOLBAR -->
<div class="content-toolbar">
<div class="toolbar-left">
<div class="view-switcher">
<button class="view-btn active" data-view="list" title="List View">
<span class="view-icon">&#9776;</span>
<span class="view-label">LIST</span>
</button>
<button class="view-btn" data-view="grid" title="Grid View">
<span class="view-icon">&#9632;&#9632;</span>
<span class="view-label">GRID</span>
</button>
</div>
<div class="sort-controls">
<span class="sort-label">SORT:</span>
<select id="sort-select" class="sort-select">
<option value="name-asc">NAME [A-Z]</option>
<option value="name-desc">NAME [Z-A]</option>
<option value="date-desc">DATE [NEWEST]</option>
<option value="date-asc">DATE [OLDEST]</option>
<option value="size-desc">SIZE [LARGEST]</option>
<option value="size-asc">SIZE [SMALLEST]</option>
<option value="type">TYPE</option>
</select>
</div>
</div>
<div class="toolbar-right">
<div class="search-box">
<span class="search-icon">&#9906;</span>
<input type="text" id="search-input" class="search-input" placeholder="SEARCH FILES...">
</div>
</div>
</div>
<!-- FILE LIST -->
<div class="file-list-container">
<div class="file-list-header" id="file-list-header">
<span class="col-name">NAME</span>
<span class="col-type">TYPE</span>
<span class="col-size">SIZE</span>
<span class="col-date">DATE</span>
</div>
<div class="file-list" id="file-list"></div>
</div>
</div>
<!-- PREVIEW PANE -->
<aside class="archive-preview" id="preview-pane">
<div class="preview-header">
<span class="preview-title">PREVIEW</span>
<button class="preview-close" id="preview-close">&#10005;</button>
</div>
<div class="preview-content" id="preview-content">
<div class="preview-placeholder">
<span class="placeholder-icon">&#9632;</span>
<span class="placeholder-text">SELECT A FILE TO PREVIEW</span>
</div>
</div>
<div class="preview-meta" id="preview-meta">
<div class="meta-row">
<span class="meta-label">FILENAME:</span>
<span class="meta-value" id="meta-filename">---</span>
</div>
<div class="meta-row">
<span class="meta-label">TYPE:</span>
<span class="meta-value" id="meta-type">---</span>
</div>
<div class="meta-row">
<span class="meta-label">SIZE:</span>
<span class="meta-value" id="meta-size">---</span>
</div>
<div class="meta-row">
<span class="meta-label">CREATED:</span>
<span class="meta-value" id="meta-created">---</span>
</div>
<div class="meta-row">
<span class="meta-label">MODIFIED:</span>
<span class="meta-value" id="meta-modified">---</span>
</div>
<div class="meta-row">
<span class="meta-label">CHECKSUM:</span>
<span class="meta-value checksum" id="meta-checksum">---</span>
</div>
</div>
<div class="preview-actions">
<button class="action-btn" id="action-download">
<span class="action-icon">&#9660;</span>
DOWNLOAD
</button>
<button class="action-btn" id="action-copy">
<span class="action-icon">&#9900;</span>
COPY HASH
</button>
</div>
</aside>
</div>
<!-- FOOTER -->
<footer class="archive-footer">
<div class="footer-left">
<span class="footer-item">PROD: t.me/forgecadrape</span>
</div>
<div class="footer-center">
<span class="footer-item" id="footer-status">READY</span>
</div>
<div class="footer-right">
<span class="footer-item session-truncated">SESSION: 056834db96ce...f77222</span>
</div>
</footer>
</main>
<!-- AUDIO ELEMENT -->
<audio id="bg-audio" preload="auto"></audio>
<script src="index.js"></script>
</body>
</html>

666
web_files/index.js Normal file
View File

@@ -0,0 +1,666 @@
/**
* CANNIBAL GIRLS - ARCHIVE TERMINAL
* Main Application Logic
*/
(function() {
'use strict';
// ========================================
// CONFIGURATION
// ========================================
const CONFIG = {
loadDelayMin: 4000,
loadDelayMax: 9000,
audioVolume: 0.15,
audioPath: 'audio/',
terminalLines: [
{ text: 'Mounting storage volumes...', type: 'normal', delay: 0 },
{ text: 'Checking filesystem integrity...', type: 'normal', delay: 300 },
{ text: 'Scanning /archive/repository...', type: 'normal', delay: 600 },
{ text: 'Found 247 indexed files', type: 'success', delay: 1200 },
{ text: 'Verifying checksums...', type: 'normal', delay: 1500 },
{ text: 'Warning: 3 files flagged for review', type: 'warning', delay: 2200 },
{ text: 'Rebuilding file index...', type: 'normal', delay: 2500 },
{ text: 'Optimizing cache...', type: 'normal', delay: 3200 },
{ text: 'Archive mount successful', type: 'success', delay: 4000 },
{ text: 'Ready for access', type: 'success', delay: 4500 }
]
};
// ========================================
// DEMO FILE DATA
// ========================================
const DEMO_FILES = [
{ id: 1, name: 'project_venus_raw.mp4', type: 'video', size: 2847563210, date: '2024-01-15T14:23:00', checksum: 'a3f7c9d2e1b4f5a6c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1' },
{ id: 2, name: 'archive_segment_07.mkv', type: 'video', size: 1456789012, date: '2024-01-14T09:15:00', checksum: 'b4c8d0e3f2a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c2' },
{ id: 3, name: 'nocturne_stills_set.zip', type: 'archive', size: 456782345, date: '2024-01-13T16:45:00', checksum: 'c5d9e1f4a3b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d3' },
{ id: 4, name: 'soundscape_01.flac', type: 'audio', size: 124567890, date: '2024-01-12T11:30:00', checksum: 'd6e0f2a5b4c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e4' },
{ id: 5, name: 'behind_the_scenes_03.jpg', type: 'image', size: 8945678, date: '2024-01-11T08:20:00', checksum: 'e7f1a3b6c5d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f5' },
{ id: 6, name: 'production_notes_v2.pdf', type: 'document', size: 2345678, date: '2024-01-10T19:00:00', checksum: 'f8a2b4c7d6e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a6' },
{ id: 7, name: 'raw_footage_reel_12.mp4', type: 'video', size: 5678901234, date: '2024-01-09T13:10:00', checksum: 'a9b3c5d7e8f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7' },
{ id: 8, name: 'color_grade_lut.cube', type: 'document', size: 45678, date: '2024-01-08T10:45:00', checksum: 'b0c4d6e8f9a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8' },
{ id: 9, name: 'ambient_collection.zip', type: 'archive', size: 345678901, date: '2024-01-07T15:30:00', checksum: 'c1d5e7f9a0b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9' },
{ id: 10, name: 'title_sequence_v3.mov', type: 'video', size: 1234567890, date: '2024-01-06T07:00:00', checksum: 'd2e6f8a0b1c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0' },
{ id: 11, name: 'location_scout_photos.zip', type: 'archive', size: 156789012, date: '2024-01-05T12:15:00', checksum: 'e3f7a9b1c2d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1' },
{ id: 12, name: 'score_master_24bit.wav', type: 'audio', size: 890123456, date: '2024-01-04T18:30:00', checksum: 'f4a8b0c2d3e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2' },
{ id: 13, name: 'character_studies_01.png', type: 'image', size: 45678901, date: '2024-01-03T09:45:00', checksum: 'a5b9c1d3e4f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3' },
{ id: 14, name: 'edit_timeline_v4.prproj', type: 'document', size: 12345678, date: '2024-01-02T14:00:00', checksum: 'b6c0d2e4f5a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4' },
{ id: 15, name: 'promo_cut_30sec.mp4', type: 'video', size: 678901234, date: '2024-01-01T11:20:00', checksum: 'c7d1e3f5a6b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5' }
];
// ========================================
// STATE
// ========================================
let state = {
files: [],
filteredFiles: [],
selectedFile: null,
currentView: 'list',
currentFilter: 'all',
currentSort: 'date-desc',
searchQuery: '',
uptime: 0,
audioContext: null
};
// ========================================
// DOM REFERENCES
// ========================================
const els = {};
function cacheElements() {
els.loadingScreen = document.getElementById('loading-screen');
els.loadingTitle = document.getElementById('loading-title');
els.loadingDatetime = document.getElementById('loading-datetime');
els.loadBtn = document.getElementById('load-files-btn');
els.loadingPhase = document.getElementById('loading-phase');
els.terminalOutput = document.getElementById('terminal-output');
els.progressText = document.getElementById('progress-text');
els.progressPercent = document.getElementById('progress-percent');
els.progressFill = document.getElementById('progress-fill');
els.archiveBrowser = document.getElementById('archive-browser');
els.fileList = document.getElementById('file-list');
els.fileListHeader = document.getElementById('file-list-header');
els.sidebarMenu = document.getElementById('sidebar-menu');
els.sortSelect = document.getElementById('sort-select');
els.searchInput = document.getElementById('search-input');
els.previewPane = document.getElementById('preview-pane');
els.previewContent = document.getElementById('preview-content');
els.previewClose = document.getElementById('preview-close');
els.previewMeta = document.getElementById('preview-meta');
els.headerTime = document.getElementById('header-time');
els.uptime = document.getElementById('uptime');
els.fileCount = document.getElementById('file-count');
els.totalSize = document.getElementById('total-size');
els.footerStatus = document.getElementById('footer-status');
els.bgAudio = document.getElementById('bg-audio');
els.actionDownload = document.getElementById('action-download');
els.actionCopy = document.getElementById('action-copy');
}
// ========================================
// UTILITIES
// ========================================
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function formatDate(dateString) {
const date = new Date(dateString);
return date.toISOString().slice(0, 19).replace('T', ' ');
}
function formatDateShort(dateString) {
const date = new Date(dateString);
return date.toISOString().slice(0, 10);
}
function getFileIcon(type) {
const icons = {
video: '&#9654;',
audio: '&#9836;',
image: '&#9632;',
document: '&#9776;',
archive: '&#9632;'
};
return icons[type] || '&#9632;';
}
function getRandomDelay() {
return Math.floor(Math.random() * (CONFIG.loadDelayMax - CONFIG.loadDelayMin + 1)) + CONFIG.loadDelayMin;
}
function generateChecksum() {
const chars = 'abcdef0123456789';
let result = '';
for (let i = 0; i < 64; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
// ========================================
// TEXT SCRAMBLE EFFECT
// ========================================
class TextScramble {
constructor(el) {
this.el = el;
this.chars = '!<>-_\\/[]{}--=+*^?#________';
this.update = this.update.bind(this);
}
setText(newText) {
const oldText = this.el.innerText;
const length = Math.max(oldText.length, newText.length);
const promise = new Promise(resolve => this.resolve = resolve);
this.queue = [];
for (let i = 0; i < length; i++) {
const from = oldText[i] || '';
const to = newText[i] || '';
const start = Math.floor(Math.random() * 40);
const end = start + Math.floor(Math.random() * 40);
this.queue.push({ from, to, start, end });
}
cancelAnimationFrame(this.frameRequest);
this.frame = 0;
this.update();
return promise;
}
update() {
let output = '';
let complete = 0;
for (let i = 0, n = this.queue.length; i < n; i++) {
let { from, to, start, end, char } = this.queue[i];
if (this.frame >= end) {
complete++;
output += to;
} else if (this.frame >= start) {
if (!char || Math.random() < 0.28) {
char = this.randomChar();
this.queue[i].char = char;
}
output += `<span class="text-dim">${char}</span>`;
} else {
output += from;
}
}
this.el.innerHTML = output;
if (complete === this.queue.length) {
this.resolve();
} else {
this.frameRequest = requestAnimationFrame(this.update);
this.frame++;
}
}
randomChar() {
return this.chars[Math.floor(Math.random() * this.chars.length)];
}
}
// ========================================
// AUDIO MANAGEMENT
// ========================================
async function getAudioFiles() {
// In a real scenario, this would fetch from the server
// For now, we'll try common audio filenames or return empty
const commonNames = ['ambient.mp3', 'noise.mp3', 'static.mp3', 'drone.mp3', 'hum.mp3'];
return commonNames.map(name => CONFIG.audioPath + name);
}
async function playRandomAudio() {
try {
const audioFiles = await getAudioFiles();
if (audioFiles.length === 0) {
console.log('No audio files found in', CONFIG.audioPath);
return;
}
const randomFile = audioFiles[Math.floor(Math.random() * audioFiles.length)];
els.bgAudio.src = randomFile;
els.bgAudio.volume = CONFIG.audioVolume;
const playPromise = els.bgAudio.play();
if (playPromise !== undefined) {
playPromise.catch(err => {
console.log('Audio playback failed:', err.message);
});
}
} catch (err) {
console.log('Audio error:', err);
}
}
function stopAudio() {
els.bgAudio.pause();
els.bgAudio.currentTime = 0;
}
// ========================================
// TERMINAL OUTPUT
// ========================================
function addTerminalLine(text, type = 'normal') {
const line = document.createElement('div');
line.className = 'terminal-line';
const prompt = document.createElement('span');
prompt.className = 'line-prompt';
prompt.textContent = '>';
const content = document.createElement('span');
content.className = `line-content ${type}`;
content.textContent = text;
line.appendChild(prompt);
line.appendChild(content);
els.terminalOutput.appendChild(line);
// Auto-scroll
els.terminalOutput.scrollTop = els.terminalOutput.scrollHeight;
}
// ========================================
// LOADING SEQUENCE
// ========================================
async function startLoadingSequence() {
const delay = getRandomDelay();
// Hide loading screen, show loading phase
els.loadBtn.disabled = true;
els.loadBtn.innerHTML = '<span class="spinner"></span>';
setTimeout(() => {
els.loadingScreen.classList.add('hidden');
els.loadingPhase.classList.remove('hidden');
// Start audio
playRandomAudio();
// Start terminal output
let currentTime = 0;
CONFIG.terminalLines.forEach(line => {
setTimeout(() => {
addTerminalLine(line.text, line.type);
}, line.delay);
});
// Progress bar
const startTime = Date.now();
const updateProgress = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min((elapsed / delay) * 100, 100);
els.progressFill.style.width = progress + '%';
els.progressPercent.textContent = Math.floor(progress) + '%';
// Update progress text based on percentage
if (progress < 20) els.progressText.textContent = 'MOUNTING VOLUMES...';
else if (progress < 40) els.progressText.textContent = 'VERIFYING CHECKSUMS...';
else if (progress < 60) els.progressText.textContent = 'INDEXING FILES...';
else if (progress < 80) els.progressText.textContent = 'OPTIMIZING CACHE...';
else if (progress < 100) els.progressText.textContent = 'FINALIZING...';
else els.progressText.textContent = 'COMPLETE';
if (progress < 100) {
requestAnimationFrame(updateProgress);
} else {
setTimeout(finishLoading, 500);
}
};
requestAnimationFrame(updateProgress);
}, 300);
}
function finishLoading() {
stopAudio();
els.loadingPhase.classList.add('hidden');
els.archiveBrowser.classList.remove('hidden');
els.archiveBrowser.classList.add('active');
// Initialize archive
initArchive();
}
// ========================================
// ARCHIVE INITIALIZATION
// ========================================
function initArchive() {
state.files = DEMO_FILES.map(f => ({
...f,
checksum: f.checksum || generateChecksum()
}));
updateCounts();
filterAndSortFiles();
renderFiles();
updateStats();
// Start uptime counter
setInterval(() => {
state.uptime++;
updateUptime();
}, 1000);
}
function updateCounts() {
const counts = {
all: state.files.length,
video: state.files.filter(f => f.type === 'video').length,
image: state.files.filter(f => f.type === 'image').length,
audio: state.files.filter(f => f.type === 'audio').length,
document: state.files.filter(f => f.type === 'document').length,
archive: state.files.filter(f => f.type === 'archive').length
};
Object.keys(counts).forEach(key => {
const el = document.getElementById(`count-${key}`);
if (el) el.textContent = counts[key];
});
}
function updateStats() {
els.fileCount.textContent = state.filteredFiles.length;
const totalBytes = state.filteredFiles.reduce((sum, f) => sum + f.size, 0);
els.totalSize.textContent = formatBytes(totalBytes);
}
function updateUptime() {
const hours = Math.floor(state.uptime / 3600).toString().padStart(2, '0');
const minutes = Math.floor((state.uptime % 3600) / 60).toString().padStart(2, '0');
const seconds = (state.uptime % 60).toString().padStart(2, '0');
els.uptime.textContent = `${hours}:${minutes}:${seconds}`;
}
// ========================================
// FILE FILTERING & SORTING
// ========================================
function filterAndSortFiles() {
let files = [...state.files];
// Apply filter
if (state.currentFilter !== 'all') {
files = files.filter(f => f.type === state.currentFilter);
}
// Apply search
if (state.searchQuery) {
const query = state.searchQuery.toLowerCase();
files = files.filter(f => f.name.toLowerCase().includes(query));
}
// Apply sort
files.sort((a, b) => {
switch (state.currentSort) {
case 'name-asc':
return a.name.localeCompare(b.name);
case 'name-desc':
return b.name.localeCompare(a.name);
case 'date-desc':
return new Date(b.date) - new Date(a.date);
case 'date-asc':
return new Date(a.date) - new Date(b.date);
case 'size-desc':
return b.size - a.size;
case 'size-asc':
return a.size - b.size;
case 'type':
return a.type.localeCompare(b.type) || a.name.localeCompare(b.name);
default:
return 0;
}
});
state.filteredFiles = files;
updateStats();
}
// ========================================
// FILE RENDERING
// ========================================
function renderFiles() {
els.fileList.innerHTML = '';
if (state.filteredFiles.length === 0) {
const empty = document.createElement('div');
empty.className = 'file-item';
empty.innerHTML = '<span style="color: #5A4545; grid-column: 1 / -1; text-align: center; padding: 2rem;">NO FILES FOUND</span>';
els.fileList.appendChild(empty);
return;
}
state.filteredFiles.forEach((file, index) => {
const item = document.createElement('div');
item.className = 'file-item';
if (state.selectedFile && state.selectedFile.id === file.id) {
item.classList.add('selected');
}
// Add staggered animation
item.style.animation = `fadeInUp 0.3s ease-out ${index * 0.03}s both`;
item.innerHTML = `
<div class="file-name-cell">
<span class="file-icon">${getFileIcon(file.type)}</span>
<span class="file-name" title="${file.name}">${file.name}</span>
</div>
<span class="file-type">${file.type}</span>
<span class="file-size">${formatBytes(file.size)}</span>
<span class="file-date">${formatDateShort(file.date)}</span>
`;
item.addEventListener('click', () => selectFile(file));
els.fileList.appendChild(item);
});
}
// ========================================
// FILE SELECTION & PREVIEW
// ========================================
function selectFile(file) {
state.selectedFile = file;
// Update selection in list
document.querySelectorAll('.file-item').forEach(item => {
item.classList.remove('selected');
});
const selectedEl = Array.from(els.fileList.children).find(el =>
el.querySelector('.file-name')?.textContent === file.name
);
if (selectedEl) selectedEl.classList.add('selected');
// Update preview
updatePreview(file);
// Update footer status
els.footerStatus.textContent = `SELECTED: ${file.name}`;
}
function updatePreview(file) {
// Update preview content
els.previewContent.innerHTML = `
<div class="preview-placeholder">
<span class="preview-file-icon">${getFileIcon(file.type)}</span>
<span class="placeholder-text">${file.type.toUpperCase()} FILE</span>
</div>
`;
// Update meta
document.getElementById('meta-filename').textContent = file.name;
document.getElementById('meta-type').textContent = file.type.toUpperCase();
document.getElementById('meta-size').textContent = formatBytes(file.size);
document.getElementById('meta-created').textContent = formatDate(file.date);
document.getElementById('meta-modified').textContent = formatDate(file.date);
document.getElementById('meta-checksum').textContent = file.checksum;
}
function closePreview() {
state.selectedFile = null;
document.querySelectorAll('.file-item').forEach(item => {
item.classList.remove('selected');
});
els.previewContent.innerHTML = `
<div class="preview-placeholder">
<span class="placeholder-icon">&#9632;</span>
<span class="placeholder-text">SELECT A FILE TO PREVIEW</span>
</div>
`;
document.getElementById('meta-filename').textContent = '---';
document.getElementById('meta-type').textContent = '---';
document.getElementById('meta-size').textContent = '---';
document.getElementById('meta-created').textContent = '---';
document.getElementById('meta-modified').textContent = '---';
document.getElementById('meta-checksum').textContent = '---';
els.footerStatus.textContent = 'READY';
}
// ========================================
// VIEW SWITCHING
// ========================================
function switchView(view) {
state.currentView = view;
// Update buttons
document.querySelectorAll('.view-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.view === view);
});
// Update list class
if (view === 'grid') {
els.fileList.classList.add('grid-view');
els.fileListHeader.style.display = 'none';
} else {
els.fileList.classList.remove('grid-view');
els.fileListHeader.style.display = 'grid';
}
renderFiles();
}
// ========================================
// EVENT LISTENERS
// ========================================
function bindEvents() {
// Load button
els.loadBtn.addEventListener('click', startLoadingSequence);
// Sidebar filters
els.sidebarMenu.addEventListener('click', (e) => {
const item = e.target.closest('.sidebar-item');
if (!item) return;
document.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
state.currentFilter = item.dataset.filter;
filterAndSortFiles();
renderFiles();
});
// View switcher
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', () => switchView(btn.dataset.view));
});
// Sort
els.sortSelect.addEventListener('change', (e) => {
state.currentSort = e.target.value;
filterAndSortFiles();
renderFiles();
});
// Search
els.searchInput.addEventListener('input', (e) => {
state.searchQuery = e.target.value;
filterAndSortFiles();
renderFiles();
});
// Preview close
els.previewClose.addEventListener('click', closePreview);
// Download button
els.actionDownload.addEventListener('click', () => {
if (!state.selectedFile) return;
const blob = new Blob([`File: ${state.selectedFile.name}\nSize: ${formatBytes(state.selectedFile.size)}\nChecksum: ${state.selectedFile.checksum}`], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = state.selectedFile.name + '.info.txt';
a.click();
URL.revokeObjectURL(url);
els.footerStatus.textContent = 'DOWNLOADED INFO FILE';
setTimeout(() => {
els.footerStatus.textContent = 'READY';
}, 2000);
});
// Copy hash button
els.actionCopy.addEventListener('click', () => {
if (!state.selectedFile) return;
navigator.clipboard.writeText(state.selectedFile.checksum).then(() => {
els.footerStatus.textContent = 'CHECKSUM COPIED TO CLIPBOARD';
setTimeout(() => {
els.footerStatus.textContent = 'READY';
}, 2000);
}).catch(() => {
els.footerStatus.textContent = 'COPY FAILED';
});
});
}
// ========================================
// CLOCK UPDATES
// ========================================
function updateClock() {
const now = new Date();
const dateStr = now.toISOString().slice(0, 19).replace('T', ' ');
if (els.loadingDatetime) {
els.loadingDatetime.textContent = dateStr;
}
if (els.headerTime) {
els.headerTime.textContent = dateStr;
}
}
// ========================================
// INITIALIZATION
// ========================================
function init() {
cacheElements();
// Title scramble effect
const scrambler = new TextScramble(els.loadingTitle);
setTimeout(() => {
scrambler.setText('CANNIBAL GIRLS');
}, 500);
// Clock
updateClock();
setInterval(updateClock, 1000);
// Bind events
bindEvents();
console.log('%c CANNIBAL GIRLS // ARCHIVE TERMINAL v2.4.1 ', 'background: #0F0D0D; color: #E85D75; font-family: monospace; padding: 4px 8px;');
console.log('%c SYSTEM ONLINE ', 'background: #181414; color: #4A7C59; font-family: monospace; padding: 2px 6px;');
}
// Start when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

0
web_files/resources.js Normal file
View File

1386
web_files/style.css Normal file

File diff suppressed because it is too large Load Diff

0
webhandler.py Normal file
View File

0
webloader.py Normal file
View File