Compare commits

...

33 Commits
a ... main

Author SHA1 Message Date
7667c5064b Update README.md 2024-10-26 18:42:45 +02:00
454c411121 Update README.md 2024-10-07 19:24:18 +02:00
a031e9d2be fixv2v3 2024-10-05 20:38:04 +02:00
26ea8020c3 fix2v2 2024-10-05 20:37:31 +02:00
25ce46a70b fix? 2024-10-05 20:37:08 +02:00
23c535d933 fix 2 2024-10-05 20:32:33 +02:00
f9d9e0e3c0 revert back 2024-10-05 20:31:49 +02:00
92ca08c1ed fix that anonymous pastebins returned user None instead of Anonymous 2024-10-05 20:29:58 +02:00
c3a8994769 allow user to reset api key 2024-09-16 14:41:26 +02:00
de1b826928 pastebin line offset fix 2024-09-16 14:21:48 +02:00
7f5c9202bb fix fix fix 2024-09-16 14:10:51 +02:00
2e5fd2024f this contains bug next commit is fix 2024-09-16 13:54:54 +02:00
a33bf8f665 fixes v69 2024-09-16 13:34:50 +02:00
052acdb394 minor fixes 2024-09-16 13:02:12 +02:00
3e40c79b81 raw embed fix 2024-09-16 12:53:05 +02:00
67bdc5c325 opengraph fix v2 2024-09-16 12:26:00 +02:00
a2544633e6 fixed that insatnt upload doesnt go on top of fiel upload model and other minor bugfixes 2024-09-16 12:09:50 +02:00
740300edaf password requirements and opengraph for discord,slack etc 2024-09-16 11:49:09 +02:00
79d0adfcf8 asdasd removed from the ------------ line 2024-09-15 20:44:29 +02:00
333c7f13c0 Merge pull request 'Update README.md' (#4) from testt into main
Reviewed-on: cgcristi/aCloud#4
2024-09-15 20:26:20 +02:00
29e26aa647 ctrl + v when file modal is open and drag file to instantly upload 2024-09-15 17:03:38 -04:00
24cb6cd848 Update README.md 2024-09-15 20:23:09 +02:00
fa373d6b46 this? 2024-09-15 16:01:29 +02:00
88099f5a54 is this how u iframe? 2024-09-15 16:00:46 +02:00
cd2c8323d5 sharexfix 2024-09-14 21:00:33 +02:00
c76ff11c26 i dont even remember what i did i just gonn apsuh because its not up to date and i dproably made some curical improvements that i will realsie ver time nd i cant speel because im alread yhigh 2024-09-14 20:36:31 +02:00
3edd810c36 this took me over 10 hours and my head hurts and im feeling so bad that i cant even explain what i did but like passwords redesigning and etc 2024-09-14 16:53:26 +02:00
d404490442 minor improvement to user dashboard 2024-09-11 19:29:29 +02:00
45699446e0 sharex support https 2024-09-10 20:21:22 +02:00
5c0d0413ef fix v2 2024-09-10 20:16:06 +02:00
5751297239 sharex fix 2024-09-10 19:56:15 +02:00
4b0ab996ab VERY VERY VERY BIG UPDATE i wont list here try it urself 2024-09-10 19:41:49 +02:00
0f444b7606 too many changes for me to list but basically user suppport file lsiting uploading deleting renaming moving copying etc and toggle if index.html presnet shw it to acces ur page /username and then ASKDJAKLSDl this is a development why do i have to document everything 2024-09-10 16:00:43 +02:00
22 changed files with 5207 additions and 737 deletions

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# Ignore SQLite database
data.db
# Ignore uploads directory
/uploads/
# Python-related files
__pycache__/
*.py[cod]
*$py.class
# Virtual environment
venv/
env/
.env
# IDE-specific files
.vscode/
.idea/
# OS-specific files
.DS_Store
Thumbs.db
# Log files
*.log
# Temporary files
*.tmp

View File

@ -1,30 +1,2 @@
## Text sharing, File sharing, link shortener. this repo is deprecated
### frontend and backend, easily configurable (thru editing the html files and the .py) https://github.com/spitkov/sxbin
----------------
1. Introduction
</br>
</br>
</br>
As said above, this is a **simple & private** backend (app.py) and frontend (in /templates), which has these features:
- File Sharing (get a short link to share files, and when the link is opened it will show some information)
- Text Sharing (kind of a pastebin, it will show the text and when the text was made, again in a short link)
- URL Shortener (when hosting this, it will short an url, and give you a link, and when you open it, it will automatically redirect you to that website)
2. This is 100% **anonymous**, no one can see who created the links, etc.
3. The UI is simple.
4. This is very easily self-hostable </br> </br>
Rookie Mistake: when you self-host it, you can open it in localhost:7123, when making a file/paste/shortener don't share that localhost link, localhost is only accessible on YOUR network. - instead, u can host it with your domain.
</br>
</br>
5. This project is **COMPLETLY** open source.
</br>
</br>
6. You might need to install some pip packages, if you dont have them installed already download this repo and go into the folder and execute ```pip install -r requirements.txt```
</br>
</br>
7. Demo:
<video src="https://cgcristi.xyz/demo.mp4" controls></video>

1515
app.py

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,17 @@
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
api_key TEXT
);
CREATE TABLE IF NOT EXISTS content ( CREATE TABLE IF NOT EXISTS content (
vanity TEXT PRIMARY KEY, vanity TEXT PRIMARY KEY,
type TEXT NOT NULL, type TEXT NOT NULL,
data TEXT NOT NULL, data TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
); user_id INTEGER,
is_private INTEGER DEFAULT 0,
CREATE TABLE IF NOT EXISTS users ( password TEXT,
id INTEGER PRIMARY KEY AUTOINCREMENT, FOREIGN KEY (user_id) REFERENCES users (id)
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL
); );

View File

@ -66,7 +66,13 @@
</br> </br>
</br> </br>
</br> </br>
<p>Source code on: </p><a href="https://github.com/realcgcristi/aCloud" target="_blank">GitHub</a> | <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> <div class="footer">
<p>
Source code on: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
</p>
</div>
</div> </div>
</body> </body>
</html> </html>

62
templates/api_docs.html Normal file
View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Documentation - sxbin</title>
<style>
/* Add your styles here */
</style>
</head>
<body>
<h1>sxbin API Documentation</h1>
<h2>Authentication</h2>
<p>All API requests require an API key to be sent in the X-API-Key header.</p>
<h2>Endpoints</h2>
<h3>Upload File</h3>
<pre>
POST /api/upload/file
Headers:
X-API-Key: your_api_key
Body: multipart/form-data
file: (binary)
</pre>
<p>Returns: JSON with file URL and deletion URL</p>
<h3>Upload Pastebin</h3>
<pre>
POST /api/upload/pastebin
Headers:
X-API-Key: your_api_key
Content-Type: application/json
Body:
{
"content": "Your pastebin content here"
}
</pre>
<p>Returns: JSON with pastebin URL and deletion URL</p>
<h3>Shorten URL</h3>
<pre>
POST /api/shorten
Headers:
X-API-Key: your_api_key
Content-Type: application/json
Body:
{
"url": "https://example.com/your-long-url-here"
}
</pre>
<p>Returns: JSON with shortened URL and deletion URL</p>
<h2>Error Handling</h2>
<p>All errors are returned as JSON with an "error" field describing the issue.</p>
<footer>
<p>For more information or support, please contact our team.</p>
</footer>
</body>
</html>

View File

@ -86,6 +86,8 @@
/* Ensure proper contrast for syntax highlighting */ /* Ensure proper contrast for syntax highlighting */
.highlight pre { .highlight pre {
color: var(--text-color); color: var(--text-color);
white-space: pre-wrap;
word-wrap: break-word;
} }
/* Override Pygments styles for dark mode */ /* Override Pygments styles for dark mode */
@ -154,42 +156,65 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>Content</h1> <h2>Content</h2>
{% if created_at %} {% if content %}
<p>Created at: {{ created_at }}</p> <p>Uploaded by: {{ 'Anonymous' if content.username == 'None' else content.username }}</p>
{% endif %} <p>Created at: {{ content.created_at }}</p>
{% if language %}
<p>Detected Language: {{ language }}</p>
{% endif %}
<div class="button-container">
<button id="copy-button" onclick="copyToClipboard()">Copy Raw</button>
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" target="_blank"><button>View Raw</button></a>
</div>
<div class="highlight">
{% if highlighted_content %} {% if highlighted_content %}
<style>{{ css|safe }}</style>
<div class="highlight">
{{ highlighted_content|safe }} {{ highlighted_content|safe }}
{% else %}
<pre>{{ content }}</pre>
{% endif %}
</div> </div>
<button onclick="copyToClipboard()" class="btn">Copy</button>
<a href="{{ url_for('raw_vanity', vanity=content.vanity) }}" class="btn">View Raw</a>
{% if current_user.is_authenticated and current_user.id == content.user_id %}
<a href="{{ url_for('edit_content', vanity=content.vanity) }}" class="btn">Edit</a>
{% endif %}
{% elif url %}
<p>Shortened URL: <a href="{{ url }}">{{ url }}</a></p>
<button onclick="copyToClipboard()" class="btn">Copy URL</button>
{% elif raw_content %}
<pre style="white-space: pre-wrap; word-wrap: break-word;">{{ raw_content }}</pre>
<button onclick="copyToClipboard()" class="btn">Copy</button>
<a href="{{ url_for('raw_vanity', vanity=content.vanity) }}" class="btn">View Raw</a>
{% if current_user.is_authenticated and current_user.id == content.user_id %}
<a href="{{ url_for('edit_content', vanity=content.vanity) }}" class="btn">Edit</a>
{% endif %}
{% else %}
<p>No content available</p>
{% endif %}
{% if current_user.is_authenticated and current_user.id == content.user_id %}
<form action="{{ url_for('delete_content', vanity=content.vanity) }}" method="post" style="display: inline;">
<button type="submit" class="btn">Delete</button>
</form>
{% endif %}
{% else %}
<p>No content found.</p>
{% endif %}
</div> </div>
<button id="theme-toggle">Toggle Theme</button> <button id="theme-toggle">Toggle Theme</button>
<script> <script>
const rawContent = {{ raw_content|tojson }}; const rawContent = {{ raw_content|tojson|default('null') }};
const url = {{ url|tojson|default('null') }};
function copyToClipboard() { function copyToClipboard() {
navigator.clipboard.writeText(rawContent).then(() => { let textToCopy = rawContent;
const copyButton = document.getElementById("copy-button"); if (url) {
const originalText = copyButton.textContent; textToCopy = url;
copyButton.textContent = "Copied!"; }
setTimeout(() => { if (textToCopy) {
copyButton.textContent = originalText; navigator.clipboard.writeText(textToCopy).then(() => {
}, 2000); alert('Copied to clipboard!');
}).catch(err => { }).catch(err => {
console.error('Failed to copy text: ', err); console.error('Failed to copy text: ', err);
}); });
} else {
console.error('No content to copy');
}
} }
const themeToggle = document.getElementById('theme-toggle'); const themeToggle = document.getElementById('theme-toggle');

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Content Info</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #1a1a1a;
color: #f0f0f0;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h2 {
color: #4CAF50;
}
.info-item {
margin-bottom: 10px;
}
.btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 8px 12px;
text-decoration: none;
border-radius: 4px;
margin: 5px 0;
}
.btn:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h2>Content Information</h2>
<div class="info-item"><strong>Type:</strong> {{ info.type }}</div>
<div class="info-item"><strong>Vanity URL:</strong> {{ info.vanity }}</div>
<div class="info-item"><strong>Created At:</strong> {{ info.created_at }}</div>
<div class="info-item"><strong>Uploaded By:</strong> {{ info.username }}</div>
{% if info.type == 'file' %}
<div class="info-item"><strong>Filename:</strong> {{ info.data }}</div>
{% if info.file_size %}
<div class="info-item"><strong>File Size:</strong> {{ info.file_size }} bytes</div>
{% endif %}
<a href="{{ url_for('redirect_vanity', vanity=info.vanity)|replace('/download', '') }}" class="btn">View/Embed</a>
<a href="{{ url_for('redirect_vanity', vanity=info.vanity) ~ ('/download' if '/download' not in url_for('redirect_vanity', vanity=info.vanity) else '') }}" class="btn">Download</a>
{% elif info.type == 'url' %}
<div class="info-item"><strong>Target URL:</strong> {{ info.data }}</div>
<a href="{{ url_for('redirect_vanity', vanity=info.vanity) }}" class="btn">Visit URL</a>
{% elif info.type == 'pastebin' %}
<a href="{{ url_for('redirect_vanity', vanity=info.vanity) }}" class="btn">View Pastebin</a>
<a href="{{ url_for('raw_vanity', vanity=info.vanity) }}" class="btn">View Raw</a>
{% endif %}
{% if current_user.is_authenticated and current_user.username == info.username %}
{% if info.type != 'file' or not info.is_media %}
<a href="{{ url_for('edit_content', vanity=info.vanity) }}" class="btn">Edit</a>
{% endif %}
<form action="{{ url_for('delete_content', vanity=info.vanity) }}" method="post" style="display: inline;">
<button type="submit" class="btn">Delete</button>
</form>
{% endif %}
</div>
</body>
</html>

