comics captcha and view fix

This commit is contained in:
aneuhmanh 2025-03-08 02:01:18 +02:00
parent 3fac6070ca
commit db675ccf36
7 changed files with 122 additions and 104 deletions

24
app.py
View File

@ -160,14 +160,11 @@ def view(content_type, id):
content = Comic.query.get_or_404(id) content = Comic.query.get_or_404(id)
comments = Comments.query.filter_by(comic_id=id).order_by(Comments.comment_date.desc()).all() comments = Comments.query.filter_by(comic_id=id).order_by(Comments.comment_date.desc()).all()
comic_pages_dir = os.path.join(app.config['UPLOAD_FOLDER']['comics'], content.comic_folder) comic_pages = ComicPage.query.filter_by(comic_id=id).order_by(ComicPage.page_number).all()
if not os.path.exists(comic_pages_dir): if not comic_pages:
return render_template('error.html', message="Comic pages not found") return render_template('error.html', message="Comic pages not found")
comic_pages = os.listdir(comic_pages_dir)
comic_pages = sorted(comic_pages)
all_comics = Comic.query.order_by(Comic.id).all() all_comics = Comic.query.order_by(Comic.id).all()
comic_ids = [comic.id for comic in all_comics] comic_ids = [comic.id for comic in all_comics]
current_index = comic_ids.index(id) current_index = comic_ids.index(id)
@ -530,28 +527,32 @@ async def comic_edit(comic_id):
if action == 'delete' and (page_id := request.form.get('page')): if action == 'delete' and (page_id := request.form.get('page')):
page = ComicPage.query.get(page_id) page = ComicPage.query.get(page_id)
if page: if page:
os.remove(page.file_path) os.remove(page.file_path.replace('\\', '/'))
db.session.delete(page) db.session.delete(page)
db.session.commit() db.session.commit()
pages = ComicPage.query.filter_by(comic_id=comic.id).order_by(ComicPage.page_number).all()
for i, page in enumerate(pages, start=1):
page.page_number = i
db.session.commit()
elif action == 'update' and (page_id := request.form.get('page')) and 'new_page' in request.files: elif action == 'update' and (page_id := request.form.get('page')) and 'new_page' in request.files:
new_page = request.files['new_page'] new_page = request.files['new_page']
filename = f"{uuid.uuid4().hex}.webp" filename = f"{uuid.uuid4().hex}.webp"
file_path = os.path.join(cfp, filename) file_path = os.path.join(cfp, filename).replace('\\', '/')
webp_image = await convert_to_webp(new_page) webp_image = await convert_to_webp(new_page)
async with aiofiles.open(file_path, 'wb') as f: async with aiofiles.open(file_path, 'wb') as f:
await f.write(webp_image.read()) await f.write(webp_image.read())
page = ComicPage.query.get(page_id) page = ComicPage.query.get(page_id)
if page: if page:
os.remove(page.file_path) os.remove(page.file_path.replace('\\', '/'))
page.file_path = file_path page.file_path = file_path
db.session.commit() db.session.commit()
elif action == 'add' and 'new_page' in request.files: elif action == 'add' and 'new_page' in request.files:
new_page = request.files['new_page'] new_page = request.files['new_page']
filename = f"{uuid.uuid4().hex}.webp" filename = f"{uuid.uuid4().hex}.webp"
file_path = os.path.join(cfp, filename) file_path = os.path.join(cfp, filename).replace('\\', '/')
webp_image = await convert_to_webp(new_page) webp_image = await convert_to_webp(new_page)
async with aiofiles.open(file_path, 'wb') as f: async with aiofiles.open(file_path, 'wb') as f:
await f.write(webp_image.read()) await f.write(webp_image.read())
@ -562,7 +563,8 @@ async def comic_edit(comic_id):
return redirect(url_for('comic_edit', comic_id=comic.id)) return redirect(url_for('comic_edit', comic_id=comic.id))
return render_template('comic_edit.html', comic=comic, comic_pages=comic.pages, form=form) comic_pages = ComicPage.query.filter_by(comic_id=comic.id).order_by(ComicPage.page_number).all()
return render_template('comic_edit.html', comic=comic, comic_pages=comic_pages, form=form)
def update_related_tables(old_username, new_username): def update_related_tables(old_username, new_username):
@ -862,4 +864,4 @@ def buy_item(item_id):
if __name__ == '__main__': if __name__ == '__main__':
with app.app_context(): with app.app_context():
db.create_all() db.create_all()
app.run(debug=True) app.run(debug=False)

