Compare commits
No commits in common. "main" and "main" have entirely different histories.
33
README.md
33
README.md
@ -1,2 +1,31 @@
|
|||||||
this repo is deprecated
|
## Text sharing, File sharing, link shortener.
|
||||||
https://github.com/spitkov/sxbin
|
### frontend and backend, easily configurable (thru editing the html files and the .py)
|
||||||
|
--------------dasdasdasdasdasdadasd--
|
||||||
|
TODO
|
||||||
|
<iframe src="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/"> </iframe>
|
||||||
|
1. Introduction
|
||||||
|
</br>
|
||||||
|
</br>
|
||||||
|
</br>
|
||||||
|
As said above, this is a **simple & private** backend (app.py) and frontend (in /templates), which has these features:
|
||||||
|
- File Sharing (get a short link to share files, and when the link is opened it will show some information)
|
||||||
|
- Text Sharing (kind of a pastebin, it will show the text and when the text was made, again in a short link)
|
||||||
|
- URL Shortener (when hosting this, it will short an url, and give you a link, and when you open it, it will automatically redirect you to that website)
|
||||||
|
|
||||||
|
2. This is 100% **anonymous**, no one can see who created the links, etc.
|
||||||
|
|
||||||
|
3. The UI is simple.
|
||||||
|
|
||||||
|
4. This is very easily self-hostable </br> </br>
|
||||||
|
Rookie Mistake: when you self-host it, you can open it in localhost:7123, when making a file/paste/shortener don't share that localhost link, localhost is only accessible on YOUR network. - instead, u can host it with your domain.
|
||||||
|
</br>
|
||||||
|
</br>
|
||||||
|
5. This project is **COMPLETLY** open source.
|
||||||
|
</br>
|
||||||
|
</br>
|
||||||
|
6. You might need to install some pip packages, if you dont have them installed already download this repo and go into the folder and execute ```pip install -r requirements.txt```
|
||||||
|
</br>
|
||||||
|
</br>
|
||||||
|
7. Demo:
|
||||||
|
<video src="https://cgcristi.xyz/demo.mp4" controls></video>
|
||||||
|
|
||||||
|
254
app.py
254
app.py
@ -233,22 +233,11 @@ def serve_user_page(username, filename=None):
|
|||||||
@app.route('/<vanity>/<password>', methods=['GET', 'POST'])
|
@app.route('/<vanity>/<password>', methods=['GET', 'POST'])
|
||||||
@app.route('/<vanity>/download', methods=['GET', 'POST'])
|
@app.route('/<vanity>/download', methods=['GET', 'POST'])
|
||||||
@app.route('/<vanity>/download/<password>', methods=['GET', 'POST'])
|
@app.route('/<vanity>/download/<password>', methods=['GET', 'POST'])
|
||||||
@app.route('/<vanity>/raw', methods=['GET', 'POST'])
|
|
||||||
@app.route('/<vanity>/raw/<password>', methods=['GET', 'POST'])
|
|
||||||
def redirect_vanity(vanity, password=None):
|
def redirect_vanity(vanity, password=None):
|
||||||
app.logger.info(f"Accessing redirect_vanity: vanity={vanity}, password={password}")
|
app.logger.info(f"Accessing vanity: {vanity}, password: {password}")
|
||||||
app.logger.info(f"Request path: {request.path}")
|
|
||||||
app.logger.info(f"Request method: {request.method}")
|
|
||||||
app.logger.info(f"Request URL: {request.url}")
|
|
||||||
app.logger.info(f"Request endpoint: {request.endpoint}")
|
|
||||||
app.logger.info(f"Request view args: {request.view_args}")
|
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
|
|
||||||
is_download = 'download' in request.path
|
|
||||||
is_raw = 'raw' in request.path
|
|
||||||
|
|
||||||
# First, try to find the content with the full vanity (including extension)
|
# First, try to find the content with the full vanity (including extension)
|
||||||
cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.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()
|
||||||
@ -263,11 +252,6 @@ def redirect_vanity(vanity, password=None):
|
|||||||
content_type, content_data, created_at, user_id, is_private, stored_password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7]
|
content_type, content_data, created_at, user_id, is_private, stored_password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7]
|
||||||
app.logger.info(f"Content found: type={content_type}, data={content_data}, is_private={is_private}")
|
app.logger.info(f"Content found: type={content_type}, data={content_data}, is_private={is_private}")
|
||||||
|
|
||||||
try:
|
|
||||||
created_at = datetime.strptime(created_at, '%Y-%m-%d %H:%M:%S.%f')
|
|
||||||
except ValueError:
|
|
||||||
created_at = datetime.strptime(created_at, '%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
if is_private and stored_password:
|
if is_private and stored_password:
|
||||||
if password:
|
if password:
|
||||||
if password != stored_password:
|
if password != stored_password:
|
||||||
@ -275,58 +259,25 @@ def redirect_vanity(vanity, password=None):
|
|||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
entered_password = request.form.get('password')
|
entered_password = request.form.get('password')
|
||||||
if entered_password != stored_password:
|
if entered_password != stored_password:
|
||||||
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password", content_type=content_type)
|
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password")
|
||||||
password = entered_password # Set the password for use in raw_url
|
|
||||||
else:
|
else:
|
||||||
return render_template('password_prompt.html', vanity=vanity, error=None, content_type=content_type)
|
return render_template('password_prompt.html', vanity=vanity, error=None)
|
||||||
|
|
||||||
if content_type == 'url':
|
if content_type == 'url':
|
||||||
return render_template('og_shorturl.html', long_url=content_data, username=username, created_at=created_at, vanity=vanity, is_private=is_private)
|
return redirect(content_data)
|
||||||
elif content_type == 'file':
|
elif content_type == 'file':
|
||||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data)
|
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content_data)
|
||||||
|
app.logger.info(f"Attempting to serve file: {file_path}")
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
file_size = os.path.getsize(file_path)
|
if 'download' in request.path:
|
||||||
file_extension = os.path.splitext(content_data)[1].lower()
|
|
||||||
is_embeddable = file_extension in ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.pdf']
|
|
||||||
file_url = f"{request.scheme}://{request.host}/{vanity}"
|
|
||||||
raw_url = f"{file_url}/raw"
|
|
||||||
|
|
||||||
if is_private and password:
|
|
||||||
raw_url += f'/{password}'
|
|
||||||
|
|
||||||
if is_download:
|
|
||||||
return send_file(file_path, as_attachment=True)
|
return send_file(file_path, as_attachment=True)
|
||||||
elif is_raw:
|
else:
|
||||||
return send_file(file_path)
|
return send_file(file_path)
|
||||||
else:
|
else:
|
||||||
return render_template('file_info.html',
|
app.logger.error(f"File not found: {file_path}")
|
||||||
filename=content_data,
|
return "File not found", 404
|
||||||
file_size=file_size,
|
|
||||||
username=username,
|
|
||||||
created_at=created_at,
|
|
||||||
is_embeddable=is_embeddable,
|
|
||||||
file_url=file_url,
|
|
||||||
raw_url=raw_url,
|
|
||||||
vanity=vanity,
|
|
||||||
user_id=user_id,
|
|
||||||
is_private=is_private,
|
|
||||||
password=password)
|
|
||||||
elif content_type == 'pastebin':
|
elif content_type == 'pastebin':
|
||||||
try:
|
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
|
||||||
lexer = guess_lexer(content_data)
|
|
||||||
except ClassNotFound:
|
|
||||||
lexer = get_lexer_by_name('text')
|
|
||||||
formatter = HtmlFormatter(style='monokai', linenos=True, cssclass="source")
|
|
||||||
highlighted_content = highlight(content_data, lexer, formatter)
|
|
||||||
css = formatter.get_style_defs('.source')
|
|
||||||
return render_template('pastebin.html',
|
|
||||||
content={'data': content_data, 'user_id': user_id, 'username': username},
|
|
||||||
highlighted_content=highlighted_content,
|
|
||||||
css=css,
|
|
||||||
raw_content=content_data,
|
|
||||||
created_at=created_at,
|
|
||||||
vanity=vanity,
|
|
||||||
is_private=is_private)
|
|
||||||
|
|
||||||
app.logger.error(f"Content not found for vanity: {vanity}")
|
app.logger.error(f"Content not found for vanity: {vanity}")
|
||||||
return "Not found", 404
|
return "Not found", 404
|
||||||
@ -379,6 +330,17 @@ def raw_vanity(vanity, password=None):
|
|||||||
|
|
||||||
return content_data, 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
return content_data, 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
||||||
return 'Not Found', 404
|
return 'Not Found', 404
|
||||||
|
# Replace the LoginForm and RegistrationForm classes with simple classes
|
||||||
|
class LoginForm:
|
||||||
|
def __init__(self, username, password, remember):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.remember = remember
|
||||||
|
|
||||||
|
class RegistrationForm:
|
||||||
|
def __init__(self, username, password):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
@ -386,15 +348,16 @@ def login():
|
|||||||
username = request.form['username']
|
username = request.form['username']
|
||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
remember = 'remember' in request.form
|
remember = 'remember' in request.form
|
||||||
|
form = LoginForm(username, password, remember)
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
|
cursor.execute("SELECT * FROM users WHERE username = ?", (form.username,))
|
||||||
user = cursor.fetchone()
|
user = cursor.fetchone()
|
||||||
if user and User.verify_password(user[2], password):
|
if user and User.verify_password(user[2], form.password):
|
||||||
user_obj = User(user[0], user[1], user[2], user[3])
|
user_obj = User(user[0], user[1], user[2], user[3])
|
||||||
login_user(user_obj, remember=remember)
|
login_user(user_obj, remember=form.remember)
|
||||||
return jsonify({'success': True, 'redirect': url_for('user_files', username=username)})
|
return redirect(url_for('user_files', username=form.username))
|
||||||
return jsonify({'success': False, 'error': 'Invalid username or password'})
|
return "Invalid username or password"
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
@app.route('/register', methods=['GET', 'POST'])
|
@app.route('/register', methods=['GET', 'POST'])
|
||||||
@ -402,26 +365,23 @@ def register():
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
username = request.form['username']
|
username = request.form['username']
|
||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
|
api_key = User.generate_api_key() # Generate API key
|
||||||
if len(password) < 5 or not any(c.isupper() for c in password):
|
|
||||||
return jsonify({'success': False, 'error': 'Password does not meet requirements'})
|
|
||||||
|
|
||||||
api_key = User.generate_api_key()
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
|
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
|
||||||
if cursor.fetchone():
|
if cursor.fetchone():
|
||||||
return jsonify({'success': False, 'error': 'Username already exists'})
|
return "Username already exists"
|
||||||
hashed_password = User.hash_password(password)
|
hashed_password = User.hash_password(password)
|
||||||
cursor.execute("INSERT INTO users (username, password_hash, api_key) VALUES (?, ?, ?)",
|
cursor.execute("INSERT INTO users (username, password_hash, api_key) VALUES (?, ?, ?)",
|
||||||
(username, hashed_password, api_key))
|
(username, hashed_password, api_key))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
# Create user directory
|
||||||
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
|
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
|
||||||
if not os.path.exists(user_folder):
|
if not os.path.exists(user_folder):
|
||||||
os.makedirs(user_folder)
|
os.makedirs(user_folder)
|
||||||
|
|
||||||
return jsonify({'success': True, 'redirect': url_for('login')})
|
return redirect(url_for('login'))
|
||||||
return render_template('register.html')
|
return render_template('register.html')
|
||||||
|
|
||||||
@app.route('/logout')
|
@app.route('/logout')
|
||||||
@ -470,16 +430,12 @@ def user_files(username, subpath=''):
|
|||||||
|
|
||||||
uploads = []
|
uploads = []
|
||||||
for upload in user_uploads:
|
for upload in user_uploads:
|
||||||
vanity, content_type, data, created_at, _, is_private = upload[:6]
|
|
||||||
url = f"{request.scheme}://{request.host}/{vanity}"
|
|
||||||
uploads.append({
|
uploads.append({
|
||||||
'type': content_type,
|
'type': upload[1],
|
||||||
'vanity': vanity,
|
'vanity': upload[0],
|
||||||
'data': data,
|
'data': upload[2],
|
||||||
'created_at': created_at,
|
'created_at': upload[3],
|
||||||
'is_private': is_private,
|
'is_private': upload[5]
|
||||||
'url': url,
|
|
||||||
'download_url': f"{url}/download" if content_type == 'file' else None
|
|
||||||
})
|
})
|
||||||
|
|
||||||
parent_folder = os.path.dirname(subpath.rstrip('/')) if subpath else None
|
parent_folder = os.path.dirname(subpath.rstrip('/')) if subpath else None
|
||||||
@ -668,7 +624,7 @@ def upload_pastebin():
|
|||||||
vanity = shortuuid.uuid()[:8]
|
vanity = shortuuid.uuid()[:8]
|
||||||
print(f"Generated vanity: {vanity}")
|
print(f"Generated vanity: {vanity}")
|
||||||
|
|
||||||
user_id = current_user.id if current_user.is_authenticated else "Anonymous"
|
user_id = current_user.id if current_user.is_authenticated else None
|
||||||
print(f"User ID: {user_id}")
|
print(f"User ID: {user_id}")
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@ -693,7 +649,7 @@ def upload_pastebin():
|
|||||||
inserted_data = cursor.fetchone()
|
inserted_data = cursor.fetchone()
|
||||||
print(f"Inserted data: {inserted_data}")
|
print(f"Inserted data: {inserted_data}")
|
||||||
|
|
||||||
short_url = f"{request.scheme}://{request.host}/{vanity}"
|
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 short URL: {short_url}")
|
||||||
print(f"Generated deletion URL: {deletion_url}")
|
print(f"Generated deletion URL: {deletion_url}")
|
||||||
@ -817,19 +773,11 @@ def delete_content(vanity):
|
|||||||
def content_info(vanity):
|
def content_info(vanity):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
|
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
|
||||||
# First, try to find the content with the full vanity (including extension)
|
|
||||||
cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.vanity = ?", (vanity,))
|
|
||||||
content = cursor.fetchone()
|
|
||||||
|
|
||||||
# If not found, try without the extension
|
|
||||||
if not content:
|
|
||||||
vanity_without_extension = os.path.splitext(vanity)[0]
|
|
||||||
cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.vanity LIKE ?", (f"{vanity_without_extension}%",))
|
|
||||||
content = cursor.fetchone()
|
content = cursor.fetchone()
|
||||||
|
|
||||||
if content:
|
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]
|
content_type, content_data, created_at, user_id, is_private, password = content[1], content[2], content[3], content[4], content[5], content[6]
|
||||||
|
|
||||||
if is_private and password:
|
if is_private and password:
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -839,6 +787,8 @@ def content_info(vanity):
|
|||||||
else:
|
else:
|
||||||
return render_template('password_prompt.html', vanity=vanity, error=None)
|
return render_template('password_prompt.html', vanity=vanity, error=None)
|
||||||
|
|
||||||
|
username = get_username(user_id)
|
||||||
|
|
||||||
file_size = None
|
file_size = None
|
||||||
is_media = False
|
is_media = False
|
||||||
if content_type == 'file':
|
if content_type == 'file':
|
||||||
@ -869,7 +819,7 @@ def generate_sharex_config():
|
|||||||
base_url = request.url_root.replace('http://', 'https://', 1).rstrip('/')
|
base_url = request.url_root.replace('http://', 'https://', 1).rstrip('/')
|
||||||
config = {
|
config = {
|
||||||
"Version": "13.7.0",
|
"Version": "13.7.0",
|
||||||
"Name": "sxbin",
|
"Name": "aCloud",
|
||||||
"DestinationType": "ImageUploader, TextUploader, FileUploader, URLShortener",
|
"DestinationType": "ImageUploader, TextUploader, FileUploader, URLShortener",
|
||||||
"RequestMethod": "POST",
|
"RequestMethod": "POST",
|
||||||
"RequestURL": f"{base_url}/api/upload",
|
"RequestURL": f"{base_url}/api/upload",
|
||||||
@ -886,7 +836,7 @@ def generate_sharex_config():
|
|||||||
|
|
||||||
response = make_response(json.dumps(config, indent=2))
|
response = make_response(json.dumps(config, indent=2))
|
||||||
response.headers.set('Content-Type', 'application/json')
|
response.headers.set('Content-Type', 'application/json')
|
||||||
response.headers.set('Content-Disposition', 'attachment', filename='sxbin_ShareX.sxcu')
|
response.headers.set('Content-Disposition', 'attachment', filename='aCloud_ShareX.sxcu')
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.route('/api/upload', methods=['POST'])
|
@app.route('/api/upload', methods=['POST'])
|
||||||
@ -1097,124 +1047,64 @@ def rename_user_file(username):
|
|||||||
|
|
||||||
@app.route('/upload/file', methods=['POST'])
|
@app.route('/upload/file', methods=['POST'])
|
||||||
def upload_file():
|
def upload_file():
|
||||||
app.logger.info("Starting upload_file function")
|
|
||||||
app.logger.info("Code: if 'file' not in request.files:")
|
|
||||||
if 'file' not in request.files:
|
if 'file' not in request.files:
|
||||||
app.logger.error("No file part in the request")
|
|
||||||
return jsonify({'success': False, 'error': 'No file part'}), 400
|
return jsonify({'success': False, 'error': 'No file part'}), 400
|
||||||
|
|
||||||
app.logger.info("Code: file = request.files['file']")
|
|
||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
app.logger.info(f"File object: {file}")
|
|
||||||
|
|
||||||
app.logger.info("Code: if file.filename == '':")
|
|
||||||
if file.filename == '':
|
if file.filename == '':
|
||||||
app.logger.error("No selected file")
|
|
||||||
return jsonify({'success': False, 'error': 'No selected file'}), 400
|
return jsonify({'success': False, 'error': 'No selected file'}), 400
|
||||||
|
|
||||||
app.logger.info("Code: if file:")
|
|
||||||
if file:
|
if file:
|
||||||
try:
|
try:
|
||||||
app.logger.info(f"Processing file: {file.filename}")
|
|
||||||
|
|
||||||
app.logger.info("Code: filename = secure_filename(file.filename)")
|
|
||||||
filename = secure_filename(file.filename)
|
filename = secure_filename(file.filename)
|
||||||
app.logger.info(f"Secure filename: {filename}")
|
|
||||||
|
|
||||||
app.logger.info("Code: extension = os.path.splitext(filename)[1].lower()")
|
|
||||||
extension = os.path.splitext(filename)[1].lower()
|
extension = os.path.splitext(filename)[1].lower()
|
||||||
app.logger.info(f"File extension: {extension}")
|
|
||||||
|
|
||||||
app.logger.info("Code: vanity = shortuuid.uuid()[:8]")
|
|
||||||
vanity = shortuuid.uuid()[:8]
|
vanity = shortuuid.uuid()[:8]
|
||||||
app.logger.info(f"Generated vanity: {vanity}")
|
|
||||||
|
|
||||||
app.logger.info("Code: vanity_with_extension = f'{vanity}{extension}'")
|
|
||||||
vanity_with_extension = f"{vanity}{extension}"
|
vanity_with_extension = f"{vanity}{extension}"
|
||||||
app.logger.info(f"Vanity with extension: {vanity_with_extension}")
|
|
||||||
|
|
||||||
app.logger.info("Code: new_filename = vanity_with_extension")
|
|
||||||
new_filename = vanity_with_extension
|
new_filename = vanity_with_extension
|
||||||
app.logger.info(f"New filename: {new_filename}")
|
|
||||||
|
|
||||||
app.logger.info("Code: file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], new_filename)")
|
|
||||||
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], new_filename)
|
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], new_filename)
|
||||||
app.logger.info(f"File path: {file_path}")
|
|
||||||
|
|
||||||
app.logger.info("Code: file.save(file_path)")
|
|
||||||
file.save(file_path)
|
file.save(file_path)
|
||||||
app.logger.info("File saved successfully")
|
|
||||||
|
|
||||||
app.logger.info("Code: user_id = current_user.id if current_user.is_authenticated else None")
|
|
||||||
user_id = current_user.id if current_user.is_authenticated else None
|
user_id = current_user.id if current_user.is_authenticated else None
|
||||||
app.logger.info(f"User ID: {user_id}")
|
|
||||||
|
|
||||||
app.logger.info("Code: password = request.form.get('password')")
|
|
||||||
password = request.form.get('password')
|
password = request.form.get('password')
|
||||||
app.logger.info(f"Password: {'Set' if password else 'Not set'}")
|
|
||||||
|
|
||||||
app.logger.info("Code: is_private = 1 if password else 0")
|
|
||||||
is_private = 1 if password else 0
|
is_private = 1 if password else 0
|
||||||
app.logger.info(f"Is private: {is_private}")
|
|
||||||
|
|
||||||
app.logger.info("Code: db = get_db()")
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
app.logger.info("Database connection established")
|
|
||||||
|
|
||||||
app.logger.info("Code: cursor = db.cursor()")
|
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
app.logger.info("Database cursor created")
|
|
||||||
|
|
||||||
app.logger.info("Inserting file info into database")
|
|
||||||
app.logger.info("Code: vanity_for_db = vanity_with_extension")
|
|
||||||
vanity_for_db = vanity_with_extension
|
|
||||||
app.logger.info(f"Vanity for DB: {vanity_for_db}")
|
|
||||||
|
|
||||||
app.logger.info("Code: cursor.execute(...)")
|
|
||||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private, password) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private, password) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
(vanity_for_db, 'file', new_filename, datetime.now(), user_id, is_private, password))
|
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id, is_private, password))
|
||||||
app.logger.info("SQL query executed")
|
|
||||||
|
|
||||||
app.logger.info("Code: db.commit()")
|
|
||||||
db.commit()
|
db.commit()
|
||||||
app.logger.info("Database changes committed")
|
|
||||||
|
|
||||||
app.logger.info("Code: scheme = 'https' if request.is_secure else 'http'")
|
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
|
||||||
scheme = 'https' if request.is_secure else 'http'
|
short_url = short_url.replace('/download', '')
|
||||||
app.logger.info(f"Using scheme: {scheme}")
|
download_url = short_url + '/download'
|
||||||
|
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
|
||||||
|
|
||||||
app.logger.info("Generating URLs")
|
# Add debug logging
|
||||||
app.logger.info("Code: short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True, _scheme=scheme)")
|
app.logger.info(f"File uploaded: {new_filename}")
|
||||||
short_url = f"{scheme}://{request.host}/{vanity_with_extension}"
|
app.logger.info(f"File path: {file_path}")
|
||||||
app.logger.info(f"Generated short URL: {short_url}")
|
app.logger.info(f"Short URL: {short_url}")
|
||||||
|
app.logger.info(f"Download URL: {download_url}")
|
||||||
|
|
||||||
app.logger.info("Code: download_url = short_url + '/download'")
|
# Check if the request is from ShareX
|
||||||
download_url = f"{short_url}/download"
|
if 'X-API-Key' in request.headers:
|
||||||
app.logger.info(f"Generated download URL: {download_url}")
|
return json.dumps({
|
||||||
|
'status': 'success',
|
||||||
app.logger.info("Code: deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True, _scheme=scheme)")
|
'url': short_url,
|
||||||
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True, _scheme=scheme)
|
'deletion_url': deletion_url,
|
||||||
app.logger.info(f"Generated deletion URL: {deletion_url}")
|
})
|
||||||
|
else:
|
||||||
app.logger.info("Preparing JSON response")
|
return jsonify({
|
||||||
response_data = {
|
|
||||||
'success': True,
|
'success': True,
|
||||||
'vanity': vanity_with_extension,
|
'vanity': vanity_with_extension,
|
||||||
'url': short_url,
|
'url': short_url,
|
||||||
'download_url': download_url,
|
'download_url': download_url,
|
||||||
'deletion_url': deletion_url,
|
'deletion_url': deletion_url,
|
||||||
'filename': new_filename
|
'filename': new_filename
|
||||||
}
|
})
|
||||||
app.logger.info(f"Response data: {response_data}")
|
|
||||||
|
|
||||||
return jsonify(response_data)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.error(f"Error uploading file: {str(e)}")
|
app.logger.error(f"Error uploading file: {str(e)}")
|
||||||
app.logger.exception("Exception traceback:")
|
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
app.logger.error("Unknown error occurred")
|
|
||||||
return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500
|
return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500
|
||||||
|
|
||||||
# Add this function to validate passwords
|
# Add this function to validate passwords
|
||||||
@ -1222,20 +1112,6 @@ def is_valid_password(password):
|
|||||||
banned_passwords = ['info', 'download']
|
banned_passwords = ['info', 'download']
|
||||||
return password not in banned_passwords
|
return password not in banned_passwords
|
||||||
|
|
||||||
@app.route('/reset_api_key', methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def reset_api_key():
|
|
||||||
new_api_key = secrets.token_urlsafe(32)
|
|
||||||
db = get_db()
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute("UPDATE users SET api_key = ? WHERE id = ?", (new_api_key, current_user.id))
|
|
||||||
db.commit()
|
|
||||||
return jsonify({'success': True, 'new_api_key': new_api_key})
|
|
||||||
|
|
||||||
@app.route('/api/docs')
|
|
||||||
def api_docs():
|
|
||||||
return render_template('api_docs.html')
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
<!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>
|
|
@ -158,7 +158,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Content</h2>
|
<h2>Content</h2>
|
||||||
{% if content %}
|
{% if content %}
|
||||||
<p>Uploaded by: {{ 'Anonymous' if content.username == 'None' else content.username }}</p>
|
<p>Uploaded by: {{ content.username }}</p>
|
||||||
<p>Created at: {{ content.created_at }}</p>
|
<p>Created at: {{ content.created_at }}</p>
|
||||||
|
|
||||||
{% if highlighted_content %}
|
{% if highlighted_content %}
|
||||||
|
@ -1,165 +0,0 @@
|
|||||||
<!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">⌂</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>
|
|
@ -4,7 +4,7 @@
|
|||||||
<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>sxbin - File Sharing Service</title>
|
<title>aCloud - File Sharing Service</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
@ -110,10 +110,6 @@
|
|||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
.cursor {
|
.cursor {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -285,38 +281,16 @@
|
|||||||
color: white;
|
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 {
|
.instant-upload-result {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
background-color: #2a2a2a;
|
background-color: #2a2a2a;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
position: relative;
|
z-index: 10000;
|
||||||
max-width: 80%;
|
display: none;
|
||||||
}
|
|
||||||
|
|
||||||
.close-modal {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 15px;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-modal:hover {
|
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -407,13 +381,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="instant-upload-overlay">
|
|
||||||
<div class="instant-upload-result">
|
<div class="instant-upload-result">
|
||||||
<span class="close-modal">×</span>
|
|
||||||
<h3>File Uploaded</h3>
|
<h3>File Uploaded</h3>
|
||||||
<p>Direct download URL: <a id="directDownloadUrl" href="#" target="_blank"></a></p>
|
<p>Direct download URL: <a id="directDownloadUrl" href="#" target="_blank"></a></p>
|
||||||
<p>Normal URL: <a id="normalUrl" href="#" target="_blank"></a></p>
|
<p>Normal URL: <a id="normalUrl" href="#" target="_blank"></a></p>
|
||||||
</div>
|
<button onclick="closeInstantUploadResult()">Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
@ -427,7 +399,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
const message1 = "Welcome to sxbin.gay";
|
const message1 = "Welcome to aCloud.";
|
||||||
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
|
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
|
||||||
const typewriterTextElement = document.getElementById('typewriter-text');
|
const typewriterTextElement = document.getElementById('typewriter-text');
|
||||||
const cursorElement = document.getElementById('cursor');
|
const cursorElement = document.getElementById('cursor');
|
||||||
@ -458,8 +430,6 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let isFileModalOpen = false;
|
|
||||||
|
|
||||||
function openModal(modalId) {
|
function openModal(modalId) {
|
||||||
const modal = document.getElementById(modalId);
|
const modal = document.getElementById(modalId);
|
||||||
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
||||||
@ -484,10 +454,6 @@
|
|||||||
clearInterval(fadeIn);
|
clearInterval(fadeIn);
|
||||||
}
|
}
|
||||||
}, 30);
|
}, 30);
|
||||||
|
|
||||||
if (modalId === 'fileModal') {
|
|
||||||
isFileModalOpen = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal(modalId) {
|
function closeModal(modalId) {
|
||||||
@ -507,18 +473,11 @@
|
|||||||
modal.style.display = "none";
|
modal.style.display = "none";
|
||||||
}
|
}
|
||||||
}, 30);
|
}, 30);
|
||||||
|
|
||||||
if (modalId === 'fileModal') {
|
|
||||||
isFileModalOpen = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onclick = function(event) {
|
window.onclick = function(event) {
|
||||||
if (event.target.className === "modal") {
|
if (event.target.className === "modal") {
|
||||||
event.target.style.display = "none";
|
event.target.style.display = "none";
|
||||||
if (event.target.id === 'fileModal') {
|
|
||||||
isFileModalOpen = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -751,9 +710,7 @@
|
|||||||
// Global drag and drop
|
// Global drag and drop
|
||||||
document.addEventListener('dragover', function(e) {
|
document.addEventListener('dragover', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!isFileModalOpen) {
|
|
||||||
document.querySelector('.global-drop-area').style.display = 'flex';
|
document.querySelector('.global-drop-area').style.display = 'flex';
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('dragleave', function(e) {
|
document.addEventListener('dragleave', function(e) {
|
||||||
@ -765,7 +722,7 @@
|
|||||||
document.addEventListener('drop', function(e) {
|
document.addEventListener('drop', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.querySelector('.global-drop-area').style.display = 'none';
|
document.querySelector('.global-drop-area').style.display = 'none';
|
||||||
if (!isFileModalOpen && e.dataTransfer.files.length > 0) {
|
if (e.dataTransfer.files.length > 0) {
|
||||||
instantUploadFile(e.dataTransfer.files[0]);
|
instantUploadFile(e.dataTransfer.files[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -785,7 +742,7 @@
|
|||||||
document.getElementById('directDownloadUrl').textContent = data.download_url;
|
document.getElementById('directDownloadUrl').textContent = data.download_url;
|
||||||
document.getElementById('normalUrl').href = data.url;
|
document.getElementById('normalUrl').href = data.url;
|
||||||
document.getElementById('normalUrl').textContent = data.url;
|
document.getElementById('normalUrl').textContent = data.url;
|
||||||
document.querySelector('.instant-upload-overlay').style.display = 'flex';
|
document.querySelector('.instant-upload-result').style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
alert('Error uploading file: ' + data.error);
|
alert('Error uploading file: ' + data.error);
|
||||||
}
|
}
|
||||||
@ -796,25 +753,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this new function to close the modal
|
function closeInstantUploadResult() {
|
||||||
function closeInstantUploadModal() {
|
document.querySelector('.instant-upload-result').style.display = 'none';
|
||||||
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
|
// Paste functionality for file upload
|
||||||
document.addEventListener('paste', function(e) {
|
document.addEventListener('paste', function(e) {
|
||||||
if (document.getElementById('fileModal').style.display === 'block') {
|
if (document.getElementById('fileModal').style.display === 'block') {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<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>Login - sxbin</title>
|
<title>Login - aCloud</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
@ -97,54 +97,37 @@
|
|||||||
background-color: #2a2a2a;
|
background-color: #2a2a2a;
|
||||||
color: #f0f0f0;
|
color: #f0f0f0;
|
||||||
}
|
}
|
||||||
.error-message {
|
|
||||||
background-color: #ff6b6b;
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a href="/" class="back-button">←</a>
|
<a href="/" class="back-button">←</a>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Login to sxbin</h2>
|
<h2>Login</h2>
|
||||||
<div id="error-message" class="error-message"></div>
|
<form method="POST">
|
||||||
<form id="login-form" method="POST">
|
<div>
|
||||||
<label for="username">Username:</label>
|
<label for="username">Username:</label>
|
||||||
<input type="text" id="username" name="username" required>
|
<input type="text" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<label for="password">Password:</label>
|
<label for="password">Password:</label>
|
||||||
<input type="password" id="password" name="password" required>
|
<input type="password" id="password" name="password" required>
|
||||||
<label>
|
</div>
|
||||||
<input type="checkbox" name="remember"> Remember me
|
<div class="remember-me">
|
||||||
</label>
|
<input type="checkbox" id="remember" name="remember">
|
||||||
<input type="submit" value="Login">
|
<label for="remember">Remember me</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Login</button>
|
||||||
</form>
|
</form>
|
||||||
<p>Don't have an account? <a href="{{ url_for('register') }}">Register</a></p>
|
<p>Don't have an account? <a href="{{ url_for('register') }}">Register here</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<footer class="footer">
|
||||||
<script>
|
<p>
|
||||||
document.getElementById('login-form').addEventListener('submit', function(e) {
|
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
|
||||||
e.preventDefault();
|
<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>
|
||||||
fetch('/login', {
|
</p>
|
||||||
method: 'POST',
|
</footer>
|
||||||
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>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,22 +0,0 @@
|
|||||||
<!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>
|
|
@ -1,20 +0,0 @@
|
|||||||
<!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>
|
|
@ -1,24 +0,0 @@
|
|||||||
<!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>
|
|
@ -3,26 +3,104 @@
|
|||||||
<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>Password Protected Content - sxbin</title>
|
<title>Enter Password</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>
|
<style>
|
||||||
/* Add your styles here */
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Password Protected Content</h1>
|
<div class="container">
|
||||||
<p>This {{ content_type }} is password protected. Please enter the password to view.</p>
|
<h2>This content is password protected</h2>
|
||||||
{% if error %}
|
<div id="errorAlert" class="alert">
|
||||||
<p style="color: red;">{{ error }}</p>
|
Incorrect password. Please try again.
|
||||||
{% endif %}
|
</div>
|
||||||
<form method="POST">
|
<form method="POST" action="{{ url_for('redirect_vanity', vanity=vanity) }}">
|
||||||
<input type="password" name="password" required>
|
<input type="password" name="password" placeholder="Enter password">
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,15 +1,9 @@
|
|||||||
<!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 {{ vanity }} - sxbin</title>
|
<title>Content</title>
|
||||||
<meta property="og:title" content="Pastebin {{ vanity }} - sxbin">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:url" content="{{ request.url }}">
|
|
||||||
<meta property="og:description" content="Pastebin{% if is_private %} (Password Protected){% endif %} | Uploaded by: {{ content.username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
|
|
||||||
<meta property="og:site_name" content="sxbin">
|
|
||||||
<meta property="theme-color" content="#4CAF50">
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg-color: #1e1e1e;
|
--bg-color: #1e1e1e;
|
||||||
@ -43,7 +37,7 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2 {
|
h1 {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,31 +66,6 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight pre {
|
|
||||||
color: var(--text-color);
|
|
||||||
white-space: pre;
|
|
||||||
word-wrap: normal;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight .linenos {
|
|
||||||
color: #999;
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 10px;
|
|
||||||
border-right: 1px solid var(--highlight-border);
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight .code {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#theme-toggle {
|
#theme-toggle {
|
||||||
@ -114,6 +83,12 @@
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlight pre {
|
||||||
|
color: var(--text-color);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-container {
|
.btn-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@ -148,6 +123,68 @@
|
|||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
.home-button {
|
.home-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
@ -169,7 +206,7 @@
|
|||||||
<a href="/" class="home-button">⌂</a>
|
<a href="/" class="home-button">⌂</a>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Content</h2>
|
<h2>Content</h2>
|
||||||
<p>Uploaded by: {{ 'Anonymous' if content.username == 'None' else content.username }}</p>
|
<p>Uploaded by: {{ content.username }}</p>
|
||||||
<p>Created at: {{ created_at }}</p>
|
<p>Created at: {{ created_at }}</p>
|
||||||
|
|
||||||
<div class="highlight">
|
<div class="highlight">
|
||||||
@ -194,6 +231,19 @@
|
|||||||
|
|
||||||
<button id="theme-toggle">Toggle Theme</button>
|
<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>
|
||||||
|
|
||||||
<footer style="text-align: center; margin-top: 20px; padding: 10px; background-color: #2a2a2a; color: #f0f0f0;">
|
<footer style="text-align: center; margin-top: 20px; padding: 10px; background-color: #2a2a2a; color: #f0f0f0;">
|
||||||
<p>
|
<p>
|
||||||
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
|
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
|
||||||
@ -227,6 +277,87 @@
|
|||||||
if (localStorage.getItem('lightMode') === 'true') {
|
if (localStorage.getItem('lightMode') === 'true') {
|
||||||
html.classList.add('light-mode');
|
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>
|
@ -3,7 +3,7 @@
|
|||||||
<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>Register - sxbin</title>
|
<title>Register - aCloud</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
@ -89,83 +89,33 @@
|
|||||||
background-color: #2a2a2a;
|
background-color: #2a2a2a;
|
||||||
color: #f0f0f0;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a href="/" class="back-button">←</a>
|
<a href="/" class="back-button">←</a>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Register for sxbin</h2>
|
<h2>Register</h2>
|
||||||
<div id="error-message" class="error-message"></div>
|
<form method="POST">
|
||||||
<form id="register-form" method="POST">
|
<div>
|
||||||
<label for="username">Username:</label>
|
<label for="username">Username:</label>
|
||||||
<input type="text" id="username" name="username" required>
|
<input type="text" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<label for="password">Password:</label>
|
<label for="password">Password:</label>
|
||||||
<input type="password" id="password" name="password" required>
|
<input type="password" id="password" name="password" required>
|
||||||
<div id="password-length" class="password-requirement requirement-not-met">At least 5 characters</div>
|
</div>
|
||||||
<div id="password-uppercase" class="password-requirement requirement-not-met">At least one uppercase letter</div>
|
<button type="submit">Register</button>
|
||||||
<input type="submit" value="Register" id="submit-button" disabled>
|
|
||||||
</form>
|
</form>
|
||||||
<p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
|
<p>Already have an account? <a href="{{ url_for('login') }}">Login here</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<footer class="footer">
|
||||||
<script>
|
<p>
|
||||||
const passwordInput = document.getElementById('password');
|
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
|
||||||
const lengthRequirement = document.getElementById('password-length');
|
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
|
||||||
const uppercaseRequirement = document.getElementById('password-uppercase');
|
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
|
||||||
const submitButton = document.getElementById('submit-button');
|
</p>
|
||||||
|
</footer>
|
||||||
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>
|
</body>
|
||||||
</html>
|
</html>
|
@ -881,7 +881,7 @@
|
|||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab active" onclick="openTab(event, 'filesAndFolders')">Files and Folders</button>
|
<button class="tab active" onclick="openTab(event, 'filesAndFolders')">Files and Folders</button>
|
||||||
<button class="tab" onclick="openTab(event, 'myUploads')">My Uploads</button>
|
<button class="tab" onclick="openTab(event, 'myUploads')">My Uploads</button>
|
||||||
<button class="tab" onclick="openTab(event, 'apiKeyShareX')">API Key & ShareX</button>
|
<button class="tab" onclick="openTab(event, 'shareXConfig')">ShareX Config</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="filesAndFolders" class="tab-content active">
|
<div id="filesAndFolders" class="tab-content active">
|
||||||
@ -954,7 +954,7 @@
|
|||||||
{% elif upload.type == 'url' %}
|
{% elif upload.type == 'url' %}
|
||||||
Shortened URL:
|
Shortened URL:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ upload.url }}" target="_blank">
|
<a href="{{ url_for('redirect_vanity', vanity=upload.vanity) }}" target="_blank">
|
||||||
{% if upload.type == 'file' %}
|
{% if upload.type == 'file' %}
|
||||||
{{ upload.data }}
|
{{ upload.data }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -985,33 +985,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="apiKeyShareX" class="tab-content">
|
<div id="shareXConfig" class="tab-content">
|
||||||
<h3>API Key & ShareX</h3>
|
<h3>ShareX Configuration</h3>
|
||||||
|
<p>Click the button below to download your ShareX configuration file:</p>
|
||||||
<div class="sharex-config-section">
|
<a href="{{ url_for('generate_sharex_config') }}" class="btn" download="aCloud_ShareX.sxcu">Download ShareX Config</a>
|
||||||
<h4>ShareX Configuration</h4>
|
|
||||||
<p>Download the ShareX configuration file to easily integrate with our service:</p>
|
|
||||||
<a href="{{ url_for('generate_sharex_config') }}" class="btn">Download ShareX Config</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="api-key-section">
|
|
||||||
<h4>Your API Key</h4>
|
|
||||||
<p>API Key: <span id="apiKey" class="blurred">{{ current_user.api_key }}</span></p>
|
|
||||||
<button onclick="showResetApiKeyModal()" class="btn">Reset API Key</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Reset API Key Modal -->
|
|
||||||
<div id="resetApiKeyModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h2>Reset API Key</h2>
|
|
||||||
<p>Are you sure you want to reset your API key? This action cannot be undone.</p>
|
|
||||||
<p>Your old ShareX configuration will no longer work. You'll need to generate a new one after resetting.</p>
|
|
||||||
<div class="modal-buttons">
|
|
||||||
<button onclick="resetApiKey()" class="btn btn-danger">Reset API Key</button>
|
|
||||||
<button onclick="closeResetApiKeyModal()" class="btn">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1612,22 +1589,6 @@
|
|||||||
.file-group-link a:hover {
|
.file-group-link a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blurred {
|
|
||||||
filter: blur(5px);
|
|
||||||
transition: filter 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blurred:hover {
|
|
||||||
filter: blur(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.api-key-section, .sharex-config-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background-color: var(--highlight-bg);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -2227,38 +2188,6 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function showResetApiKeyModal() {
|
|
||||||
document.getElementById('resetApiKeyModal').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeResetApiKeyModal() {
|
|
||||||
document.getElementById('resetApiKeyModal').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetApiKey() {
|
|
||||||
fetch('/reset_api_key', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
document.getElementById('apiKey').textContent = data.new_api_key;
|
|
||||||
alert('API Key has been reset. Please update your applications and download a new ShareX configuration.');
|
|
||||||
} else {
|
|
||||||
alert('Error resetting API Key: ' + data.error);
|
|
||||||
}
|
|
||||||
closeResetApiKeyModal();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('An error occurred while resetting the API Key');
|
|
||||||
closeResetApiKeyModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue
Block a user