forked from rDrama/rDrama
commit
28695c459c
|
@ -76,6 +76,9 @@ app.config["SPAM_URL_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_URL_SIMILA
|
|||
app.config["COMMENT_SPAM_SIMILAR_THRESHOLD"] = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD", 0.5))
|
||||
app.config["COMMENT_SPAM_COUNT_THRESHOLD"] = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD", 0.5))
|
||||
|
||||
# how many coins are required to upload videos
|
||||
app.config["VIDEO_COIN_REQUIREMENT"] = int(environ.get("VIDEO_COIN_REQUIREMENT", 0))
|
||||
|
||||
app.config["CACHE_REDIS_URL"] = environ.get("REDIS_URL").strip()
|
||||
app.config["CACHE_DEFAULT_TIMEOUT"] = 60
|
||||
app.config["CACHE_KEY_PREFIX"] = "flask_caching_"
|
||||
|
|
|
@ -45,6 +45,7 @@ class Submission(Base, Stndrd, Age_times, Scores, Fuzzing):
|
|||
thumburl = Column(String)
|
||||
is_banned = Column(Boolean, default=False)
|
||||
bannedfor = Column(Boolean)
|
||||
processing = Column(Boolean, default=False)
|
||||
views = Column(Integer, default=0)
|
||||
deleted_utc = Column(Integer, default=0)
|
||||
distinguish_level = Column(Integer, default=0)
|
||||
|
@ -400,6 +401,13 @@ class Submission(Base, Stndrd, Age_times, Scores, Fuzzing):
|
|||
if self.url: return self.url.lower().endswith('.jpg') or self.url.lower().endswith('.png') or self.url.lower().endswith('.gif') or self.url.lower().endswith('.jpeg') or self.url.lower().endswith('?maxwidth=9999')
|
||||
else: return False
|
||||
|
||||
@property
|
||||
def is_video(self) -> bool:
|
||||
if self.url:
|
||||
return self.url.startswith("https://i.imgur.com") and self.url.lower().endswith('.mp4')
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def active_flags(self): return self.flags.count()
|
||||
|
|
|
@ -7,7 +7,16 @@ from .sanitize import *
|
|||
from .const import *
|
||||
|
||||
|
||||
def send_notification(vid, user, text):
|
||||
def send_notification(vid, user, text, db=None):
|
||||
|
||||
# for when working outside request context
|
||||
if isinstance(user, int):
|
||||
uid = user
|
||||
else:
|
||||
uid = user.id
|
||||
|
||||
if not db:
|
||||
db = g.db
|
||||
|
||||
text = text.replace('r/', 'r\/').replace('u/', 'u\/')
|
||||
text = text.replace("\n", "\n\n").replace("\n\n\n\n\n\n", "\n\n").replace("\n\n\n\n", "\n\n").replace("\n\n\n", "\n\n")
|
||||
|
@ -20,19 +29,19 @@ def send_notification(vid, user, text):
|
|||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
db.add(new_comment)
|
||||
|
||||
g.db.flush()
|
||||
db.flush()
|
||||
|
||||
new_aux = CommentAux(id=new_comment.id,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_aux)
|
||||
db.add(new_aux)
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user.id)
|
||||
g.db.add(notif)
|
||||
user_id=uid)
|
||||
db.add(notif)
|
||||
|
||||
|
||||
def send_pm(vid, user, text):
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import requests
|
||||
from os import environ
|
||||
from os import environ, path, remove
|
||||
from PIL import Image as IImage, ImageSequence
|
||||
import base64
|
||||
from files.classes.images import *
|
||||
from flask import g
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
CF_KEY = environ.get("CLOUDFLARE_KEY", "").strip()
|
||||
CF_ZONE = environ.get("CLOUDFLARE_ZONE", "").strip()
|
||||
|
@ -86,4 +87,36 @@ def upload_imgur(file=None, resize=False, png=False):
|
|||
|
||||
new_image = Image(text=url, deletehash=resp["deletehash"])
|
||||
g.db.add(new_image)
|
||||
return(url)
|
||||
return(url)
|
||||
|
||||
|
||||
class UploadException(Exception):
|
||||
"""Custom exception to raise if upload goes wrong"""
|
||||
pass
|
||||
|
||||
|
||||
def upload_video(file):
|
||||
|
||||
file_path = path.join("temp", secure_filename(file.filename))
|
||||
file.save(file_path)
|
||||
|
||||
headers = {"Authorization": f"Client-ID {IMGUR_KEY}"}
|
||||
with open(file_path, 'rb') as f:
|
||||
try:
|
||||
r = requests.post('https://api.imgur.com/3/upload', headers=headers, files={"video": f})
|
||||
|
||||
r.raise_for_status()
|
||||
|
||||
resp = r.json()['data']
|
||||
except requests.HTTPError as e:
|
||||
raise UploadException("Invalid video. Make sure it's 1 minute long or shorter.")
|
||||
except Exception:
|
||||
raise UploadException("Error, please try again later.")
|
||||
finally:
|
||||
remove(file_path)
|
||||
|
||||
link = resp['link']
|
||||
img = Image(text=link, deletehash=resp['deletehash'])
|
||||
g.db.add(img)
|
||||
|
||||
return link
|
||||
|
|
|
@ -121,6 +121,11 @@ def frontlist(v=None, sort="hot", page=1, t="all", ids_only=True, filter_words='
|
|||
|
||||
posts = posts.filter_by(is_banned=False,stickied=False,private=False).filter(Submission.deleted_utc == 0)
|
||||
|
||||
if v:
|
||||
posts = posts.filter(or_(Submission.processing == False, Submission.author_id == v.id))
|
||||
else:
|
||||
posts = posts.filter_by(processing=False)
|
||||
|
||||
if v and v.admin_level == 0:
|
||||
blocking = g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import time
|
||||
from urllib.parse import urlparse
|
||||
import mistletoe
|
||||
import urllib.parse
|
||||
|
@ -527,6 +528,44 @@ def filter_title(title):
|
|||
|
||||
return title
|
||||
|
||||
|
||||
IMGUR_KEY = environ.get("IMGUR_KEY", "").strip()
|
||||
|
||||
|
||||
def check_processing_thread(v, post, link, db):
|
||||
|
||||
image_id = link.split('/')[-1].rstrip('.mp4')
|
||||
headers = {"Authorization": f"Client-ID {IMGUR_KEY}"}
|
||||
|
||||
while True:
|
||||
# break on error to prevent zombie threads
|
||||
try:
|
||||
time.sleep(15)
|
||||
|
||||
req = requests.get(f"https://api.imgur.com/3/image/{image_id}", headers=headers)
|
||||
|
||||
status = req.json()['data']['processing']['status']
|
||||
if status == 'completed':
|
||||
post.processing = False
|
||||
db.add(post)
|
||||
|
||||
send_notification(
|
||||
NOTIFICATIONS_ACCOUNT,
|
||||
v,
|
||||
f"Your video has finished processing and your [post](/post/{post.id}) is now live.",
|
||||
db=db
|
||||
)
|
||||
|
||||
db.commit()
|
||||
break
|
||||
# just in case
|
||||
elif status == 'failed':
|
||||
print(f"video upload for post {post.id} failed")
|
||||
break
|
||||
except Exception:
|
||||
break
|
||||
|
||||
|
||||
@app.post("/submit")
|
||||
@limiter.limit("6/minute")
|
||||
@is_not_banned
|
||||
|
@ -827,13 +866,72 @@ def submit_post(v):
|
|||
abort(413)
|
||||
|
||||
file = request.files['file']
|
||||
if not file.content_type.startswith('image/'):
|
||||
if request.headers.get("Authorization"): return {"error": f"Image files only"}, 400
|
||||
else: return render_template("submit.html", v=v, error=f"Image files only.", title=title, body=request.form.get("body", "")), 400
|
||||
#if not file.content_type.startswith('image/'):
|
||||
# if request.headers.get("Authorization"): return {"error": f"Image files only"}, 400
|
||||
# else: return render_template("submit.html", v=v, error=f"Image files only.", title=title, body=request.form.get("body", "")), 400
|
||||
|
||||
if not file.content_type.startswith(('image/', 'video/')):
|
||||
if request.headers.get("Authorization"): return {"error": f"File type not allowed"}, 400
|
||||
else: return render_template("submit.html", v=v, error=f"File type not allowed.", title=title, body=request.form.get("body", "")), 400
|
||||
|
||||
if 'pcm' in request.host: new_post.url = upload_ibb(file)
|
||||
else: new_post.url = upload_imgur(file)
|
||||
if file.content_type.startswith('video/') and v.coins < app.config["VIDEO_COIN_REQUIREMENT"] and v.admin_level < 1:
|
||||
if request.headers.get("Authorization"):
|
||||
return {
|
||||
"error": f"You need at least {app.config['VIDEO_COIN_REQUIREMENT']} coins to upload videos"
|
||||
}, 403
|
||||
else:
|
||||
return render_template(
|
||||
"submit.html",
|
||||
v=v,
|
||||
error=f"You need at least {app.config['VIDEO_COIN_REQUIREMENT']} coins to upload videos.",
|
||||
title=title,
|
||||
body=request.form.get("body", "")
|
||||
), 403
|
||||
|
||||
if 'pcm' in request.host:
|
||||
if file.content_type.startswith('image/'):
|
||||
new_post.url = upload_ibb(file)
|
||||
else:
|
||||
try:
|
||||
post_url = upload_video(file)
|
||||
new_post.url = post_url
|
||||
new_post.processing = True
|
||||
gevent.spawn(check_processing_thread, v.id, new_post, post_url, g.db)
|
||||
except UploadException as e:
|
||||
if request.headers.get("Authorization"):
|
||||
return {
|
||||
"error": str(e),
|
||||
}, 400
|
||||
else:
|
||||
return render_template(
|
||||
"submit.html",
|
||||
v=v,
|
||||
error=str(e),
|
||||
title=title,
|
||||
body=request.form.get("body", "")
|
||||
), 400
|
||||
else:
|
||||
if file.content_type.startswith('image/'):
|
||||
new_post.url = upload_imgur(file)
|
||||
else:
|
||||
try:
|
||||
post_url = upload_video(file)
|
||||
new_post.url = post_url
|
||||
new_post.processing = True
|
||||
gevent.spawn(check_processing_thread, v.id, new_post, post_url, g.db)
|
||||
except UploadException as e:
|
||||
if request.headers.get("Authorization"):
|
||||
return {
|
||||
"error": str(e),
|
||||
}, 400
|
||||
else:
|
||||
return render_template(
|
||||
"submit.html",
|
||||
v=v,
|
||||
error=str(e),
|
||||
title=title,
|
||||
body=request.form.get("body", "")
|
||||
), 400
|
||||
|
||||
g.db.add(new_post)
|
||||
g.db.add(new_post.submission_aux)
|
||||
|
|
|
@ -36,6 +36,9 @@
|
|||
<meta property="og:author" name="author" content="{{'@'+comment_info.author.username}}" />
|
||||
<meta property="og:title" content="{{'@'+comment_info.author.username}} comments on {{p.title}} - {{'SITE_NAME' | app_config}}" />
|
||||
<meta property="og:image" content="{% if p.is_image %}{{p.realurl(v)}}{% elif p.has_thumb%}{{p.thumb_url}}{% else %}{{'SITE_NAME' | app_config}}/assets/images/{{'SITE_NAME' | app_config}}/preview.gif{% endif %}" />
|
||||
{% if p.is_video %}
|
||||
<meta property="og:video" content="{{ p.realurl(v) }}" />
|
||||
{% endif %}
|
||||
<meta property="og:url" content="{{comment_info.permalink | full_link}}" />
|
||||
<meta property="og:site_name" content="{{request.host}}" />
|
||||
|
||||
|
@ -62,6 +65,9 @@
|
|||
<meta property="og:author" name="author" content="{{'@'+p.author.username}}" />
|
||||
<meta property="og:title" content="{{p.title}} - {{'SITE_NAME' | app_config}}" />
|
||||
<meta property="og:image" content="{% if p.is_image %}{{p.realurl(v)}}{% elif p.has_thumb%}{{p.thumb_url}}{% else %}{{'SITE_NAME' | app_config}}/assets/images/{{'SITE_NAME' | app_config}}/preview.gif{% endif %}" />
|
||||
{% if p.is_video %}
|
||||
<meta property="og:video" content="{{ p.realurl(v) }}" />
|
||||
{% endif %}
|
||||
<meta property="og:url" content="{{p.permalink | full_link}}" />
|
||||
<meta property="og:site_name" content="{{request.host}}" />
|
||||
|
||||
|
@ -256,6 +262,7 @@
|
|||
{% if p.is_bot %} <i class="fad fa-robot text-info" data-toggle="tooltip" data-placement="bottom" data-original-title="Bot"></i>{% endif %}
|
||||
{% if p.over_18 %}<span class="badge badge-danger text-small-extra mr-1">+18</span>{% endif %}
|
||||
{% if p.private %}<span class="badge border-warning border-1 text-small-extra">Draft</span>{% endif %}
|
||||
{% if p.processing %}<span class="badge border-warning border-1 text-small-extra">uploading...</span>{% endif %}
|
||||
{% if p.active_flags %}<a class="btn btn-primary" href="javascript:void(0)" style="padding:1px 5px; font-size:10px;" onclick="document.getElementById('flaggers').classList.toggle('d-none')">{{p.active_flags}} Reports</a>{% endif %}
|
||||
{% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1" style="color:#1DA1F2" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="{{p.author.verified}}"></i>
|
||||
{% endif %}
|
||||
|
@ -301,7 +308,7 @@
|
|||
<iframe src="{{streamurl}}" width="100%" height="80" frameBorder="0" allowtransparency="true" allow="encrypted-media"></iframe>
|
||||
<pre></pre>
|
||||
|
||||
{% elif not p.embed_url and not p.is_image %}
|
||||
{% elif not p.embed_url and not p.is_image and not p.is_video %}
|
||||
<a rel="nofollow noopener noreferrer" href="{{p.realurl(v)}}" {% if not v or v.newtabexternal %}target="_blank"{% endif %}>
|
||||
<div class="d-flex d-md-none justify-content-between align-items-center border rounded p-2{% if p.realbody(v) %} mb-3{% endif %}">
|
||||
<span>{{p.domain|truncate(30, True)}}</span>
|
||||
|
@ -321,6 +328,15 @@
|
|||
</div>
|
||||
</div>
|
||||
<pre></pre>
|
||||
{% elif p.is_video %}
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
<video controls preload="metadata" style="max-width: 100%">
|
||||
<source src="{{ p.realurl(v) }}" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
<pre></pre>
|
||||
{% endif %}
|
||||
{{p.realbody(v) | safe}}
|
||||
</div>
|
||||
|
@ -497,7 +513,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
{% if not p.is_image %}
|
||||
{% if not p.is_image and not p.is_video %}
|
||||
<div class="row no-gutters d-block d-md-none">
|
||||
<div class="col">
|
||||
<a {% if not v or v.newtabexternal %}target="_blank"{% endif %} rel="nofollow noopener noreferrer" href="{{p.realurl(v)}}"></a>
|
||||
|
|
|
@ -69,10 +69,10 @@
|
|||
<img loading="lazy" src="{{p.thumb_url}}" class="post-img">
|
||||
</a>
|
||||
{% elif p.is_image %}
|
||||
<a href="javascript:void(0)" class="expandable-image" data-toggle="modal" data-target="#expandImageModal" data-url="{{p.realurl(v)}}">
|
||||
<a href="javascript:void(0)" class="expandable-image" data-toggle="modal" data-target="#expandImageModal" data-url="{{p.realurl(v)}}" onclick="expandDesktopImage('{{ p.realurl(v) }}')">
|
||||
<img loading="lazy" src="{{p.thumb_url}}" class="post-img">
|
||||
</a>
|
||||
{% else %}
|
||||
{% elif not p.is_video %}
|
||||
<a {% if not v or v.newtabexternal %}target="_blank"{% endif %} rel="nofollow noopener noreferrer" href="{{p.realurl(v)}}">
|
||||
<img loading="lazy" src="{{p.thumb_url}}" class="post-img">
|
||||
</a>
|
||||
|
@ -104,6 +104,7 @@
|
|||
{% if p.is_blocking %}<i class="fas fa-user-minus text-warning" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="You're blocking this user, but you can see this post because {{'it\'s an admin post' if p.distinguish_level else 'you\'re an admin'}}."></i>{% endif %}
|
||||
{% if p.is_blocked %}<i class="fas fa-user-minus text-danger" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="This user is blocking you."></i>{% endif %}
|
||||
{% if p.private %}<span class="badge border-warning border-1 text-small-extra">Draft</span>{% endif %}
|
||||
{% if p.processing %}<span class="badge border-warning border-1 text-small-extra">uploading...</span>{% endif %}
|
||||
{% if p.active_flags %}<a class="btn btn-primary" href="javascript:void(0)" style="padding:1px 5px; font-size:10px;" onclick="document.getElementById('flaggers-{{p.id}}').classList.toggle('d-none')">{{p.active_flags}} Reports</a>{% endif %}
|
||||
{% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1" style="color:#1DA1F2" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="{{p.author.verified}}"></i>
|
||||
{% endif %}
|
||||
|
@ -392,6 +393,14 @@
|
|||
<img loading="lazy" src="{{p.url}}" class="img-fluid" style="max-height:500px;" alt="Unable to load image">
|
||||
</a>
|
||||
</div>
|
||||
{% elif p.is_video %}
|
||||
<a href="javascript:void(0)">
|
||||
<div style="text-align: center" class="mt-4">
|
||||
<video controls preload="metadata" style="max-width: 100%">
|
||||
<source src="{{p.realurl(v)}}" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
</a>
|
||||
{% elif p.embed_url and "youtu" in p.domain or "streamable.com/" in p.url %}
|
||||
<div style="text-align: center" class="mt-3 mb-4">
|
||||
<iframe src="{{p.embed_url}}" frameborder="0" width="600" height="337" allow="fullscreen"></iframe>
|
||||
|
|
|
@ -330,16 +330,17 @@
|
|||
</div>
|
||||
|
||||
<div id="image-upload-block">
|
||||
<div><label class="mt-3">Image Upload</label></div>
|
||||
<div><label class="mt-3">Attachment Upload</label></div>
|
||||
|
||||
<img loading="lazy" id="image-preview" class="w-100 d-block">
|
||||
|
||||
<label class="btn btn-secondary m-0" for="file-upload">
|
||||
<div id="filename-show">Select Image</div>
|
||||
<input id="file-upload" type="file" name="file" accept="image/*" hidden>
|
||||
<div id="filename-show">Select File</div>
|
||||
<input id="file-upload" type="file" name="file" accept="image/*, video/*" hidden>
|
||||
</label>
|
||||
|
||||
<small class="form-text text-muted">Images uploaded will be public. Optional if you have text.</small>
|
||||
<small class="form-text text-muted">Optional if you have text.</small>
|
||||
<small class="form-text text-muted">You can upload videos up to 1 minute long if you have at least {{ 'VIDEO_COIN_REQUIREMENT' | app_config }} {{ 'COINS_NAME' | app_config }}{% if v.admin_level > 1 %} or are an admin{% endif %}.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in New Issue