View File

@ -29,7 +29,6 @@ from config import Config
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, FileField, BooleanField from wtforms import StringField, PasswordField, SubmitField, FileField, BooleanField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
from flask_wtf import RecaptchaField
from flask_wtf.file import FileAllowed from flask_wtf.file import FileAllowed
db = SQLAlchemy() db = SQLAlchemy()
@ -220,6 +219,14 @@ class UploadVideoForm(FlaskForm):
submit = SubmitField('Upload') submit = SubmitField('Upload')
agree_with_rules = BooleanField('I agree with the publication rules', validators=[DataRequired()]) agree_with_rules = BooleanField('I agree with the publication rules', validators=[DataRequired()])
class UploadComicForm(FlaskForm):
title = StringField('Comic Name', validators=[DataRequired()])
thumbnail = FileField('Thumbnail', validators=[DataRequired()])
tags = StringField('Tags (comma-separated)')
recaptcha = RecaptchaField()
agree_with_rules = BooleanField('I agree with the publication rules', validators=[DataRequired(message="You must agree with the publication rules.")])
submit = SubmitField('Upload')
class EditTagsForm(FlaskForm): class EditTagsForm(FlaskForm):
tags = StringField('Tags', validators=[DataRequired(), Length(max=100)], render_kw={"placeholder": "Enter tags"}) tags = StringField('Tags', validators=[DataRequired(), Length(max=100)], render_kw={"placeholder": "Enter tags"})
submit = SubmitField('Save') submit = SubmitField('Save')

15
static/js/comic_upload.js Normal file
View File

@ -0,0 +1,15 @@
let pageCount = 1;
const maxPages = 64;
function addPage() {
if (pageCount >= maxPages) {
alert(`You can't add more than ${maxPages} pages!`);
return;
}
pageCount++;
const newPage = document.createElement("div");
newPage.classList.add("form-group");
newPage.innerHTML = `<label>Page ${pageCount}:</label><input type="file" name="pages[]" class="file-input" accept="image/*" required>`;
document.getElementById("pages").appendChild(newPage);
}

View File

@ -26,8 +26,8 @@
<ul> <ul>
{% for page in comic_pages %} {% for page in comic_pages %}
<li> <li>
<img src="{{ url_for('static', filename=page.file_path) }}" alt="Comic Page {{ loop.index }}"> <img src="{{ url_for('static', filename=page.file_path) }}" alt="Comic Page {{ page.page_number }}">
<h3>Page {{ loop.index }}</h3> <h3>Page {{ page.page_number }}</h3>
<div class="form-group"> <div class="form-group">
<form method="POST" action="{{ url_for('comic_edit', comic_id=comic.id) }}" class="form-group"> <form method="POST" action="{{ url_for('comic_edit', comic_id=comic.id) }}" class="form-group">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}

View File

