Compare commits

...

25 Commits
a ... main

Author SHA1 Message Date
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: #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 708 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,7 +1,8 @@
## Text sharing, File sharing, link shortener.
### frontend and backend, easily configurable (thru editing the html files and the .py)
----------------
TODO
<iframe src="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/"> </iframe>
1. Introduction
</br>
</br>

1563
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 (
vanity TEXT PRIMARY KEY,
type TEXT NOT NULL,
data TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
user_id INTEGER,
is_private INTEGER DEFAULT 0,
password TEXT,
FOREIGN KEY (user_id) REFERENCES users (id)
);

View File

@ -66,7 +66,13 @@
</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>
</body>
</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 */
.highlight pre {
color: var(--text-color);
white-space: pre-wrap;
word-wrap: break-word;
}
/* Override Pygments styles for dark mode */
@ -154,42 +156,65 @@
</head>
<body>
<div class="container">
<h1>Content</h1>
{% if created_at %}
<p>Created at: {{ created_at }}</p>
{% endif %}
{% 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">
<h2>Content</h2>
{% if content %}
<p>Uploaded by: {{ content.username }}</p>
<p>Created at: {{ content.created_at }}</p>
{% if highlighted_content %}
{{ highlighted_content|safe }}
<style>{{ css|safe }}</style>
<div class="highlight">
{{ highlighted_content|safe }}
</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 %}
<pre>{{ content }}</pre>
<p>No content available</p>
{% endif %}
</div>
{% 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>
<button id="theme-toggle">Toggle Theme</button>
<script>
const rawContent = {{ raw_content|tojson }};
const rawContent = {{ raw_content|tojson|default('null') }};
const url = {{ url|tojson|default('null') }};
function copyToClipboard() {
navigator.clipboard.writeText(rawContent).then(() => {
const copyButton = document.getElementById("copy-button");
const originalText = copyButton.textContent;
copyButton.textContent = "Copied!";
setTimeout(() => {
copyButton.textContent = originalText;
}, 2000);
}).catch(err => {
console.error('Failed to copy text: ', err);
});
let textToCopy = rawContent;
if (url) {
textToCopy = url;
}
if (textToCopy) {
navigator.clipboard.writeText(textToCopy).then(() => {
alert('Copied to clipboard!');
}).catch(err => {
console.error('Failed to copy text: ', err);
});
} else {
console.error('No content to copy');
}
}
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>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>aCloud - File Sharing Service</title>
<title>sxbin - File Sharing Service</title>
<style>
body {
font-family: Arial, sans-serif;
@ -47,40 +48,54 @@
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.upload-form {
display: none;
background-color: #2a2a2a;
padding: 20px;
border-radius: 8px;
max-width: 400px;
width: 100%;
.upload-options button:hover {
transform: scale(1.05);
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
}
.upload-form.active {
display: block;
.upload-options button:active {
transform: scale(0.95);
}
.upload-form input[type="text"],
.upload-form input[type="file"],
.upload-form textarea {
width: 100%;
padding: 10px;
margin: 10px 0;
border-radius: 4px;
border: 1px solid #4CAF50;
background-color: #333;
color: #f0f0f0;
.upload-options button::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, .5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
}
.upload-form button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 1;
}
20% {
transform: scale(25, 25);
opacity: 1;
}
100% {
opacity: 0;
transform: scale(40, 40);
}
}
.result {
margin-top: 10px;
color: #4CAF50;
.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 {
text-align: center;
@ -94,6 +109,11 @@
overflow: hidden;
font-size: 1.2em;
margin-bottom: 20px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.cursor {
display: inline-block;
@ -107,13 +127,209 @@
50% { opacity: 1; }
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>
</head>
<body>
<div class="container">
<nav>
<a href="{{ url_for('login') }}">Login</a>
<a href="{{ url_for('register') }}">Register</a>
{% 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('register') }}">Register</a>
{% endif %}
</nav>
<div class="typewriter-container">
@ -121,58 +337,98 @@
</div>
<div class="upload-options">
<button onclick="showForm('text')">Upload Text</button>
<button onclick="showForm('file')">Upload File</button>
<button onclick="showForm('folder')">Upload Folder</button>
<button onclick="showForm('url')">Shorten URL</button>
<button onclick="openModal('textModal')">Upload Text</button>
<button onclick="openModal('fileModal')">Upload File</button>
<button onclick="openModal('urlModal')">Shorten URL</button>
</div>
<div id="uploadFormContainer">
<div id="textForm" class="upload-form">
<h2>Upload Text</h2>
<form>
<textarea name="content" rows="4" placeholder="Enter text here..."></textarea>
<button type="button" onclick="uploadText()">Upload Text</button>
</form>
<div id="textResult" class="result"></div>
</div>
<div id="textModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('textModal')">&times;</span>
<h2>Upload Text</h2>
<textarea id="textContent" rows="4" placeholder="Enter text here..."></textarea>
<div>
<input type="checkbox" id="isPrivate" name="isPrivate">
<label for="isPrivate">Add password protection</label>
</div>
<div id="fileForm" class="upload-form">
<h2>Upload File</h2>
<form enctype="multipart/form-data">
<input type="file" name="file" />
<button type="button" onclick="uploadFile()">Upload File</button>
</form>
<div id="fileResult" class="result"></div>
<div id="passwordField" style="display: none;">
<input type="password" id="textPassword" placeholder="Enter password">
</div>
<div id="folderForm" class="upload-form">
<h2>Upload Folder</h2>
<form enctype="multipart/form-data">
<input type="file" name="file" webkitdirectory directory />
<button type="button" onclick="uploadFolder()">Upload Folder</button>
</form>
<div id="folderResult" class="result"></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>
<div id="uploadArea" class="upload-area">
<p>Drag and drop files here or click to select files</p>
<input type="file" id="fileInput" multiple>
</div>
<div id="urlForm" class="upload-form">
<h2>Shorten URL</h2>
<form>
<input type="text" name="url" placeholder="Enter URL here..." />
<button type="button" onclick="shortenUrl()">Shorten URL</button>
</form>
<div id="urlResult" class="result"></div>
<div id="selectedFiles"></div>
<div>
<input type="checkbox" id="fileIsPrivate" name="fileIsPrivate">
<label for="fileIsPrivate">Add password protection</label>
</div>
<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>
<input type="text" id="urlInput" placeholder="Enter URL here...">
<div>
<input type="checkbox" id="urlIsPrivate" name="urlIsPrivate">
<label for="urlIsPrivate">Add password protection</label>
</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>
<footer class="footer">
<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>
</footer>
<script>
document.addEventListener("DOMContentLoaded", function() {
const message1 = "Welcome to aCloud.";
const message2 = "\n A simple toolbox for file uploading,\n URL shortening and pastebin.";
const message1 = "Welcome to sxbin.gay";
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
const typewriterTextElement = document.getElementById('typewriter-text');
const cursorElement = document.getElementById('cursor');
const typingSpeed = 70;
@ -188,7 +444,6 @@
typewriterTextElement.innerHTML += message[index];
}
index++;
updateCursorPosition();
setTimeout(typeCharacter, typingSpeed);
} else if (callback) {
setTimeout(callback, typingSpeed);
@ -198,115 +453,381 @@
typeCharacter();
}
function updateCursorPosition() {
const textRect = typewriterTextElement.getBoundingClientRect();
cursorElement.style.left = (textRect.width + textRect.left - cursorElement.offsetWidth) + 'px';
}
typeMessage(message1, function() {
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();
var formData = new FormData(this);
var xhr = new XMLHttpRequest();
uploadArea.style.backgroundColor = '#2d2d2d';
});
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.upload.onprogress = function(e) {
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
var percentComplete = (e.loaded / e.total) * 100;
document.getElementById('progressBar').style.display = 'block';
document.getElementById('progressBar').value = percentComplete;
const percentComplete = (e.loaded / e.total) * 100;
progressBar.style.width = percentComplete + '%';
progressBar.textContent = percentComplete.toFixed(2) + '%';
}
};
xhr.onload = function() {
if (xhr.status === 200) {
alert('Upload complete!');
document.getElementById('progressBar').style.display = 'none';
const response = JSON.parse(xhr.responseText);
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 {
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 {
alert('Upload failed.');
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);
};
function showForm(type) {
document.querySelectorAll('.upload-form').forEach(form => {
form.classList.remove('active');
});
document.getElementById(type + 'Form').classList.add('active');
}
function uploadText() {
const content = document.querySelector('#textForm textarea').value;
fetch('/upload/pastebin', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({ content }),
})
.then(response => response.json())
.then(data => {
document.getElementById('textResult').innerHTML = `Text uploaded. Access it <a href="/${data.vanity}">here</a>.`;
});
}
function uploadFile() {
const formData = new FormData();
formData.append('file', document.querySelector('#fileForm input[type="file"]').files[0]);
fetch('/upload/file', {
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;
function toggleFileGroup(header) {
const content = header.nextElementSibling;
const arrow = header.querySelector('span:first-child');
const filename = header.querySelector('span:nth-child(2)');
content.classList.toggle('show');
if (content.classList.contains('show')) {
arrow.textContent = '▼';
filename.style.display = 'inline';
} else {
arrow.textContent = '▶';
filename.style.display = 'inline';
}
}
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 toggleOtherLinks(event, button) {
event.stopPropagation();
const otherLinks = button.closest('.file-group-header').nextElementSibling.querySelector('.other-links');
otherLinks.classList.toggle('show');
}
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', {
method: 'POST',
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(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>
</body>
</html>

View File

@ -1,6 +1,150 @@
<h2>Login</h2>
<form method="post">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<input type="submit" value="Login">
</form>
<!DOCTYPE html>
<html lang="en">
<head>
<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">
</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>
<meta charset="UTF-8">
<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>
{{ css|safe }}
/* Add any additional styles here */
:root {
--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 {
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;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
}
button:hover {
opacity: 0.8;
}
.highlight {
background-color: #f8f8f8;
border: 1px solid #ccc;
background-color: var(--highlight-bg);
border: 1px solid var(--highlight-border);
border-radius: 4px;
padding: 1em;
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>
</head>
<body>
<h1>Pastebin Content</h1>
<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">
{{ content|safe }}
<a href="/" class="home-button">&#8962;</a>
<div class="container">
<h2>Content</h2>
<p>Uploaded by: {{ content.username }}</p>
<p>Created at: {{ created_at }}</p>
<div class="highlight">
{{ highlighted_content|safe }}
</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>
const rawContent = {{ raw_content|tojson }};
function copyToClipboard() {
navigator.clipboard.writeText(rawContent).then(() => {
const copyButton = document.getElementById("copy-button");
const originalText = copyButton.textContent;
copyButton.textContent = "Copied!";
setTimeout(() => {
copyButton.textContent = originalText;
}, 2000);
alert('Copied to clipboard!');
}).catch(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>
</body>
</html>

View File

@ -1,6 +1,171 @@
<h2>Register</h2>
<form method="post">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<input type="submit" value="Register">
</form>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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>
<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 {
color: #4CAF50;
}
ul {
list-style-type: none;
.file-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 0;
list-style-type: none;
}
li {
margin-bottom: 10px;
.file-item {
background-color: #2a2a2a;
padding: 10px;
border-radius: 5px;
display: flex;
align-items: center;
}
a {
color: #4CAF50;
@ -30,13 +37,34 @@
a:hover {
text-decoration: underline;
}
.folder {
font-weight: bold;
}
.file-icon {
margin-right: 5px;
}
</style>
</head>
<body>
<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 %}
<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 %}
</ul>
</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>