View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Content</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #1a1a1a;
color: #f0f0f0;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #4CAF50;
}
#editor {
width: 100%;
height: 400px;
font-family: monospace;
font-size: 14px;
background-color: #2a2a2a;
color: #f0f0f0;
border: 1px solid #4CAF50;
padding: 10px;
box-sizing: border-box;
}
input[type="submit"] {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
font-size: 16px;
margin-top: 10px;
}
input[type="submit"]:hover {
background-color: #45a049;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"></script>
</head>
<body>
<div class="container">
<h1>Edit Content</h1>
<form method="post">
<div id="editor">{{ content }}</div>
<input type="hidden" name="content" id="hidden-content">
<input type="submit" value="Save" onclick="updateContent()">
</form>
</div>
<script>
var editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/text");
function updateContent() {
document.getElementById('hidden-content').value = editor.getValue();
}
</script>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Password</title>
<style>
/* Add your styles here */
</style>
</head>
<body>
<div class="container">
<h2>Edit Password</h2>
<form method="POST">
<input type="password" name="new_password" placeholder="Enter new password">
<input type="hidden" name="action" value="update">
<button type="submit">Update Password</button>
</form>
<form method="POST">
<input type="hidden" name="action" value="remove">
<button type="submit">Remove Password</button>
</form>
<a href="{{ url_for('user_files', username=current_user.username) }}">Cancel</a>
</div>
</body>
</html>

165
templates/file_info.html Normal file
View File

@ -0,0 +1,165 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ filename }} - sxbin</title>
<meta property="og:title" content="{{ filename }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="File size: {{ file_size|filesizeformat }} | Uploaded by: {{ username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}{% if is_private %} | Password Protected{% endif %}">
{% if is_embeddable %}
<meta property="og:image" content="{{ raw_url }}">
{% endif %}
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #e0e0e0;
background-color: #1e1e1e;
margin: 0;
padding: 0;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h2 {
color: #4CAF50;
}
.info-item {
margin-bottom: 10px;
}
.embed-container {
margin-top: 20px;
margin-bottom: 20px;
}
.embed-container img, .embed-container embed {
max-width: 100%;
max-height: 600px;
display: block;
margin: 0 auto;
}
.btn-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
}
.btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #45a049;
}
.home-button {
position: fixed;
top: 20px;
left: 20px;
font-size: 24px;
color: #4CAF50;
text-decoration: none;
}
.home-button:hover {
color: #45a049;
}
#theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background-color: #333;
color: #fff;
border: none;
padding: 5px 10px;
cursor: pointer;
border-radius: 4px;
}
footer {
text-align: center;
margin-top: 20px;
padding: 10px;
background-color: #2a2a2a;
color: #f0f0f0;
}
.edit-btn, .delete-btn {
background-color: #f44336;
}
.edit-btn:hover, .delete-btn:hover {
background-color: #d32f2f;
}
</style>
</head>
<body>
<a href="/" class="home-button">&#8962;</a>
<div class="container">
<h2>{{ filename }}</h2>
<div class="info-item"><strong>File size:</strong> {{ file_size|filesizeformat }}</div>
<div class="info-item"><strong>Uploaded by:</strong> {{ username }}</div>
<div class="info-item"><strong>Date:</strong> {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
<div class="info-item"><strong>File type:</strong> {{ filename.split('.')[-1].upper() if '.' in filename else 'Unknown' }}</div>
{% if is_embeddable %}
<div class="embed-container">
{% if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.svg')) %}
<img src="{{ raw_url }}" alt="{{ filename }}">
{% elif filename.lower().endswith('.pdf') %}
<embed src="{{ raw_url }}" type="application/pdf" width="100%" height="600px">
{% endif %}
</div>
{% endif %}
<div class="btn-container">
<a href="{{ file_url }}/download{% if password %}/{{ password }}{% endif %}" class="btn">Download</a>
<a href="{{ raw_url }}" class="btn">View Raw</a>
{% if current_user.is_authenticated and current_user.id == user_id %}
{% if filename.lower().endswith(('.txt', '.html', '.css', '.js', '.py', '.md')) or '.' not in filename %}
<a href="{{ url_for('edit_content', vanity=vanity) }}" class="btn edit-btn">Edit</a>
{% endif %}
<form action="{{ url_for('delete_content', vanity=vanity) }}" method="post" style="display: inline;">
<button type="submit" class="btn delete-btn" onclick="return confirm('Are you sure you want to delete this file?')">Delete</button>
</form>
{% endif %}
</div>
</div>
<button id="theme-toggle">Toggle Theme</button>
<footer>
<p>
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
</p>
</footer>
<script>
const themeToggle = document.getElementById('theme-toggle');
const body = document.body;
themeToggle.addEventListener('click', () => {
body.classList.toggle('light-mode');
localStorage.setItem('theme', body.classList.contains('light-mode') ? 'light' : 'dark');
});
// Check for saved theme preference
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
body.classList.add('light-mode');
}
</script>
</body>
</html>

View File

@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>aCloud - File Sharing Service</title> <title>sxbin - File Sharing Service</title>
<style> <style>
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
@ -47,40 +48,54 @@
margin: 4px 2px; margin: 4px 2px;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
} }
.upload-form { .upload-options button:hover {
display: none; transform: scale(1.05);
background-color: #2a2a2a; box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
padding: 20px;
border-radius: 8px;
max-width: 400px;
width: 100%;
} }
.upload-form.active { .upload-options button:active {
display: block; transform: scale(0.95);
} }
.upload-form input[type="text"], .upload-options button::after {
.upload-form input[type="file"], content: '';
.upload-form textarea { position: absolute;
width: 100%; top: 50%;
padding: 10px; left: 50%;
margin: 10px 0; width: 5px;
border-radius: 4px; height: 5px;
border: 1px solid #4CAF50; background: rgba(255, 255, 255, .5);
background-color: #333; opacity: 0;
color: #f0f0f0; border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
} }
.upload-form button { @keyframes ripple {
background-color: #4CAF50; 0% {
color: white; transform: scale(0, 0);
padding: 10px 20px; opacity: 1;
border: none;
border-radius: 4px;
cursor: pointer;
} }
.result { 20% {
margin-top: 10px; transform: scale(25, 25);
color: #4CAF50; opacity: 1;
}
100% {
opacity: 0;
transform: scale(40, 40);
}
}
.upload-options button:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 0.5s;
} }
.footer { .footer {
text-align: center; text-align: center;
@ -94,6 +109,11 @@
overflow: hidden; overflow: hidden;
font-size: 1.2em; font-size: 1.2em;
margin-bottom: 20px; margin-bottom: 20px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
} }
.cursor { .cursor {
display: inline-block; display: inline-block;
@ -107,13 +127,209 @@
50% { opacity: 1; } 50% { opacity: 1; }
100% { opacity: 0; } 100% { opacity: 0; }
} }
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #2a2a2a;
margin: 15% auto;
padding: 20px 30px;
border: 1px solid #4CAF50;
width: 50%;
max-width: 500px;
border-radius: 8px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #4CAF50;
text-decoration: none;
cursor: pointer;
}
.upload-area {
border: 2px dashed #4CAF50;
border-radius: 8px;
padding: 20px;
text-align: center;
cursor: pointer;
margin-bottom: 20px;
width: calc(100% - 44px);
}
.upload-area:hover {
background-color: #2d2d2d;
}
#fileInput {
display: none;
}
.progress-bar {
width: 100%;
background-color: #333;
border-radius: 4px;
margin-top: 10px;
}
.progress {
width: 0%;
height: 20px;
background-color: #4CAF50;
border-radius: 4px;
text-align: center;
line-height: 20px;
color: white;
}
textarea, input[type="text"], input[type="file"], input[type="password"] {
width: calc(100% - 20px);
padding: 10px;
margin: 10px 0;
background-color: #333;
border: 1px solid #4CAF50;
color: #f0f0f0;
border-radius: 4px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.file-group {
border: 1px solid #4CAF50;
border-radius: 5px;
margin-bottom: 10px;
overflow: hidden;
}
.file-group-header {
background-color: #4CAF50;
color: white;
padding: 10px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-group-header span:first-child {
margin-right: 10px;
}
.file-group-header span:nth-child(2) {
flex-grow: 1;
text-align: left;
}
.file-group-content {
padding: 10px;
display: none;
}
.file-group-content.show {
display: block;
}
.other-links-btn {
background-color: #45a049;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}
.other-links {
margin-top: 10px;
display: none;
}
.other-links.show {
display: block;
}
.global-drop-area {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
justify-content: center;
align-items: center;
}
.global-drop-box {
border: 3px dashed #4CAF50;
border-radius: 20px;
padding: 50px;
text-align: center;
background-color: rgba(0, 0, 0, 0.7);
color: white;
}
.instant-upload-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
justify-content: center;
align-items: center;
}
.instant-upload-result {
background-color: #2a2a2a;
padding: 20px;
border-radius: 10px;
position: relative;
max-width: 80%;
}
.close-modal {
position: absolute;
top: 10px;
right: 15px;
font-size: 24px;
cursor: pointer;
color: #aaa;
}
.close-modal:hover {
color: #fff;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<nav> <nav>
{% if user %}
<a href="{{ url_for('user_files', username=user.username) }}">View Dashboard</a>
<a href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('login') }}">Login</a> <a href="{{ url_for('login') }}">Login</a>
<a href="{{ url_for('register') }}">Register</a> <a href="{{ url_for('register') }}">Register</a>
{% endif %}
</nav> </nav>
<div class="typewriter-container"> <div class="typewriter-container">
@ -121,57 +337,97 @@
</div> </div>
<div class="upload-options"> <div class="upload-options">
<button onclick="showForm('text')">Upload Text</button> <button onclick="openModal('textModal')">Upload Text</button>
<button onclick="showForm('file')">Upload File</button> <button onclick="openModal('fileModal')">Upload File</button>
<button onclick="showForm('folder')">Upload Folder</button> <button onclick="openModal('urlModal')">Shorten URL</button>
<button onclick="showForm('url')">Shorten URL</button>
</div> </div>
<div id="uploadFormContainer"> </div>
<div id="textForm" class="upload-form">
<div id="textModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('textModal')">&times;</span>
<h2>Upload Text</h2> <h2>Upload Text</h2>
<form> <textarea id="textContent" rows="4" placeholder="Enter text here..."></textarea>
<textarea name="content" rows="4" placeholder="Enter text here..."></textarea> <div>
<button type="button" onclick="uploadText()">Upload Text</button> <input type="checkbox" id="isPrivate" name="isPrivate">
</form> <label for="isPrivate">Add password protection</label>
<div id="textResult" class="result"></div>
</div> </div>
<div id="fileForm" class="upload-form"> <div id="passwordField" style="display: none;">
<input type="password" id="textPassword" placeholder="Enter password">
</div>
<button onclick="uploadText()">Upload Text</button>
<div id="textResult"></div>
</div>
</div>
<div id="fileModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('fileModal')">&times;</span>
<h2>Upload File</h2> <h2>Upload File</h2>
<form enctype="multipart/form-data"> <div id="uploadArea" class="upload-area">
<input type="file" name="file" /> <p>Drag and drop files here or click to select files</p>
<button type="button" onclick="uploadFile()">Upload File</button> <input type="file" id="fileInput" multiple>
</form>
<div id="fileResult" class="result"></div>
</div> </div>
<div id="folderForm" class="upload-form"> <div id="selectedFiles"></div>
<h2>Upload Folder</h2> <div>
<form enctype="multipart/form-data"> <input type="checkbox" id="fileIsPrivate" name="fileIsPrivate">
<input type="file" name="file" webkitdirectory directory /> <label for="fileIsPrivate">Add password protection</label>
<button type="button" onclick="uploadFolder()">Upload Folder</button>
</form>
<div id="folderResult" class="result"></div>
</div> </div>
<div id="urlForm" class="upload-form"> <div id="filePasswordField" style="display: none;">
<input type="password" id="filePassword" placeholder="Enter password">
</div>
<button onclick="uploadSelectedFiles()">Upload</button>
<div class="progress-bar">
<div id="progressBar" class="progress"></div>
</div>
<div id="fileResult"></div>
</div>
</div>
<div id="urlModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('urlModal')">&times;</span>
<h2>Shorten URL</h2> <h2>Shorten URL</h2>
<form> <input type="text" id="urlInput" placeholder="Enter URL here...">
<input type="text" name="url" placeholder="Enter URL here..." /> <div>
<button type="button" onclick="shortenUrl()">Shorten URL</button> <input type="checkbox" id="urlIsPrivate" name="urlIsPrivate">
</form> <label for="urlIsPrivate">Add password protection</label>
<div id="urlResult" class="result"></div>
</div> </div>
<div id="urlPasswordField" style="display: none;">
<input type="password" id="urlPassword" placeholder="Enter password">
</div>
<button onclick="shortenUrl()">Shorten URL</button>
<div id="urlResult"></div>
</div>
</div>
<div class="global-drop-area">
<div class="global-drop-box">
<h2>Drop file to instantly upload</h2>
</div>
</div>
<div class="instant-upload-overlay">
<div class="instant-upload-result">
<span class="close-modal">&times;</span>
<h3>File Uploaded</h3>
<p>Direct download URL: <a id="directDownloadUrl" href="#" target="_blank"></a></p>
<p>Normal URL: <a id="normalUrl" href="#" target="_blank"></a></p>
</div> </div>
</div> </div>
<footer class="footer"> <footer class="footer">
<p>Source code available on: <p>Source code available on:
<a href="https://github.com/realcgcristi/order" target="_blank">GitHub (not up-to-date)</a> |
<a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git (main)</a> <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git (main)</a> |
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
</p> </p>
</footer> </footer>
<script> <script>
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const message1 = "Welcome to aCloud."; const message1 = "Welcome to sxbin.gay";
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin."; const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
const typewriterTextElement = document.getElementById('typewriter-text'); const typewriterTextElement = document.getElementById('typewriter-text');
const cursorElement = document.getElementById('cursor'); const cursorElement = document.getElementById('cursor');
@ -188,7 +444,6 @@
typewriterTextElement.innerHTML += message[index]; typewriterTextElement.innerHTML += message[index];
} }
index++; index++;
updateCursorPosition();
setTimeout(typeCharacter, typingSpeed); setTimeout(typeCharacter, typingSpeed);
} else if (callback) { } else if (callback) {
setTimeout(callback, typingSpeed); setTimeout(callback, typingSpeed);
@ -198,115 +453,381 @@
typeCharacter(); typeCharacter();
} }
function updateCursorPosition() {
const textRect = typewriterTextElement.getBoundingClientRect();
cursorElement.style.left = (textRect.width + textRect.left - cursorElement.offsetWidth) + 'px';
}
typeMessage(message1, function() { typeMessage(message1, function() {
typeMessage(message2); typeMessage(message2);
}); });
});; });
let isFileModalOpen = false;
document.getElementById('uploadForm').onsubmit = function(e) { function openModal(modalId) {
const modal = document.getElementById(modalId);
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
modal.style.display = "block";
button.classList.add('pulse');
// Add ripple effect
button.classList.add('ripple');
setTimeout(() => {
button.classList.remove('ripple');
}, 1000);
// Add fade-in animation to modal
modal.style.opacity = 0;
let opacity = 0;
const fadeIn = setInterval(() => {
if (opacity < 1) {
opacity += 0.1;
modal.style.opacity = opacity;
} else {
clearInterval(fadeIn);
}
}, 30);
if (modalId === 'fileModal') {
isFileModalOpen = true;
}
}
function closeModal(modalId) {
const modal = document.getElementById(modalId);
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
button.classList.remove('pulse');
// Add fade-out animation to modal
let opacity = 1;
const fadeOut = setInterval(() => {
if (opacity > 0) {
opacity -= 0.1;
modal.style.opacity = opacity;
} else {
clearInterval(fadeOut);
modal.style.display = "none";
}
}, 30);
if (modalId === 'fileModal') {
isFileModalOpen = false;
}
}
window.onclick = function(event) {
if (event.target.className === "modal") {
event.target.style.display = "none";
if (event.target.id === 'fileModal') {
isFileModalOpen = false;
}
}
}
document.getElementById('isPrivate').addEventListener('change', function() {
document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none';
});
document.getElementById('fileIsPrivate').addEventListener('change', function() {
document.getElementById('filePasswordField').style.display = this.checked ? 'block' : 'none';
});
document.getElementById('urlIsPrivate').addEventListener('change', function() {
document.getElementById('urlPasswordField').style.display = this.checked ? 'block' : 'none';
});
function uploadText() {
const content = document.getElementById('textContent').value;
const isPrivate = document.getElementById('isPrivate').checked;
const password = isPrivate ? document.getElementById('textPassword').value : null;
const data = {
content: content,
password: password
};
fetch('/upload/pastebin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.success) {
const simpleUrl = `${window.location.origin}/${data.vanity}`;
document.getElementById('textResult').innerHTML = `Pastebin created. Access it <a href="${simpleUrl}" target="_blank">${simpleUrl}</a>`;
} else {
document.getElementById('textResult').innerHTML = `Error: ${data.error}`;
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('textResult').innerHTML = `An error occurred: ${error.message}`;
});
}
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar');
const selectedFiles = document.getElementById('selectedFiles');
let filesToUpload = [];
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault(); e.preventDefault();
var formData = new FormData(this); uploadArea.style.backgroundColor = '#2d2d2d';
var xhr = new XMLHttpRequest(); });
uploadArea.addEventListener('dragleave', () => {
uploadArea.style.backgroundColor = '';
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.backgroundColor = '';
handleFiles(e.dataTransfer.files);
});
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
function handleFiles(files) {
filesToUpload = Array.from(files);
updateSelectedFilesList();
}
function updateSelectedFilesList() {
selectedFiles.innerHTML = filesToUpload.map(file => `<div>${file.name}</div>`).join('');
}
function uploadSelectedFiles() {
const isPrivate = document.getElementById('fileIsPrivate').checked;
const password = isPrivate ? document.getElementById('filePassword').value : null;
filesToUpload.forEach(file => uploadFile(file, isPrivate, password));
}
function uploadFile(file, isPrivate, password) {
if (isPrivate && (password === 'info' || password === 'download')) {
document.getElementById('fileResult').innerHTML += `Error: Password cannot be 'info' or 'download'<br>`;
return;
}
const formData = new FormData();
formData.append('file', file);
if (isPrivate && password) {
formData.append('password', password);
}
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload/file', true); xhr.open('POST', '/upload/file', true);
xhr.upload.onprogress = function(e) { xhr.upload.onprogress = (e) => {
if (e.lengthComputable) { if (e.lengthComputable) {
var percentComplete = (e.loaded / e.total) * 100; const percentComplete = (e.loaded / e.total) * 100;
document.getElementById('progressBar').style.display = 'block'; progressBar.style.width = percentComplete + '%';
document.getElementById('progressBar').value = percentComplete; progressBar.textContent = percentComplete.toFixed(2) + '%';
} }
}; };
xhr.onload = function() { xhr.onload = function() {
if (xhr.status === 200) { if (xhr.status === 200) {
alert('Upload complete!'); const response = JSON.parse(xhr.responseText);
document.getElementById('progressBar').style.display = 'none'; if (response.success) {
let resultHtml = `
<div class="file-group">
<div class="file-group-header" onclick="toggleFileGroup(this)">
<span></span>
<span>${file.name}</span>
<button class="other-links-btn" onclick="toggleOtherLinks(event, this)">Other Links</button>
</div>
<div class="file-group-content show">
<p>File uploaded: <a href="${response.url}" target="_blank">${response.url}</a></p>
<div class="other-links">
`;
if (isPrivate) {
resultHtml += `
<p>Password-protected link: <a href="${response.url}/${password}" target="_blank">${response.url}/${password}</a></p>
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
<p>Password-protected direct download link: <a href="${response.download_url}/${password}" target="_blank">${response.download_url}/${password}</a></p>
`;
} else { } else {
alert('Upload failed.'); resultHtml += `
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
`;
} }
resultHtml += `
</div>
</div>
</div>
`;
document.getElementById('fileResult').innerHTML += resultHtml;
} else {
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
}
} else {
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
}
progressBar.style.width = '0%';
progressBar.textContent = '';
}; };
xhr.onerror = function() {
console.error('Error:', xhr.statusText);
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
progressBar.style.width = '0%';
progressBar.textContent = '';
};
xhr.send(formData); xhr.send(formData);
};
function showForm(type) {
document.querySelectorAll('.upload-form').forEach(form => {
form.classList.remove('active');
});
document.getElementById(type + 'Form').classList.add('active');
} }
function uploadText() { function toggleFileGroup(header) {
const content = document.querySelector('#textForm textarea').value; const content = header.nextElementSibling;
fetch('/upload/pastebin', { const arrow = header.querySelector('span:first-child');
method: 'POST', const filename = header.querySelector('span:nth-child(2)');
headers: { content.classList.toggle('show');
'Content-Type': 'application/x-www-form-urlencoded', if (content.classList.contains('show')) {
}, arrow.textContent = '▼';
body: new URLSearchParams({ content }), filename.style.display = 'inline';
}) } else {
.then(response => response.json()) arrow.textContent = '▶';
.then(data => { filename.style.display = 'inline';
document.getElementById('textResult').innerHTML = `Text uploaded. Access it <a href="/${data.vanity}">here</a>.`; }
});
} }
function uploadFile() { function toggleOtherLinks(event, button) {
const formData = new FormData(); event.stopPropagation();
formData.append('file', document.querySelector('#fileForm input[type="file"]').files[0]); const otherLinks = button.closest('.file-group-header').nextElementSibling.querySelector('.other-links');
fetch('/upload/file', { otherLinks.classList.toggle('show');
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
document.getElementById('fileResult').innerHTML = `File uploaded. Download it <a href="/download/${data.vanity}">here</a>.`;
});
}
function uploadFolder() {
const files = document.querySelector('#folderForm input[type="file"]').files;
if (files.length === 0) {
alert('Please select a folder.');
return;
}
const formData = new FormData();
for (const file of files) {
formData.append('file', file);
}
fetch('/upload/folder', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
document.getElementById('folderResult').innerHTML = `Folder uploaded. View its contents <a href="/${data.vanity}">here</a>.`;
});
} }
function shortenUrl() { function shortenUrl() {
const url = document.querySelector('#urlForm input[name="url"]').value; const url = document.getElementById('urlInput').value;
const isPrivate = document.getElementById('urlIsPrivate').checked;
const password = isPrivate ? document.getElementById('urlPassword').value : null;
const data = {
url: url,
password: password
};
fetch('/shorten', { fetch('/shorten', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/json',
}, },
body: new URLSearchParams({ url }), body: JSON.stringify(data),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.success) {
const shortUrl = `${window.location.origin}/${data.vanity}`;
let resultHtml = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
if (isPrivate) {
resultHtml += `<br>Password-protected link: <a href="${shortUrl}/${password}" target="_blank">${shortUrl}/${password}</a>`;
}
document.getElementById('urlResult').innerHTML = resultHtml;
} else {
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('urlResult').innerHTML = `An error occurred: ${error.message}`;
});
}
// Global drag and drop
document.addEventListener('dragover', function(e) {
e.preventDefault();
if (!isFileModalOpen) {
document.querySelector('.global-drop-area').style.display = 'flex';
}
});
document.addEventListener('dragleave', function(e) {
if (e.clientX === 0 || e.clientY === 0) {
document.querySelector('.global-drop-area').style.display = 'none';
}
});
document.addEventListener('drop', function(e) {
e.preventDefault();
document.querySelector('.global-drop-area').style.display = 'none';
if (!isFileModalOpen && e.dataTransfer.files.length > 0) {
instantUploadFile(e.dataTransfer.files[0]);
}
});
function instantUploadFile(file) {
const formData = new FormData();
formData.append('file', file);
fetch('/upload/file', {
method: 'POST',
body: formData
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="/${data.vanity}">here</a>.`; if (data.success) {
document.getElementById('directDownloadUrl').href = data.download_url;
document.getElementById('directDownloadUrl').textContent = data.download_url;
document.getElementById('normalUrl').href = data.url;
document.getElementById('normalUrl').textContent = data.url;
document.querySelector('.instant-upload-overlay').style.display = 'flex';
} else {
alert('Error uploading file: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while uploading the file');
}); });
} }
// Add this new function to close the modal
function closeInstantUploadModal() {
document.querySelector('.instant-upload-overlay').style.display = 'none';
}
// Add event listeners for closing the modal
document.addEventListener('DOMContentLoaded', function() {
const closeModalButton = document.querySelector('.close-modal');
const instantUploadOverlay = document.querySelector('.instant-upload-overlay');
closeModalButton.addEventListener('click', closeInstantUploadModal);
instantUploadOverlay.addEventListener('click', function(event) {
if (event.target === instantUploadOverlay) {
closeInstantUploadModal();
}
});
});
// Paste functionality for file upload
document.addEventListener('paste', function(e) {
if (document.getElementById('fileModal').style.display === 'block') {
const items = e.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (items[i].kind === 'file') {
const file = items[i].getAsFile();
filesToUpload.push(file);
updateSelectedFilesList();
}
}
}
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,150 @@
<h2>Login</h2> <!DOCTYPE html>
<form method="post"> <html lang="en">
<input type="text" name="username" placeholder="Username" required> <head>
<input type="password" name="password" placeholder="Password" required> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - sxbin</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #1a1a1a;
color: #f0f0f0;
}
.content {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background-color: #2a2a2a;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
width: 300px;
}
h2 {
text-align: center;
color: #4CAF50;
}
form {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 5px;
}
input[type="text"],
input[type="password"] {
padding: 8px;
margin-bottom: 10px;
border-radius: 4px;
border: 1px solid #4CAF50;
background-color: #333;
color: #f0f0f0;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
.remember-me {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.remember-me input {
margin-right: 5px;
}
p {
text-align: center;
margin-top: 15px;
}
a {
color: #4CAF50;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.back-button {
position: absolute;
top: 20px;
left: 20px;
font-size: 24px;
color: #4CAF50;
text-decoration: none;
}
.back-button:hover {
color: #45a049;
}
.footer {
text-align: center;
padding: 10px;
background-color: #2a2a2a;
color: #f0f0f0;
}
.error-message {
background-color: #ff6b6b;
color: white;
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
display: none;
}
</style>
</head>
<body>
<a href="/" class="back-button">&#8592;</a>
<div class="content">
<div class="container">
<h2>Login to sxbin</h2>
<div id="error-message" class="error-message"></div>
<form id="login-form" method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<label>
<input type="checkbox" name="remember"> Remember me
</label>
<input type="submit" value="Login"> <input type="submit" value="Login">
</form> </form>
<p>Don't have an account? <a href="{{ url_for('register') }}">Register</a></p>
</div>
</div>
<script>
document.getElementById('login-form').addEventListener('submit', function(e) {
e.preventDefault();
fetch('/login', {
method: 'POST',
body: new FormData(this),
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = data.redirect;
} else {
document.getElementById('error-message').textContent = data.error;
document.getElementById('error-message').style.display = 'block';
}
});
});
</script>
</body>
</html>

22
templates/og_file.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ filename }} - sxbin</title>
<meta property="og:title" content="{{ filename }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="File size: {{ file_size|filesizeformat }} | Uploaded by: {{ username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
{% if is_embeddable %}
<meta property="og:image" content="{{ file_url }}/raw">
{% endif %}
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
</head>
<body>
<script>
window.location.href = "{{ file_url }}";
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pastebin {{ vanity }} - sxbin</title>
<meta property="og:title" content="Pastebin {{ vanity }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="{{ first_lines|truncate(200) }}">
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
</head>
<body>
<h1>Pastebin {{ vanity }}</h1>
<pre>{{ first_lines }}</pre>
<p>Created by: {{ username }}</p>
<p>Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shortened URL - sxbin</title>
<meta property="og:title" content="Shortened URL - sxbin">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="Shortened URL {% if is_private %}(Password Protected){% endif %} | Created by: {{ username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
</head>
<body>
<h1>Shortened URL</h1>
<p>Original URL: {{ long_url }}</p>
<p>Created by: {{ username }}</p>
<p>Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
{% if is_private %}
<p>This URL is password protected.</p>
{% endif %}
<a href="{{ url_for('redirect_vanity', vanity=vanity) }}">Access the URL</a>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Protected Content - sxbin</title>
<meta property="og:title" content="Password Protected Content - sxbin">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="This {{ content_type }} is password protected. Enter the password to view.">
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
<style>
/* Add your styles here */
</style>
</head>
<body>
<h1>Password Protected Content</h1>
<p>This {{ content_type }} is password protected. Please enter the password to view.</p>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST">
<input type="password" name="password" required>
<button type="submit">Submit</button>
</form>
</body>
</html>

View File

@ -3,52 +3,230 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pastebin Content</title> <title>Pastebin {{ vanity }} - sxbin</title>
<meta property="og:title" content="Pastebin {{ vanity }} - sxbin">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="Pastebin{% if is_private %} (Password Protected){% endif %} | Uploaded by: {{ content.username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
<style> <style>
{{ css|safe }} :root {
/* Add any additional styles here */ --bg-color: #1e1e1e;
--text-color: #e0e0e0;
--highlight-bg: #2d2d2d;
--highlight-border: #444;
--button-bg: #3a3a3a;
--button-text: #e0e0e0;
}
.light-mode {
--bg-color: #ffffff;
--text-color: #333333;
--highlight-bg: #f8f8f8;
--highlight-border: #ccc;
--button-bg: #f0f0f0;
--button-text: #333333;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
transition: background-color 0.3s, color 0.3s;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1, h2 {
margin-bottom: 20px;
}
.button-container { .button-container {
margin-bottom: 10px; margin-bottom: 20px;
} }
.button-container button {
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 10px 15px;
margin-right: 10px; margin-right: 10px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
} }
button:hover {
opacity: 0.8;
}
.highlight { .highlight {
background-color: #f8f8f8; background-color: var(--highlight-bg);
border: 1px solid #ccc; border: 1px solid var(--highlight-border);
border-radius: 4px; border-radius: 4px;
padding: 1em; padding: 1em;
overflow: auto; overflow: auto;
position: relative;
} }
.highlight pre {
color: var(--text-color);
white-space: pre;
word-wrap: normal;
overflow-x: auto;
margin: 0;
padding: 0;
}
.highlight .linenos {
color: #999;
text-align: right;
padding-right: 10px;
border-right: 1px solid var(--highlight-border);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.highlight .code {
padding-left: 10px;
}
#theme-toggle {
position: fixed;
top: 20px;
right: 20px;
}
a {
color: var(--text-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.btn-container {
display: flex;
gap: 10px;
margin-top: 20px;
}
.btn {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 10px 15px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s, opacity 0.3s;
text-decoration: none;
display: inline-block;
flex: 1;
text-align: center;
font-size: 14px;
}
.btn:hover {
opacity: 0.8;
}
form {
flex: 1;
display: flex;
}
form .btn {
width: 100%;
}
.home-button {
position: fixed;
top: 20px;
left: 20px;
font-size: 24px;
color: var(--text-color);
text-decoration: none;
z-index: 1001;
}
.home-button:hover {
color: #4CAF50;
}
{{ css|safe }}
</style> </style>
</head> </head>
<body> <body>
<h1>Pastebin Content</h1> <a href="/" class="home-button">&#8962;</a>
<div class="container">
<h2>Content</h2>
<p>Uploaded by: {{ 'Anonymous' if content.username == 'None' else content.username }}</p>
<p>Created at: {{ created_at }}</p> <p>Created at: {{ created_at }}</p>
<p>Detected Language: {{ language }}</p>
<div class="button-container">
<button id="copy-button" onclick="copyToClipboard()">Copy Raw</button>
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" target="_blank"><button>View Raw</button></a>
</div>
<div class="highlight"> <div class="highlight">
{{ content|safe }} {{ highlighted_content|safe }}
</div> </div>
<div class="btn-container">
<button onclick="copyToClipboard()" class="btn">Copy</button>
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" class="btn">View Raw</a>
{% if current_user.is_authenticated and current_user.id == content.user_id %}
<a href="{{ url_for('edit_content', vanity=vanity) }}" class="btn">Edit</a>
{% if is_private %}
<button onclick="openEditPasswordModal()" class="btn">Edit Password</button>
{% else %}
<button onclick="openAddPasswordModal()" class="btn">Add Password</button>
{% endif %}
<form action="{{ url_for('delete_content', vanity=vanity) }}" method="post">
<button type="submit" class="btn">Delete</button>
</form>
{% endif %}
</div>
</div>
<button id="theme-toggle">Toggle Theme</button>
<footer style="text-align: center; margin-top: 20px; padding: 10px; background-color: #2a2a2a; color: #f0f0f0;">
<p>
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
</p>
</footer>
<script> <script>
const rawContent = {{ raw_content|tojson }}; const rawContent = {{ raw_content|tojson }};
function copyToClipboard() { function copyToClipboard() {
navigator.clipboard.writeText(rawContent).then(() => { navigator.clipboard.writeText(rawContent).then(() => {
const copyButton = document.getElementById("copy-button"); alert('Copied to clipboard!');
const originalText = copyButton.textContent;
copyButton.textContent = "Copied!";
setTimeout(() => {
copyButton.textContent = originalText;
}, 2000);
}).catch(err => { }).catch(err => {
console.error('Failed to copy text: ', err); console.error('Failed to copy text: ', err);
}); });
} }
const themeToggle = document.getElementById('theme-toggle');
const html = document.documentElement;
function toggleTheme() {
html.classList.toggle('light-mode');
localStorage.setItem('lightMode', html.classList.contains('light-mode'));
}
themeToggle.addEventListener('click', toggleTheme);
// Check for saved theme preference
if (localStorage.getItem('lightMode') === 'true') {
html.classList.add('light-mode');
}
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,171 @@
<h2>Register</h2> <!DOCTYPE html>
<form method="post"> <html lang="en">
<input type="text" name="username" placeholder="Username" required> <head>
<input type="password" name="password" placeholder="Password" required> <meta charset="UTF-8">
<input type="submit" value="Register"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - sxbin</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #1a1a1a;
color: #f0f0f0;
}
.content {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background-color: #2a2a2a;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
width: 300px;
}
h2 {
text-align: center;
color: #4CAF50;
}
form {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 5px;
}
input[type="text"],
input[type="password"] {
padding: 8px;
margin-bottom: 10px;
border-radius: 4px;
border: 1px solid #4CAF50;
background-color: #333;
color: #f0f0f0;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
p {
text-align: center;
margin-top: 15px;
}
a {
color: #4CAF50;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.back-button {
position: absolute;
top: 20px;
left: 20px;
font-size: 24px;
color: #4CAF50;
text-decoration: none;
}
.back-button:hover {
color: #45a049;
}
.footer {
text-align: center;
padding: 10px;
background-color: #2a2a2a;
color: #f0f0f0;
}
.error-message {
background-color: #ff6b6b;
color: white;
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
display: none;
}
.password-requirement {
font-size: 0.9em;
margin-top: 5px;
transition: color 0.3s ease;
}
.requirement-not-met {
color: #ff6b6b;
}
.requirement-met {
color: #4CAF50;
}
</style>
</head>
<body>
<a href="/" class="back-button">&#8592;</a>
<div class="content">
<div class="container">
<h2>Register for sxbin</h2>
<div id="error-message" class="error-message"></div>
<form id="register-form" method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<div id="password-length" class="password-requirement requirement-not-met">At least 5 characters</div>
<div id="password-uppercase" class="password-requirement requirement-not-met">At least one uppercase letter</div>
<input type="submit" value="Register" id="submit-button" disabled>
</form> </form>
<p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
</div>
</div>
<script>
const passwordInput = document.getElementById('password');
const lengthRequirement = document.getElementById('password-length');
const uppercaseRequirement = document.getElementById('password-uppercase');
const submitButton = document.getElementById('submit-button');
passwordInput.addEventListener('input', function() {
const password = this.value;
const meetsLengthRequirement = password.length >= 5;
const meetsUppercaseRequirement = /[A-Z]/.test(password);
lengthRequirement.className = meetsLengthRequirement ? 'password-requirement requirement-met' : 'password-requirement requirement-not-met';
uppercaseRequirement.className = meetsUppercaseRequirement ? 'password-requirement requirement-met' : 'password-requirement requirement-not-met';
submitButton.disabled = !(meetsLengthRequirement && meetsUppercaseRequirement);
});
document.getElementById('register-form').addEventListener('submit', function(e) {
e.preventDefault();
fetch('/register', {
method: 'POST',
body: new FormData(this),
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = data.redirect;
} else {
document.getElementById('error-message').textContent = data.error;
document.getElementById('error-message').style.display = 'block';
}
});
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -16,12 +16,19 @@
h2 { h2 {
color: #4CAF50; color: #4CAF50;
} }
ul { .file-list {
list-style-type: none; display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 0; padding: 0;
list-style-type: none;
} }
li { .file-item {
margin-bottom: 10px; background-color: #2a2a2a;
padding: 10px;
border-radius: 5px;
display: flex;
align-items: center;
} }
a { a {
color: #4CAF50; color: #4CAF50;
@ -30,13 +37,34 @@
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
} }
.folder {
font-weight: bold;
}
.file-icon {
margin-right: 5px;
}
</style> </style>
</head> </head>
<body> <body>
<h2>{{ username }}'s Files</h2> <h2>{{ username }}'s Files</h2>
<ul> <p>Current folder: {{ current_folder or 'Root' }}</p>
{% if parent_url is not none %}
<p><a href="{{ parent_url }}">Parent Directory</a></p>
{% endif %}
<ul class="file-list">
{% for folder in folders %}
<li class="file-item folder">
<span class="file-icon">📁</span>
<a href="{{ url_for('serve_user_page', username=username, filename=folder.path) }}">{{ folder.name }}</a>
</li>
{% endfor %}
{% for file in files %} {% for file in files %}
<li><a href="{{ url_for('download_folder_file', vanity=username, file_name=file) }}">{{ file }}</a></li> <li class="file-item">
<span class="file-icon">📄</span>
<a href="{{ url_for('serve_user_page', username=username, filename=file.path) }}">{{ file.name }}</a>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</body> </body>

View File

@ -1,27 +0,0 @@
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>&#80;&#121;&#116;&#104;&#111;&#110;&#32;&#73;&#68;&#69;</title><script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython_stdlib.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/mode/python/python.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/addon/hint/show-hint.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/addon/hint/python-hint.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/theme/monokai.min.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/addon/hint/show-hint.css"><style>body{background-color:#1e1e1e;color:#d4d4d4;font-family:'Consolas','Courier New',monospace;margin:0;padding:20px}h1{color:#569cd6}#code{width:100%;height:300px}#output{width:100%;height:200px;background-color:#1e1e1e;border:1px solid #3c3c3c;padding:10px;margin-top:10px;overflow-y:auto;font-family:'Consolas','Courier New',monospace}#run-button,#save-button{background-color:#0e639c;color:white;border:none;padding:10px 20px;margin-top:10px;cursor:pointer}#run-button:hover,#save-button:hover{background-color:#1177bb}.toolbar{margin-bottom:10px}.toolbar button{background-color:#3c3c3c;color:#d4d4d4;border:none;padding:5px 10px;margin-right:5px;cursor:pointer}.toolbar button:hover{background-color:#4e4e4e}</style></head><body onload="brython()"><h1>&#80;&#121;&#116;&#104;&#111;&#110;&#32;&#73;&#68;&#69;</h1><div class="toolbar"><button id="undo-button">&#85;&#110;&#100;&#111;</button><button id="redo-button">&#82;&#101;&#100;&#111;</button><button id="save-button">&#83;&#97;&#118;&#101;</button></div><textarea id="code" placeholder="&#69;&#110;&#116;&#101;&#114;&#32;&#121;&#111;&#117;&#114;&#32;&#80;&#121;&#116;&#104;&#111;&#110;&#32;&#99;&#111;&#100;&#101;&#32;&#104;&#101;&#114;&#101;"></textarea><br><button id="run-button">&#82;&#117;&#110;</button><div id="output"></div><script type="text/python">from browser import document,window
import sys
class _0x1234:
def __init__(_0x5678):_0x5678._0x9abc=document["output"]
def write(_0x5678,_0xdef0):_0x5678._0x9abc.innerHTML+=_0xdef0
sys.stdout=_0x1234()
sys.stderr=_0x1234()
def _0x2345(_0x6789):
document["output"].innerHTML=""
_0xabcd=window.editor.getValue()
try:exec(_0xabcd)
except Exception as _0xef01:print(f"Error: {str(_0xef01)}")
document["run-button"].bind("click",_0x2345)
def _0x3456(_0x789a):window.editor.undo()
def _0x4567(_0x89ab):window.editor.redo()
document["undo-button"].bind("click",_0x3456)
document["redo-button"].bind("click",_0x4567)
def _0x5678(_0x9abc):
_0xbcde=window.editor.getValue()
_0xcdef=window.Blob.new([_0xbcde],{'type':'text/plain'})
_0xdef0=window.URL.createObjectURL(_0xcdef)
_0xef01=document.createElement('a')
_0xef01.href=_0xdef0
_0xef01.download='python_code.py'
_0xef01.click()
window.URL.revokeObjectURL(_0xdef0)
document["save-button"].bind("click",_0x5678)</script><script>var _0x1234=CodeMirror.fromTextArea(document.getElementById("code"),{mode:"python",theme:"monokai",lineNumbers:!0,autoCloseBrackets:!0,matchBrackets:!0,indentUnit:4,tabSize:4,indentWithTabs:!1,extraKeys:{"Ctrl-Space":"autocomplete","Tab":function(_0x5678){_0x5678.somethingSelected()?_0x5678.indentSelection("add"):_0x5678.replaceSelection(_0x5678.getOption("indentWithTabs")?"\t":Array(_0x5678.getOption("indentUnit")+1).join(" "),"end","+input")}},hintOptions:{completeSingle:!1}});_0x1234.on("inputRead",function(_0x2345,_0x3456){if("+input"===_0x3456.origin){var _0x4567=_0x2345.getCursor(),_0x5678=_0x2345.getTokenAt(_0x4567);("variable"===_0x5678.type||"."===_0x5678.string)&&_0x2345.showHint({completeSingle:!1})}})</script></body></html>