@ -5,30 +5,26 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>🫐comic upload - artberry🫐</title> <title>🫐comic upload - artberry🫐</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/comic_upload.css') }}"> <link rel="icon" href="{{ url_for('static', filename='artberry.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='artberry.ico') }}" type="image/x-icon">
<link href="https://fonts.googleapis.com/css2?family=Comfortaa:wght@400&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Comfortaa:wght@400&display=swap" rel="stylesheet">
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head> </head>
<body> <body>
<h1>Upload Comic</h1> <h1>Upload Comic</h1>
<form class="upload-comic-form" method="POST" action="{{ url_for('upload.comic_upload') }}" enctype="multipart/form-data"> <form class="upload-comic-form" method="POST" action="{{ url_for('upload.comic_upload') }}" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> {{ form.hidden_tag() }}
<div class="form-group"> <div class="form-group">
<label for="title">Comic Name:</label> {{ form.title.label }}
<input type="text" name="title" class="input-field" required> {{ form.title(class="input-field") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="thumbnail">Thumbnail:</label> {{ form.thumbnail.label }}
<input type="file" name="thumbnail" class="file-input" accept="image/*" required> {{ form.thumbnail(class="file-input", accept="image/*") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="tags">Tags:</label> {{ form.tags.label }}
<input type="text" name="tags" class="input-field" placeholder="Enter tags, separated by commas"> {{ form.tags(class="input-field", placeholder="Enter tags, separated by commas") }}
</div> </div>
<div id="pages"> <div id="pages">
<div class="form-group"> <div class="form-group">
<label>Page 1:</label> <label>Page 1:</label>
@ -36,24 +32,15 @@
</div> </div>
</div> </div>
<button class="button" type="button" onclick="addPage()">Add Page:</button> <button class="button" type="button" onclick="addPage()">Add Page:</button>
<button type="submit" class="button">Upload Comic:</button> <div class="form-group">
{{ form.recaptcha }}
</div>
<div class="form-group">
{{ form.agree_with_rules.label }}
{{ form.agree_with_rules }}
</div>
{{ form.submit(class="button") }}
</form> </form>
<script> <script src="{{ url_for('static', filename='js/comic_upload.js') }}"></script>
let pageCount = 1;
const maxPages = 64;
function addPage() {
if (pageCount >= maxPages) {
alert(`You cant add more than ${maxPages} pages!`);
return;
}
pageCount++;
const newPage = document.createElement("div");
newPage.classList.add("form-group");
newPage.innerHTML = `<label>Page ${pageCount}:</label><input type="file" name="pages[]" class="file-input" accept="image/*" required>`;
document.getElementById("pages").appendChild(newPage);
}
</script>
</body> </body>
</html> </html>

View File

@ -43,13 +43,13 @@
<div class="comic-pages"> <div class="comic-pages">
{% if comic_pages %} {% if comic_pages %}
{% for page in comic_pages %} {% for page in comic_pages %}
<img src="{{ url_for('static', filename='comics/' + content.comic_folder + '/' + page) }}" alt="Page {{ loop.index }}"> <img src="{{ url_for('static', filename=page.file_path.replace('static/', '').replace('\\', '/')) }}" alt="Page {{ page.page_number }}">
{% endfor %} {% endfor %}
{% else %} {% else %}
<p>No pages available for this comic.</p> <p>No pages available for this comic.</p>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{% if current_user.is_authenticated and current_user.username == content.username %} {% if current_user.is_authenticated and current_user.username == content.username %}
<form method="POST" action="{{ url_for('delete', content_type=content_type, content_id=content.id) }}"> <form method="POST" action="{{ url_for('delete', content_type=content_type, content_id=content.id) }}">

View File

@ -4,7 +4,7 @@ import aiofiles
from flask import Blueprint, render_template, redirect, url_for, request, current_app from flask import Blueprint, render_template, redirect, url_for, request, current_app
from flask_login import login_required, current_user from flask_login import login_required, current_user
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from models import db, Image, Video, Comic, ComicPage, Post, Cookies, UploadForm, UploadVideoForm from models import db, Image, Video, Comic, ComicPage, Post, Cookies, UploadForm, UploadVideoForm, UploadComicForm
from utils import allowed_file, check_file_content, check_file_size, convert_to_webp, generate_unique_filename from utils import allowed_file, check_file_content, check_file_size, convert_to_webp, generate_unique_filename
upload_bp = Blueprint('upload', __name__) upload_bp = Blueprint('upload', __name__)
@ -102,62 +102,69 @@ async def upload_video():
@upload_bp.route('/comic_upload', methods=['GET', 'POST']) @upload_bp.route('/comic_upload', methods=['GET', 'POST'])
@login_required @login_required
async def comic_upload(): async def comic_upload():
form = UploadComicForm()
if request.method == 'POST': if request.method == 'POST':
ct = request.files['thumbnail'] if form.validate_on_submit():
n = request.form['title'] ct = form.thumbnail.data
tags = request.form.get('tags', '') n = form.title.data
tags = form.tags.data
allowed_image_mime_types = {'image/png', 'image/jpeg', 'image/gif', 'image/webp'} allowed_image_mime_types = {'image/png', 'image/jpeg', 'image/gif', 'image/webp'}
if db.session.execute(db.select(Comic).filter_by(name=n)).scalar(): if db.session.execute(db.select(Comic).filter_by(name=n)).scalar():
return render_template('comic_upload.html') return render_template('comic_upload.html', form=form)
if ct: if ct:
if not (allowed_file(ct.filename, current_app.config['ALLOWED_IMAGE_EXTENSIONS']) and if not (allowed_file(ct.filename, current_app.config['ALLOWED_IMAGE_EXTENSIONS']) and
check_file_content(ct, allowed_image_mime_types)): check_file_content(ct, allowed_image_mime_types)):
return redirect(url_for('upload.comic_upload')) return redirect(url_for('upload.comic_upload'))
tf = f"{uuid.uuid4().hex}.webp" tf = f"{uuid.uuid4().hex}.webp"
tp = os.path.join(current_app.config['UPLOAD_FOLDER']['comicthumbs'], tf) tp = os.path.join(current_app.config['UPLOAD_FOLDER']['comicthumbs'], tf)
webp_thumbnail = await convert_to_webp(ct) webp_thumbnail = await convert_to_webp(ct)
async with aiofiles.open(tp, 'wb') as f: async with aiofiles.open(tp, 'wb') as f:
await f.write(webp_thumbnail.read()) await f.write(webp_thumbnail.read())
cf = os.path.join(current_app.config['UPLOAD_FOLDER']['comics'], n) cf = os.path.join(current_app.config['UPLOAD_FOLDER']['comics'], n)
os.makedirs(cf, exist_ok=True) os.makedirs(cf, exist_ok=True)
new_comic = Comic( new_comic = Comic(
comic_folder=n, comic_folder=n,
comic_thumbnail_file=tf, comic_thumbnail_file=tf,
username=current_user.username, username=current_user.username,
name=n, name=n,
tags=tags tags=tags
) )
db.session.add(new_comic) db.session.add(new_comic)
db.session.flush() db.session.flush()
async def save_pages(): async def save_pages():
pages = request.files.getlist('pages[]') pages = request.files.getlist('pages[]')
for i, p in enumerate(sorted(pages, key=lambda x: x.filename), start=1): for i, p in enumerate(sorted(pages, key=lambda x: x.filename), start=1):
if p: if p:
if not (allowed_file(p.filename, current_app.config['ALLOWED_IMAGE_EXTENSIONS']) and if not (allowed_file(p.filename, current_app.config['ALLOWED_IMAGE_EXTENSIONS']) and
check_file_content(p, allowed_image_mime_types)): check_file_content(p, allowed_image_mime_types)):
return redirect(url_for('upload.comic_upload')) return redirect(url_for('upload.comic_upload'))
filename = f"{uuid.uuid4().hex}.webp" filename = f"{uuid.uuid4().hex}.webp"
file_path = os.path.join(cf, filename) file_path = os.path.join(cf, filename)
webp_page = await convert_to_webp(p) webp_page = await convert_to_webp(p)
async with aiofiles.open(file_path, 'wb') as f: async with aiofiles.open(file_path, 'wb') as f:
await f.write(webp_page.read()) await f.write(webp_page.read())
new_page = ComicPage(comic_id=new_comic.id, page_number=i, file_path=file_path) new_page = ComicPage(comic_id=new_comic.id, page_number=i, file_path=file_path)
db.session.add(new_page) db.session.add(new_page)
db.session.commit() db.session.commit()
await save_pages() await save_pages()
return redirect(url_for('comics')) return redirect(url_for('comics'))
else:
for field, errors in form.errors.items():
for error in errors:
pass
return render_template('comic_upload.html') return render_template('comic_upload.html', form=form)
@upload_bp.route('/upload_post', methods=['GET', 'POST']) @upload_bp.route('/upload_post', methods=['GET', 'POST'])
@login_required @login_required