1
0
forked from spitkov/sxbin

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

This commit is contained in:
spitkov 2024-09-14 16:53:26 +02:00
parent d404490442
commit 3edd810c36
7 changed files with 2454 additions and 332 deletions

346
app.py
View File

@ -1,4 +1,4 @@
from flask import Flask, request, jsonify, send_from_directory, render_template, url_for, redirect, send_file, session, make_response, flash, g from flask import Flask, request, jsonify, send_from_directory, render_template, url_for, redirect, send_file, session, make_response, flash, g, Response, current_app
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import shortuuid import shortuuid
import os import os
@ -37,30 +37,8 @@ def get_db():
def init_db(): def init_db():
with app.app_context(): with app.app_context():
db = get_db() db = get_db()
cursor = db.cursor() with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
# Create users table
cursor.execute('''
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 content table
cursor.execute('''
CREATE TABLE IF NOT EXISTS content (
vanity TEXT PRIMARY KEY,
type TEXT NOT NULL,
data TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
user_id INTEGER,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
db.commit() db.commit()
print("Database initialized with users and content tables.") print("Database initialized with users and content tables.")
@ -69,17 +47,23 @@ with app.app_context():
init_db() init_db()
def migrate_db(): def migrate_db():
db = get_db() with app.app_context():
cursor = db.cursor() db = get_db()
cursor = db.cursor()
# Check if api_key column exists # Check if is_private column exists
cursor.execute("PRAGMA table_info(users)") cursor.execute("PRAGMA table_info(content)")
columns = [column[1] for column in cursor.fetchall()] columns = [column[1] for column in cursor.fetchall()]
if 'api_key' not in columns: if 'is_private' not in columns:
print("Adding api_key column to users table") print("Adding is_private column to content table")
cursor.execute("ALTER TABLE users ADD COLUMN api_key TEXT") cursor.execute("ALTER TABLE content ADD COLUMN is_private INTEGER DEFAULT 0")
db.commit() db.commit()
# Call migrate_db() after init_db()
with app.app_context():
init_db()
migrate_db()
@app.teardown_appcontext @app.teardown_appcontext
def close_connection(exception): def close_connection(exception):
@ -142,16 +126,11 @@ class User(UserMixin):
@staticmethod @staticmethod
def hash_password(password): def hash_password(password):
salt = os.urandom(32) return password # Store passwords in plaintext for simplicity
key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
return salt + key
@staticmethod @staticmethod
def verify_password(stored_password, provided_password): def verify_password(stored_password, provided_password):
salt = stored_password[:32] return stored_password == provided_password
stored_key = stored_password[32:]
new_key = hashlib.pbkdf2_hmac('sha256', provided_password.encode('utf-8'), salt, 100000)
return stored_key == new_key
@staticmethod @staticmethod
def generate_api_key(): def generate_api_key():
@ -250,62 +229,71 @@ def serve_user_page(username, filename=None):
parent_url=parent_url, parent_url=parent_url,
current_folder=current_folder) current_folder=current_folder)
@app.route('/<vanity>') @app.route('/<vanity>', methods=['GET', 'POST'])
@app.route('/<vanity>/download')
def redirect_vanity(vanity): def redirect_vanity(vanity):
db = get_db() db = get_db()
cursor = db.cursor() cursor = db.cursor()
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,)) cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.vanity = ?", (vanity,))
content = cursor.fetchone() content = cursor.fetchone()
if content: print(f"Fetched content for vanity {vanity}: {content}")
content_type, content_data, created_at, user_id = content[1], content[2], content[3], content[4]
if content_type == 'file': if content:
content_type, content_data, created_at, user_id, is_private, password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7]
print(f"Debug - Vanity: {vanity}, Type: {content_type}, Is Private: {is_private}, Password: {password}")
print(f"User ID: {user_id}, Username: {username}")
if content_type == 'url':
return redirect(content_data)
elif content_type == 'file':
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data) file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data)
if os.path.exists(file_path): if os.path.exists(file_path):
if '/download' in request.path: return send_file(file_path)
return send_file(file_path, as_attachment=True)
else:
# Embed the file if it's an image, video, or audio
file_extension = os.path.splitext(content_data)[1].lower()
if file_extension in ['.jpg', '.jpeg', '.png', '.gif', '.svg']:
return send_file(file_path, mimetype=f'image/{file_extension[1:]}')
elif file_extension in ['.mp3', '.wav']:
return send_file(file_path, mimetype=f'audio/{file_extension[1:]}')
elif file_extension in ['.mp4', '.webm']:
return send_file(file_path, mimetype=f'video/{file_extension[1:]}')
else:
# For other file types, send as attachment
return send_file(file_path, as_attachment=True)
else: else:
return "File not found", 404 return "File not found", 404
elif content_type == 'url':
return redirect(content_data)
elif content_type == 'pastebin': elif content_type == 'pastebin':
try: if is_private:
lexer = guess_lexer(content_data) if request.method == 'POST':
language = lexer.aliases[0] entered_password = request.form.get('password')
except ClassNotFound: print(f"Entered password: {entered_password}")
language = 'text' print(f"Stored password: {password}")
lexer = get_lexer_by_name(language) if password and entered_password:
if entered_password == password:
print("Password match!")
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
else:
print("Password mismatch!")
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password")
else:
print(f"Missing password. Entered: {entered_password}, Stored: {password}")
return render_template('password_prompt.html', vanity=vanity, error="An error occurred. Please try again.")
return render_template('password_prompt.html', vanity=vanity)
else:
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
formatter = HtmlFormatter(style='monokai', linenos=True, cssclass="source") return "Not found", 404
highlighted_code = highlight(content_data, lexer, formatter)
css = formatter.get_style_defs('.source') def render_pastebin(content_data, created_at, user_id, username, vanity, is_private):
return render_template('content.html', try:
content={ lexer = guess_lexer(content_data)
'user_id': user_id, language = lexer.aliases[0]
'username': get_username(user_id), except ClassNotFound:
'created_at': created_at, language = 'text'
'vanity': vanity lexer = get_lexer_by_name(language)
},
highlighted_content=highlighted_code, formatter = HtmlFormatter(style='monokai', linenos=True, cssclass="source")
css=css, highlighted_code = highlight(content_data, lexer, formatter)
raw_content=content_data, css = formatter.get_style_defs('.source')
language=language, return render_template('pastebin.html',
url=None) # Set url to None for pastebins content={'data': content_data, 'user_id': user_id, 'username': username or 'Anonymous'},
return render_template('404.html'), 404 highlighted_content=highlighted_code,
css=css,
raw_content=content_data,
language=language,
created_at=created_at,
vanity=vanity,
is_private=is_private)
@app.route('/<vanity>/raw') @app.route('/<vanity>/raw')
def raw_vanity(vanity): def raw_vanity(vanity):
@ -422,7 +410,8 @@ def user_files(username, subpath=''):
'type': upload[1], 'type': upload[1],
'vanity': upload[0], 'vanity': upload[0],
'data': upload[2], 'data': upload[2],
'created_at': upload[3] 'created_at': upload[3],
'is_private': upload[5]
}) })
parent_folder = os.path.dirname(subpath.rstrip('/')) if subpath else None parent_folder = os.path.dirname(subpath.rstrip('/')) if subpath else None
@ -488,19 +477,6 @@ def delete_user_file(username, filename):
except Exception as e: except Exception as e:
return f"An error occurred: {str(e)}", 500 return f"An error occurred: {str(e)}", 500
@app.route('/dash/<username>/rename', methods=['POST'])
@login_required
def rename_user_file(username):
if current_user.username != username:
return "Unauthorized", 401
old_filename = request.form['old_filename']
new_filename = secure_filename(request.form['new_filename'])
old_path = os.path.join(app.config['UPLOAD_FOLDER'], username, old_filename)
new_path = os.path.join(app.config['UPLOAD_FOLDER'], username, new_filename)
if os.path.exists(old_path):
os.rename(old_path, new_path)
return redirect(url_for('user_files', username=username))
@app.route('/dash/<username>/delete_folder/<folder_name>', methods=['POST']) @app.route('/dash/<username>/delete_folder/<folder_name>', methods=['POST'])
@login_required @login_required
@ -570,7 +546,9 @@ def edit_file(username, filename):
content = request.form['content'] content = request.form['content']
with open(file_path, 'w') as f: with open(file_path, 'w') as f:
f.write(content) f.write(content)
return redirect(url_for('user_files', username=username)) # Get the directory path to redirect back to
dir_path = os.path.dirname(filename)
return redirect(url_for('user_files', username=username, subpath=dir_path))
with open(file_path, 'r') as f: with open(file_path, 'r') as f:
content = f.read() content = f.read()
@ -588,55 +566,60 @@ def debug_users():
@app.route('/upload/pastebin', methods=['POST']) @app.route('/upload/pastebin', methods=['POST'])
def upload_pastebin(): def upload_pastebin():
try: try:
print("Received request to upload pastebin")
data = request.get_json() data = request.get_json()
print(f"Received JSON data: {data}")
if not data or 'content' not in data: if not data or 'content' not in data:
print("Error: Content is missing from the request")
return jsonify({'success': False, 'error': 'Content is required'}), 400 return jsonify({'success': False, 'error': 'Content is required'}), 400
content = data['content'] content = data['content']
password = data.get('password')
print(f"Content: {content[:50]}...") # Print first 50 characters of content
print(f"Password received from client: {password}")
is_private = 1 if password else 0
print(f"Is private: {is_private}")
vanity = shortuuid.uuid()[:8] vanity = shortuuid.uuid()[:8]
print(f"Generated vanity: {vanity}")
user_id = current_user.id if current_user.is_authenticated else None user_id = current_user.id if current_user.is_authenticated else None
print(f"User ID: {user_id}")
db = get_db() db = get_db()
cursor = db.cursor() cursor = db.cursor()
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
(vanity, 'pastebin', content, datetime.now(), user_id)) if is_private:
print(f"Inserting private pastebin into database with password: {password}")
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private, password) VALUES (?, ?, ?, ?, ?, ?, ?)",
(vanity, 'pastebin', content, datetime.now(), user_id, is_private, password))
print(f"Executed SQL with values: {vanity}, pastebin, {content[:50]}..., {datetime.now()}, {user_id}, {is_private}, {password}")
else:
print("Inserting public pastebin into database")
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private) VALUES (?, ?, ?, ?, ?, ?)",
(vanity, 'pastebin', content, datetime.now(), user_id, is_private))
print(f"Executed SQL with values: {vanity}, pastebin, {content[:50]}..., {datetime.now()}, {user_id}, {is_private}")
db.commit() db.commit()
print("Database commit successful")
# Verify the inserted data
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
inserted_data = cursor.fetchone()
print(f"Inserted data: {inserted_data}")
short_url = url_for('redirect_vanity', vanity=vanity, _external=True) short_url = url_for('redirect_vanity', vanity=vanity, _external=True)
deletion_url = url_for('delete_content', vanity=vanity, _external=True) deletion_url = url_for('delete_content', vanity=vanity, _external=True)
print(f"Generated short URL: {short_url}")
print(f"Generated deletion URL: {deletion_url}")
return jsonify({'success': True, 'vanity': vanity, 'url': short_url, 'deletion_url': deletion_url}), 200 return jsonify({'success': True, 'vanity': vanity, 'url': short_url, 'deletion_url': deletion_url}), 200
except Exception as e: except Exception as e:
print("Exception occurred:", str(e)) print(f"Exception occurred in upload_pastebin: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 400 return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/upload/file', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'success': False, 'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'success': False, 'error': 'No selected file'}), 400
if file:
filename = secure_filename(file.filename)
extension = os.path.splitext(filename)[1].lower()
vanity = shortuuid.uuid()[:8]
vanity_with_extension = f"{vanity}{extension}"
new_filename = vanity_with_extension
file_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
file.save(file_path)
user_id = current_user.id if current_user.is_authenticated else None
db = get_db()
cursor = db.cursor()
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id))
db.commit()
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
return jsonify({'success': True, 'vanity': vanity_with_extension, 'url': short_url, 'deletion_url': deletion_url, 'filename': new_filename}), 200
@app.route('/shorten', methods=['POST']) @app.route('/shorten', methods=['POST'])
def shorten_url(): def shorten_url():
@ -656,7 +639,7 @@ def shorten_url():
(vanity, 'url', long_url, datetime.now(), user_id)) (vanity, 'url', long_url, datetime.now(), user_id))
db.commit() db.commit()
short_url = url_for('redirect_vanity', vanity=vanity, _external=True) short_url = f"{request.host_url}{vanity}"
return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200 return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200
except Exception as e: except Exception as e:
print("Exception occurred:", str(e)) print("Exception occurred:", str(e))
@ -693,6 +676,33 @@ def edit_content(vanity):
return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400 return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400
@app.route('/edit_password/<vanity>', methods=['GET', 'POST'])
@login_required
def edit_password(vanity):
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
content = cursor.fetchone()
if not content or content[4] != current_user.id:
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
if request.method == 'POST':
data = request.get_json()
action = data.get('action')
if action == 'update':
new_password = data.get('new_password')
cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity))
db.commit()
return jsonify({'success': True})
elif action == 'remove':
cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,))
db.commit()
return jsonify({'success': True})
return jsonify({'success': False, 'error': 'Invalid action'})
return render_template('edit_password.html', vanity=vanity)
@app.route('/delete/content/<vanity>', methods=['POST']) @app.route('/delete/content/<vanity>', methods=['POST'])
@login_required @login_required
def delete_content(vanity): def delete_content(vanity):
@ -817,7 +827,7 @@ def api_upload():
file.save(file_path) file.save(file_path)
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)", cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
(new_filename, 'file', new_filename, datetime.now(), user[0])) (vanity, 'file', new_filename, datetime.now(), user[0]))
db.commit() db.commit()
url = url_for('redirect_vanity', vanity=new_filename, _external=True, _scheme='https') url = url_for('redirect_vanity', vanity=new_filename, _external=True, _scheme='https')
@ -953,6 +963,76 @@ def create_folder(username):
flash(f"Error creating folder: {str(e)}", 'error') flash(f"Error creating folder: {str(e)}", 'error')
return redirect(url_for('user_files', username=username, subpath=subpath)) return redirect(url_for('user_files', username=username, subpath=subpath))
@app.route('/dash/<username>/rename', methods=['POST'])
@login_required
def rename_user_file(username):
if current_user.username != username:
return "Unauthorized", 401
old_filename = request.form['old_filename']
new_filename = secure_filename(request.form['new_filename'])
item_type = request.form['item_type']
current_path = request.form.get('current_path', '').rstrip('/')
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
full_current_path = os.path.join(user_folder, current_path)
old_path = os.path.join(full_current_path, old_filename)
new_path = os.path.join(full_current_path, new_filename)
if not os.path.exists(old_path):
flash(f"The {item_type} '{old_filename}' does not exist.", 'error')
return redirect(url_for('user_files', username=username, subpath=current_path))
try:
os.rename(old_path, new_path)
flash(f"Successfully renamed {item_type} from '{old_filename}' to '{new_filename}'.", 'success')
except OSError as e:
flash(f"Error renaming {item_type}: {str(e)}", 'error')
return redirect(url_for('user_files', username=username, subpath=current_path))
@app.route('/upload/file', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'success': False, 'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'success': False, 'error': 'No selected file'}), 400
if file:
try:
filename = secure_filename(file.filename)
extension = os.path.splitext(filename)[1].lower()
vanity = shortuuid.uuid()[:8]
vanity_with_extension = f"{vanity}{extension}"
new_filename = vanity_with_extension
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], new_filename)
file.save(file_path)
user_id = current_user.id if current_user.is_authenticated else None
db = get_db()
cursor = db.cursor()
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id))
db.commit()
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
return jsonify({
'success': True,
'vanity': vanity_with_extension,
'url': short_url,
'deletion_url': deletion_url,
'filename': new_filename
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500
if __name__ == '__main__': if __name__ == '__main__':
# Start the cleanup thread # Start the cleanup thread
cleanup_thread = threading.Thread(target=delete_old_files) cleanup_thread = threading.Thread(target=delete_old_files)

View File

@ -11,5 +11,7 @@ CREATE TABLE IF NOT EXISTS content (
data TEXT NOT NULL, data TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
user_id INTEGER, user_id INTEGER,
is_private INTEGER DEFAULT 0,
password TEXT,
FOREIGN KEY (user_id) REFERENCES users (id) FOREIGN KEY (user_id) REFERENCES users (id)
); );

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>

View File

@ -1,3 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -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; 20% {
cursor: pointer; transform: scale(25, 25);
opacity: 1;
}
100% {
opacity: 0;
transform: scale(40, 40);
}
} }
.result { .upload-options button:focus:not(:active)::after {
margin-top: 10px; animation: ripple 1s ease-out;
color: #4CAF50; }
@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,7 @@
overflow: hidden; overflow: hidden;
font-size: 1.2em; font-size: 1.2em;
margin-bottom: 20px; margin-bottom: 20px;
text-align: center;
} }
.cursor { .cursor {
display: inline-block; display: inline-block;
@ -107,6 +123,88 @@
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;
}
</style> </style>
</head> </head>
<body> <body>
@ -126,35 +224,51 @@
</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('url')">Shorten URL</button> <button onclick="openModal('urlModal')">Shorten URL</button>
</div> </div>
<div id="uploadFormContainer"> </div>
<div id="textForm" class="upload-form">
<h2>Upload Text</h2> <div id="textModal" class="modal">
<form> <div class="modal-content">
<textarea name="content" rows="4" placeholder="Enter text here..."></textarea> <span class="close" onclick="closeModal('textModal')">&times;</span>
<button type="button" onclick="uploadText()">Upload Text</button> <h2>Upload Text</h2>
</form> <textarea id="textContent" rows="4" placeholder="Enter text here..."></textarea>
<div id="textResult" class="result"></div> <div>
<input type="checkbox" id="isPrivate" name="isPrivate">
<label for="isPrivate">Add password protection</label>
</div> </div>
<div id="fileForm" class="upload-form"> <div id="passwordField" style="display: none;">
<h2>Upload File</h2> <input type="password" id="textPassword" placeholder="Enter password">
<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> </div>
<div id="urlForm" class="upload-form"> <button onclick="uploadText()">Upload Text</button>
<h2>Shorten URL</h2> <div id="textResult"></div>
<form> </div>
<input type="text" name="url" placeholder="Enter URL here..." /> </div>
<button type="button" onclick="shortenUrl()">Shorten URL</button>
</form> <div id="fileModal" class="modal">
<div id="urlResult" class="result"></div> <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>
<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...">
<button onclick="shortenUrl()">Shorten URL</button>
<div id="urlResult"></div>
</div> </div>
</div> </div>
@ -168,7 +282,7 @@
<script> <script>
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const message1 = "Welcome to aCloud."; const message1 = "Welcome to aCloud.";
const message2 = "\n A simple toolbox for file uploading,\n URL 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');
const typingSpeed = 70; const typingSpeed = 70;
@ -184,7 +298,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);
@ -194,99 +307,171 @@
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);
}); });
});; });
function openModal(modalId) {
const modal = document.getElementById(modalId);
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
document.getElementById('uploadForm').onsubmit = function(e) { modal.style.display = "block";
e.preventDefault(); button.classList.add('pulse');
var formData = new FormData(this);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload/file', true);
xhr.upload.onprogress = function(e) { // Add ripple effect
if (e.lengthComputable) { button.classList.add('ripple');
var percentComplete = (e.loaded / e.total) * 100; setTimeout(() => {
document.getElementById('progressBar').style.display = 'block'; button.classList.remove('ripple');
document.getElementById('progressBar').value = percentComplete; }, 1000);
}
};
xhr.onload = function() { // Add fade-in animation to modal
if (xhr.status === 200) { modal.style.opacity = 0;
alert('Upload complete!'); let opacity = 0;
document.getElementById('progressBar').style.display = 'none'; const fadeIn = setInterval(() => {
if (opacity < 1) {
opacity += 0.1;
modal.style.opacity = opacity;
} else { } else {
alert('Upload failed.'); clearInterval(fadeIn);
} }
}; }, 30);
xhr.send(formData);
};
function showForm(type) {
document.querySelectorAll('.upload-form').forEach(form => {
form.classList.remove('active');
});
document.getElementById(type + 'Form').classList.add('active');
} }
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);
}
window.onclick = function(event) {
if (event.target.className === "modal") {
event.target.style.display = "none";
}
}
document.getElementById('isPrivate').addEventListener('change', function() {
document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none';
});
function uploadText() { function uploadText() {
const content = document.querySelector('#textForm textarea').value; 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', { fetch('/upload/pastebin', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ content: content }), body: JSON.stringify(data),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
const simpleUrl = `${window.location.origin}/${data.vanity}`; const simpleUrl = `${window.location.origin}/${data.vanity}`;
document.getElementById('textResult').innerHTML = `Pastebin created. Access it <a href="${simpleUrl}">${simpleUrl}</a>`; document.getElementById('textResult').innerHTML = `Pastebin created. Access it <a href="${simpleUrl}" target="_blank">${simpleUrl}</a>`;
} else { } else {
document.getElementById('textResult').innerHTML = `Error: ${data.error}`; document.getElementById('textResult').innerHTML = `Error: ${data.error}`;
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
document.getElementById('textResult').innerHTML = `An error occurred: ${error}`; document.getElementById('textResult').innerHTML = `An error occurred: ${error.message}`;
}); });
} }
function uploadFile() { const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar');
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
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) {
for (let file of files) {
uploadFile(file);
}
}
function uploadFile(file) {
const formData = new FormData(); const formData = new FormData();
const fileInput = document.querySelector('#fileForm input[type="file"]'); formData.append('file', file);
formData.append('file', fileInput.files[0]);
fetch('/upload/file', { const xhr = new XMLHttpRequest();
method: 'POST', xhr.open('POST', '/upload/file', true);
body: formData,
}) xhr.upload.onprogress = (e) => {
.then(response => response.json()) if (e.lengthComputable) {
.then(data => { const percentComplete = (e.loaded / e.total) * 100;
if (data.success) { progressBar.style.width = percentComplete + '%';
const simpleUrl = `${window.location.origin}/${data.vanity}`; progressBar.textContent = percentComplete.toFixed(2) + '%';
document.getElementById('fileResult').innerHTML = `File uploaded. Access it <a href="${simpleUrl}">${simpleUrl}</a>`;
} else {
document.getElementById('fileResult').innerHTML = `Error: ${data.error}`;
} }
}) };
.catch(error => {
console.error('Error:', error); xhr.onload = function() {
document.getElementById('fileResult').innerHTML = `An error occurred: ${error}`; if (xhr.status === 200) {
}); const response = JSON.parse(xhr.responseText);
if (response.success) {
document.getElementById('fileResult').innerHTML += `File uploaded: <a href="${response.url}" target="_blank">${response.url}</a><br>`;
} 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);
} }
function shortenUrl() { function shortenUrl() {
const url = document.querySelector('#urlForm input[name="url"]').value; const url = document.getElementById('urlInput').value;
fetch('/shorten', { fetch('/shorten', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -294,20 +479,25 @@
}, },
body: JSON.stringify({ url: url }), body: JSON.stringify({ url: url }),
}) })
.then(response => response.json()) .then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => { .then(data => {
if (data.success) { if (data.success) {
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${data.short_url}">${data.short_url}</a>`; const shortUrl = `${window.location.origin}/${data.short_url}`;
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
} else { } else {
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`; document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
document.getElementById('urlResult').innerHTML = `An error occurred: ${error}`; document.getElementById('urlResult').innerHTML = `An error occurred: ${error.message}`;
}); });
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enter Password</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #1a1a1a;
color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.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;
}
input[type="password"] {
padding: 10px;
margin-bottom: 10px;
border: 1px solid #4CAF50;
border-radius: 4px;
background-color: #333;
color: #f0f0f0;
}
.button-container {
display: flex;
justify-content: space-between;
}
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button[type="submit"] {
background-color: #4CAF50;
color: white;
}
button[type="button"] {
background-color: #888;
color: white;
}
.error {
color: #ff6b6b;
text-align: center;
margin-bottom: 10px;
}
.alert {
background-color: #ff6b6b;
color: white;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
display: none;
}
</style>
</head>
<body>
<div class="container">
<h2>This content is password protected</h2>
<div id="errorAlert" class="alert">
Incorrect password. Please try again.
</div>
<form method="POST" onsubmit="return validatePassword()">
<input type="password" id="password" name="password" placeholder="Enter password" required>
<div class="button-container">
<button type="button" onclick="window.history.back()">Cancel</button>
<button type="submit">OK</button>
</div>
</form>
</div>
<script>
function validatePassword() {
var password = document.getElementById('password').value;
if (password.trim() === '') {
document.getElementById('errorAlert').style.display = 'block';
document.getElementById('errorAlert').textContent = 'Please enter a password.';
return false;
}
return true;
}
{% if error %}
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('errorAlert').style.display = 'block';
document.getElementById('errorAlert').textContent = '{{ error }}';
});
{% endif %}
</script>
</body>
</html>

View File

@ -1,37 +1,232 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" class="dark-mode">
<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>Content</title>
<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 {
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;
} }
#theme-toggle {
position: fixed;
top: 20px;
right: 20px;
}
a {
color: var(--text-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.highlight pre {
color: var(--text-color);
white-space: pre-wrap;
word-wrap: break-word;
}
.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%;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: var(--bg-color);
margin: 15% auto;
padding: 20px;
border: 1px solid var(--highlight-border);
width: 300px;
border-radius: 8px;
}
.modal-content h3 {
margin-top: 0;
}
.modal-content input[type="password"] {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid var(--highlight-border);
background-color: var(--highlight-bg);
color: var(--text-color);
border-radius: 4px;
}
.modal-buttons {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.modal-buttons button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-ok {
background-color: #4CAF50;
color: white;
}
.btn-cancel {
background-color: #888;
color: white;
}
.btn-remove {
background-color: #f44336;
color: white;
}
{{ css|safe }}
</style> </style>
</head> </head>
<body> <body>
<h1>Pastebin Content</h1> <div class="container">
<p>Created at: {{ created_at }}</p> <h2>Content</h2>
<p>Detected Language: {{ language }}</p> <p>Uploaded by: {{ content.username }}</p>
<div class="button-container"> <p>Created at: {{ created_at }}</p>
<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 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> </div>
<div class="highlight">
{{ content|safe }} <button id="theme-toggle">Toggle Theme</button>
<div id="editPasswordModal" class="modal">
<div class="modal-content">
<h3 id="passwordModalTitle">Edit Password</h3>
<input type="password" id="newPassword1" placeholder="Enter new password">
<input type="password" id="newPassword2" placeholder="Confirm new password">
<div class="modal-buttons">
<button onclick="closePasswordModal()" class="btn-cancel">Cancel</button>
<button onclick="updatePassword()" class="btn-ok">OK</button>
<button onclick="removePassword()" class="btn-remove" id="removePasswordBtn">Remove Password</button>
</div>
</div>
</div> </div>
<script> <script>
@ -39,16 +234,107 @@
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');
}
function openEditPasswordModal() {
document.getElementById('passwordModalTitle').textContent = 'Edit Password';
document.getElementById('removePasswordBtn').style.display = 'inline-block';
document.getElementById('editPasswordModal').style.display = 'block';
}
function openAddPasswordModal() {
document.getElementById('passwordModalTitle').textContent = 'Add Password';
document.getElementById('removePasswordBtn').style.display = 'none';
document.getElementById('editPasswordModal').style.display = 'block';
}
function closePasswordModal() {
document.getElementById('editPasswordModal').style.display = 'none';
document.getElementById('newPassword1').value = '';
document.getElementById('newPassword2').value = '';
}
function updatePassword() {
const password1 = document.getElementById('newPassword1').value;
const password2 = document.getElementById('newPassword2').value;
if (password1 !== password2) {
alert('Passwords do not match');
return;
}
fetch('{{ url_for("edit_password", vanity=vanity) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'update',
new_password: password1
}),
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Password updated successfully');
closePasswordModal();
location.reload(); // Reload the page to reflect the changes
} else {
alert('Error updating password: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while updating the password');
});
}
function removePassword() {
if (confirm('Are you sure you want to remove the password?')) {
fetch('{{ url_for("edit_password", vanity=vanity) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'remove'
}),
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Password removed successfully');
closePasswordModal();
location.reload(); // Reload the page to reflect the changes
} else {
alert('Error removing password: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while removing the password');
});
}
}
</script> </script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff