remove holes

This commit is contained in:
justcool393 2023-02-07 05:37:08 -06:00
parent 175f9c1d22
commit 0adbec2b66
24 changed files with 12 additions and 1005 deletions

View file

@ -65,7 +65,6 @@ from .badges import BadgeDef, Badge
from .clients import OauthApp, ClientAuth from .clients import OauthApp, ClientAuth
from .comment import Comment from .comment import Comment
from .domains import BannedDomain from .domains import BannedDomain
from .exiles import Exile
from .flags import Flag, CommentFlag from .flags import Flag, CommentFlag
from .follows import Follow from .follows import Follow
from .marsey import Marsey from .marsey import Marsey
@ -73,8 +72,6 @@ from .mod import Mod
from .mod_logs import ModAction from .mod_logs import ModAction
from .notifications import Notification from .notifications import Notification
from .saves import SaveRelationship, CommentSaveRelationship from .saves import SaveRelationship, CommentSaveRelationship
from .sub import Sub
from .sub_block import SubBlock
from .submission import Submission from .submission import Submission
from .subscriptions import Subscription from .subscriptions import Subscription
from .user import User from .user import User

View file

@ -1,18 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
class Exile(Base):
__tablename__ = "exiles"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
exiler_id = Column(Integer, ForeignKey("users.id"), nullable=False)
Index('fki_exile_exiler_fkey', exiler_id)
Index('fki_exile_sub_fkey', sub)
exiler = relationship("User", primaryjoin="User.id==Exile.exiler_id", viewonly=True)
def __repr__(self):
return f"<Exile(user_id={self.user_id}, sub={self.sub})>"

View file

@ -1,26 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import *
import time
class Mod(Base):
__tablename__ = "mods"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
created_utc = Column(Integer, nullable=False)
Index('fki_mod_sub_fkey', sub)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Mod(user_id={self.user_id}, sub={self.sub})>"
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))

View file

@ -279,11 +279,6 @@ ACTIONTYPES = {
"icon": 'fa-sack-dollar', "icon": 'fa-sack-dollar',
"color": 'bg-success' "color": 'bg-success'
}, },
'move_hole': {
"str": 'moved {self.target_link} to <a href="/h/{self.target_post.sub}">/h/{self.target_post.sub}</a>',
"icon": 'fa-manhole',
"color": 'bg-primary'
},
'nuke_user': { 'nuke_user': {
"str": 'removed all content of {self.target_link}', "str": 'removed all content of {self.target_link}',
"icon": 'fa-radiation-alt', "icon": 'fa-radiation-alt',

View file

@ -1,50 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
from os import environ
from .sub_block import *
SITE = environ.get("DOMAIN", '').strip()
if SITE == "localhost": SITE_FULL = 'http://' + SITE
else: SITE_FULL = 'https://' + SITE
class Sub(Base):
__tablename__ = "subs"
name = Column(String, primary_key=True)
sidebar = Column(String)
sidebar_html = Column(String)
sidebarurl = Column(String)
bannerurl = Column(String)
css = Column(String)
Index('subs_idx', name)
blocks = relationship("SubBlock", lazy="dynamic", primaryjoin="SubBlock.sub==Sub.name", viewonly=True)
def __repr__(self):
return f"<Sub(name={self.name})>"
@property
@lazy
def sidebar_url(self):
if self.sidebarurl: return SITE_FULL + self.sidebarurl
return None # Add default sidebar for subs if subs are used again
@property
@lazy
def banner_url(self):
if self.bannerurl: return SITE_FULL + self.bannerurl
return None # Add default banner for subs if subs are used again
@property
@lazy
def subscription_num(self):
return self.subscriptions.count()
@property
@lazy
def block_num(self):
return self.blocks.count()

View file

@ -1,14 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
class SubBlock(Base):
__tablename__ = "sub_blocks"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
Index('fki_sub_blocks_sub_fkey', sub)
def __repr__(self):
return f"<SubBlock(user_id={self.user_id}, sub={self.sub})>"

View file

@ -13,7 +13,6 @@ from files.helpers.assetcache import assetcache_path
from .flags import Flag from .flags import Flag
from .comment import Comment from .comment import Comment
from flask import g from flask import g
from .sub import Sub
from .votes import CommentVote from .votes import CommentVote
class Submission(Base): class Submission(Base):
@ -32,7 +31,6 @@ class Submission(Base):
distinguish_level = Column(Integer, default=0, nullable=False) distinguish_level = Column(Integer, default=0, nullable=False)
stickied = Column(String) stickied = Column(String)
stickied_utc = Column(Integer) stickied_utc = Column(Integer)
sub = Column(String, ForeignKey("subs.name"))
is_pinned = Column(Boolean, default=False, nullable=False) is_pinned = Column(Boolean, default=False, nullable=False)
private = Column(Boolean, default=False, nullable=False) private = Column(Boolean, default=False, nullable=False)
club = Column(Boolean, default=False, nullable=False) club = Column(Boolean, default=False, nullable=False)
@ -72,7 +70,6 @@ class Submission(Base):
awards = relationship("AwardRelationship", viewonly=True) awards = relationship("AwardRelationship", viewonly=True)
reports = relationship("Flag", viewonly=True) reports = relationship("Flag", viewonly=True)
comments = relationship("Comment", primaryjoin="Comment.parent_submission==Submission.id") comments = relationship("Comment", primaryjoin="Comment.parent_submission==Submission.id")
subr = relationship("Sub", primaryjoin="foreign(Submission.sub)==remote(Sub.name)", viewonly=True)
notes = relationship("UserNote", back_populates="post") notes = relationship("UserNote", back_populates="post")
bump_utc = deferred(Column(Integer, server_default=FetchedValue())) bump_utc = deferred(Column(Integer, server_default=FetchedValue()))
@ -199,8 +196,6 @@ class Submission(Base):
@lazy @lazy
def shortlink(self): def shortlink(self):
link = f"/post/{self.id}" link = f"/post/{self.id}"
if self.sub: link = f"/h/{self.sub}{link}"
if self.club: return link + '/-' if self.club: return link + '/-'
output = title_regex.sub('', self.title.lower()) output = title_regex.sub('', self.title.lower())

View file

@ -13,9 +13,6 @@ from .userblock import *
from .badges import * from .badges import *
from .clients import * from .clients import *
from .mod_logs import * from .mod_logs import *
from .mod import *
from .exiles import *
from .sub_block import *
from files.__main__ import app, Base, cache from files.__main__ import app, Base, cache
from files.helpers.security import * from files.helpers.security import *
from files.helpers.assetcache import assetcache_path from files.helpers.assetcache import assetcache_path
@ -169,30 +166,6 @@ class User(Base):
accountAgeDays = (datetime.now() - datetime.fromtimestamp(self.created_utc)).days accountAgeDays = (datetime.now() - datetime.fromtimestamp(self.created_utc)).days
return self.comment_count < minComments or accountAgeDays < minAge or self.truecoins < minKarma return self.comment_count < minComments or accountAgeDays < minAge or self.truecoins < minKarma
@lazy
def mods(self, sub):
return self.admin_level == 3 or bool(g.db.query(Mod.user_id).filter_by(user_id=self.id, sub=sub).one_or_none())
@lazy
def exiled_from(self, sub):
return self.admin_level < 2 and bool(g.db.query(Exile.user_id).filter_by(user_id=self.id, sub=sub).one_or_none())
@property
@lazy
def all_blocks(self):
return [x[0] for x in g.db.query(SubBlock.sub).filter_by(user_id=self.id).all()]
@lazy
def blocks(self, sub):
return g.db.query(SubBlock).filter_by(user_id=self.id, sub=sub).one_or_none()
@lazy
def mod_date(self, sub):
if self.id == OWNER_ID: return 1
mod = g.db.query(Mod).filter_by(user_id=self.id, sub=sub).one_or_none()
if not mod: return None
return mod.created_utc
@property @property
@lazy @lazy
def csslazy(self): def csslazy(self):
@ -474,12 +447,6 @@ class User(Base):
return output return output
@property
@lazy
def moderated_subs(self):
modded_subs = g.db.query(Mod.sub).filter_by(user_id=self.id).all()
return modded_subs
def has_follower(self, user): def has_follower(self, user):
return g.db.query(Follow).filter_by(target_id=self.id, user_id=user.id).one_or_none() return g.db.query(Follow).filter_by(target_id=self.id, user_id=user.id).one_or_none()

View file

@ -99,7 +99,6 @@ def seed_db():
embed_url=None, embed_url=None,
title=f'Clever unique post title number {i}', title=f'Clever unique post title number {i}',
title_html=f'Clever unique post title number {i}', title_html=f'Clever unique post title number {i}',
sub=None,
ghost=False, ghost=False,
filter_state='normal' filter_state='normal'
) )

View file

@ -69,7 +69,6 @@ def notif_comment2(p):
if existing: return existing[0] if existing: return existing[0]
else: else:
text = f"@{p.author.username} has mentioned you: [{p.title}](/post/{p.id})" text = f"@{p.author.username} has mentioned you: [{p.title}](/post/{p.id})"
if p.sub: text += f" in <a href='/h/{p.sub}'>/h/{p.sub}"
text_html = sanitize(text, alert=True) text_html = sanitize(text, alert=True)
return create_comment(text_html) return create_comment(text_html)

View file

@ -3,7 +3,6 @@ import re
from copy import deepcopy from copy import deepcopy
from json import loads from json import loads
from files.__main__ import db_session from files.__main__ import db_session
from files.classes.sub import Sub
from files.classes.marsey import Marsey from files.classes.marsey import Marsey
from flask import request from flask import request

View file

@ -18,4 +18,3 @@ from .awards import *
from .volunteer import * from .volunteer import *
if app.debug: if app.debug:
from .dev import * from .dev import *
# from .subs import *

View file

@ -14,8 +14,6 @@ from collections import Counter
@app.get("/comment/<cid>") @app.get("/comment/<cid>")
@app.get("/post/<pid>/<anything>/<cid>") @app.get("/post/<pid>/<anything>/<cid>")
# @app.get("/h/<sub>/comment/<cid>")
# @app.get("/h/<sub>/post/<pid>/<anything>/<cid>")
@auth_desired @auth_desired
def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None): def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
comment = get_comment(cid, v=v) comment = get_comment(cid, v=v)
@ -104,7 +102,7 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
else: else:
if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html" if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html"
else: template = "submission.html" else: template = "submission.html"
return render_template(template, v=v, p=post, sort=sort, comment_info=comment_info, render_replies=True, sub=post.subr) return render_template(template, v=v, p=post, sort=sort, comment_info=comment_info, render_replies=True)
@app.post("/comment") @app.post("/comment")
@limiter.limit("1/second;20/minute;200/hour;1000/day") @limiter.limit("1/second;20/minute;200/hour;1000/day")
@ -119,7 +117,6 @@ def api_comment(v):
parent = None parent = None
parent_post = None parent_post = None
parent_comment_id = None parent_comment_id = None
sub = None
if parent_fullname.startswith("t2_"): if parent_fullname.startswith("t2_"):
parent = get_post(id, v=v) parent = get_post(id, v=v)
@ -131,8 +128,6 @@ def api_comment(v):
else: abort(400) else: abort(400)
if not parent_post: abort(404) # don't allow sending comments to the ether if not parent_post: abort(404) # don't allow sending comments to the ether
level = 1 if isinstance(parent, Submission) else parent.level + 1 level = 1 if isinstance(parent, Submission) else parent.level + 1
sub = parent_post.sub
if sub and v.exiled_from(sub): abort(403, f"You're exiled from /h/{sub}")
body = request.values.get("body", "").strip()[:10000] body = request.values.get("body", "").strip()[:10000]
@ -451,46 +446,6 @@ def unpin_comment(cid, v):
return {"message": "Comment unpinned!"} return {"message": "Comment unpinned!"}
@app.post("/mod_pin/<cid>")
@auth_required
def mod_pin(cid, v):
comment = get_comment(cid, v=v)
if not comment.is_pinned:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.is_pinned = v.username + " (Mod)"
g.db.add(comment)
if v.id != comment.author_id:
message = f"@{v.username} (Mod) has pinned your [comment]({comment.shortlink})!"
send_repeatable_notification(comment.author_id, message)
g.db.commit()
return {"message": "Comment pinned!"}
@app.post("/unmod_pin/<cid>")
@auth_required
def mod_unpin(cid, v):
comment = get_comment(cid, v=v)
if comment.is_pinned:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.is_pinned = None
g.db.add(comment)
if v.id != comment.author_id:
message = f"@{v.username} (Mod) has unpinned your [comment]({comment.shortlink})!"
send_repeatable_notification(comment.author_id, message)
g.db.commit()
return {"message": "Comment unpinned!"}
@app.post("/save_comment/<cid>") @app.post("/save_comment/<cid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day") @limiter.limit("1/second;30/minute;200/hour;1000/day")
@auth_required @auth_required

View file

@ -160,15 +160,9 @@ def notifications(v):
@app.get("/") @app.get("/")
@app.get("/catalog") @app.get("/catalog")
# @app.get("/h/<sub>")
# @app.get("/s/<sub>")
@limiter.limit("3/second;30/minute;1000/hour;5000/day") @limiter.limit("3/second;30/minute;1000/hour;5000/day")
@auth_desired @auth_desired
def front_all(v, sub=None, subdomain=None): def front_all(v, subdomain=None):
if sub: sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if (request.path.startswith('/h/') or request.path.startswith('/s/')) and not sub: abort(404)
if g.webview and not session.get("session_id"): if g.webview and not session.get("session_id"):
session["session_id"] = secrets.token_hex(49) session["session_id"] = secrets.token_hex(49)
@ -202,7 +196,6 @@ def front_all(v, sub=None, subdomain=None):
filter_words=v.filter_words if v else [], filter_words=v.filter_words if v else [],
gt=gt, gt=gt,
lt=lt, lt=lt,
sub=sub,
site=SITE site=SITE
) )
@ -226,12 +219,12 @@ def front_all(v, sub=None, subdomain=None):
g.db.commit() g.db.commit()
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists} if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page, ccmode=ccmode, sub=sub, home=True) return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page, ccmode=ccmode, home=True)
@cache.memoize(timeout=86400) @cache.memoize(timeout=86400)
def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false", filter_words='', gt=0, lt=0, sub=None, site=None): def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false", filter_words='', gt=0, lt=0, site=None):
posts = g.db.query(Submission) posts = g.db.query(Submission)
@ -245,9 +238,6 @@ def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false"
filter_clause = filter_clause | (Submission.author_id == v.id) filter_clause = filter_clause | (Submission.author_id == v.id)
posts = posts.filter(filter_clause) posts = posts.filter(filter_clause)
if sub: posts = posts.filter_by(sub=sub.name)
elif v: posts = posts.filter(or_(Submission.sub == None, Submission.sub.notin_(v.all_blocks)))
if gt: posts = posts.filter(Submission.created_utc > gt) if gt: posts = posts.filter(Submission.created_utc > gt)
if lt: posts = posts.filter(Submission.created_utc < lt) if lt: posts = posts.filter(Submission.created_utc < lt)
@ -289,9 +279,7 @@ def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false"
if page == 1 and ccmode == "false" and not gt and not lt: if page == 1 and ccmode == "false" and not gt and not lt:
pins = g.db.query(Submission).filter(Submission.stickied != None, Submission.is_banned == False) pins = g.db.query(Submission).filter(Submission.stickied != None, Submission.is_banned == False)
if sub: pins = pins.filter_by(sub=sub.name) if v:
elif v:
pins = pins.filter(or_(Submission.sub == None, Submission.sub.notin_(v.all_blocks)))
if v.admin_level < 2: if v.admin_level < 2:
pins = pins.filter(Submission.author_id.notin_(v.userblocks)) pins = pins.filter(Submission.author_id.notin_(v.userblocks))

View file

@ -91,7 +91,6 @@ def publish(pid, v):
if v.followers: if v.followers:
text = f"@{v.username} has made a new post: [{post.title}]({post.shortlink})" text = f"@{v.username} has made a new post: [{post.title}]({post.shortlink})"
if post.sub: text += f" in <a href='/h/{post.sub}'>/h/{post.sub}"
cid = notif_comment(text, autojanny=True) cid = notif_comment(text, autojanny=True)
for follow in v.followers: for follow in v.followers:
@ -110,23 +109,14 @@ def publish(pid, v):
return redirect(post.permalink) return redirect(post.permalink)
@app.get("/submit") @app.get("/submit")
# @app.get("/h/<sub>/submit")
@auth_required @auth_required
def submit_get(v, sub=None): def submit_get(v):
if sub: sub = g.db.query(Sub.name).filter_by(name=sub.strip().lower()).one_or_none() return render_template("submit.html", v=v)
if request.path.startswith('/h/') and not sub: abort(404)
SUBS = [x[0] for x in g.db.query(Sub.name).order_by(Sub.name).all()]
return render_template("submit.html", SUBS=SUBS, v=v, sub=sub)
@app.get("/post/<pid>") @app.get("/post/<pid>")
@app.get("/post/<pid>/<anything>") @app.get("/post/<pid>/<anything>")
# @app.get("/h/<sub>/post/<pid>")
# @app.get("/h/<sub>/post/<pid>/<anything>")
@auth_desired @auth_desired
def post_id(pid, anything=None, v=None, sub=None): def post_id(pid, anything=None, v=None):
post = get_post(pid, v=v) post = get_post(pid, v=v)
if post.over_18 and not (v and v.over_18) and session.get('over_18', 0) < int(time.time()): if post.over_18 and not (v and v.over_18) and session.get('over_18', 0) < int(time.time()):
@ -141,9 +131,7 @@ def post_id(pid, anything=None, v=None, sub=None):
if v: if v:
votes = g.db.query(CommentVote).filter_by(user_id=v.id).subquery() votes = g.db.query(CommentVote).filter_by(user_id=v.id).subquery()
blocking = v.blocking.subquery() blocking = v.blocking.subquery()
blocked = v.blocked.subquery() blocked = v.blocked.subquery()
comments = g.db.query( comments = g.db.query(
@ -242,7 +230,7 @@ def post_id(pid, anything=None, v=None, sub=None):
else: else:
if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html" if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html"
else: template = "submission.html" else: template = "submission.html"
return render_template(template, v=v, p=post, ids=list(ids), sort=sort, render_replies=True, offset=offset, sub=post.subr) return render_template(template, v=v, p=post, ids=list(ids), sort=sort, render_replies=True, offset=offset)
@app.get("/viewmore/<pid>/<sort>/<offset>") @app.get("/viewmore/<pid>/<sort>/<offset>")
@limiter.limit("1/second;30/minute;200/hour;1000/day") @limiter.limit("1/second;30/minute;200/hour;1000/day")
@ -667,32 +655,18 @@ def api_is_repost():
else: return {'permalink': ''} else: return {'permalink': ''}
@app.post("/submit") @app.post("/submit")
# @app.post("/h/<sub>/submit")
@limiter.limit("1/second;2/minute;10/hour;50/day") @limiter.limit("1/second;2/minute;10/hour;50/day")
@auth_required @auth_required
def submit_post(v, sub=None): def submit_post(v):
def error(error): def error(error):
if request.headers.get("Authorization") or request.headers.get("xhr"): abort(400, error) if request.headers.get("Authorization") or request.headers.get("xhr"): abort(400, error)
return render_template("submit.html", v=v, error=error, title=title, url=url, body=body), 400
SUBS = [x[0] for x in g.db.query(Sub.name).order_by(Sub.name).all()]
return render_template("submit.html", SUBS=SUBS, v=v, error=error, title=title, url=url, body=body), 400
title = guarded_value("title", 1, MAX_TITLE_LENGTH) title = guarded_value("title", 1, MAX_TITLE_LENGTH)
url = guarded_value("url", 0, MAX_URL_LENGTH) url = guarded_value("url", 0, MAX_URL_LENGTH)
body = guarded_value("body", 0, MAX_BODY_LENGTH) body = guarded_value("body", 0, MAX_BODY_LENGTH)
sub = request.values.get("sub")
if sub: sub = sub.replace('/h/','').replace('s/','')
if sub and sub != 'none':
sname = sub.strip().lower()
sub = g.db.query(Sub.name).filter_by(name=sname).one_or_none()
if not sub: return error(f"/h/{sname} not found!")
sub = sub[0]
if v.exiled_from(sub): return error(f"You're exiled from /h/{sub}")
else: sub = None
if v.is_suspended: return error("You can't perform this action while banned.") if v.is_suspended: return error("You can't perform this action while banned.")
title_html = filter_emojis_only(title, graceful=True) title_html = filter_emojis_only(title, graceful=True)
@ -890,7 +864,6 @@ def submit_post(v, sub=None):
embed_url=embed, embed_url=embed,
title=title, title=title,
title_html=title_html, title_html=title_html,
sub=sub,
ghost=False, ghost=False,
filter_state='filtered' if v.admin_level == 0 and app.config['SETTINGS']['FilterNewPosts'] else 'normal' filter_state='filtered' if v.admin_level == 0 and app.config['SETTINGS']['FilterNewPosts'] else 'normal'
) )
@ -945,7 +918,6 @@ def submit_post(v, sub=None):
if (request.values.get('followers') or is_bot) and v.followers: if (request.values.get('followers') or is_bot) and v.followers:
text = f"@{v.username} has made a new post: [{post.title}]({post.shortlink})" text = f"@{v.username} has made a new post: [{post.title}]({post.shortlink})"
if post.sub: text += f" in <a href='/h/{post.sub}'>/h/{post.sub}"
cid = notif_comment(text, autojanny=True) cid = notif_comment(text, autojanny=True)
for follow in v.followers: for follow in v.followers:
@ -968,7 +940,7 @@ def submit_post(v, sub=None):
post.voted = 1 post.voted = 1
if 'megathread' in post.title.lower(): sort = 'new' if 'megathread' in post.title.lower(): sort = 'new'
else: sort = v.defaultsortingcomments else: sort = v.defaultsortingcomments
return render_template('submission.html', v=v, p=post, sort=sort, render_replies=True, offset=0, success=True, sub=post.subr) return render_template('submission.html', v=v, p=post, sort=sort, render_replies=True, offset=0, success=True)
@app.post("/delete_post/<pid>") @app.post("/delete_post/<pid>")

View file

@ -1,387 +0,0 @@
from files.__main__ import app, limiter, mail
from files.helpers.alerts import *
from files.helpers.wrappers import *
from files.classes import *
from .front import frontlist
@app.post("/exile/post/<pid>")
@is_not_permabanned
def exile_post(v, pid):
p = get_post(pid)
sub = p.sub
if not sub: abort(400)
if not v.mods(sub): abort(403)
u = p.author
if u.mods(sub): abort(403)
if u.admin_level < 2 and not u.exiled_from(sub):
exile = Exile(user_id=u.id, sub=sub, exiler_id=v.id)
g.db.add(exile)
send_notification(u.id, f"@{v.username} has exiled you from /h/{sub} for [{p.title}]({p.shortlink})")
g.db.commit()
return {"message": "User exiled successfully!"}
@app.post("/exile/comment/<cid>")
@is_not_permabanned
def exile_comment(v, cid):
c = get_comment(cid)
sub = c.post.sub
if not sub: abort(400)
if not v.mods(sub): abort(403)
u = c.author
if u.mods(sub): abort(403)
if u.admin_level < 2 and not u.exiled_from(sub):
exile = Exile(user_id=u.id, sub=sub, exiler_id=v.id)
g.db.add(exile)
send_notification(u.id, f"@{v.username} has exiled you from /h/{sub} for [{c.permalink}]({c.shortlink})")
g.db.commit()
return {"message": "User exiled successfully!"}
@app.post("/h/<sub>/unexile/<uid>")
@is_not_permabanned
def unexile(v, sub, uid):
u = get_account(uid)
if not v.mods(sub): abort(403)
if u.exiled_from(sub):
exile = g.db.query(Exile).filter_by(user_id=u.id, sub=sub).one_or_none()
g.db.delete(exile)
send_notification(u.id, f"@{v.username} has revoked your exile from /h/{sub}")
g.db.commit()
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"message": "User unexiled successfully!"}
return redirect(f'/h/{sub}/exilees')
@app.post("/h/<sub>/block")
@auth_required
def block_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
if v.mods(sub): abort(409, "You can't block subs you mod!")
existing = g.db.query(SubBlock).filter_by(user_id=v.id, sub=sub).one_or_none()
if not existing:
block = SubBlock(user_id=v.id, sub=sub)
g.db.add(block)
g.db.commit()
cache.delete_memoized(frontlist)
return {"message": "Sub blocked successfully!"}
@app.post("/h/<sub>/unblock")
@auth_required
def unblock_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
block = g.db.query(SubBlock).filter_by(user_id=v.id, sub=sub).one_or_none()
if block:
g.db.delete(block)
g.db.commit()
cache.delete_memoized(frontlist)
return {"message": "Sub unblocked successfully!"}
@app.get("/h/<sub>/mods")
@auth_required
def mods(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
users = g.db.query(User, Mod).join(Mod, Mod.user_id==User.id).filter_by(sub=sub.name).order_by(Mod.created_utc).all()
return render_template("sub/mods.html", v=v, sub=sub, users=users)
@app.get("/h/<sub>/exilees")
@auth_required
def exilees(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
users = g.db.query(User, Exile).join(Exile, Exile.user_id==User.id).filter_by(sub=sub.name).all()
return render_template("sub/exilees.html", v=v, sub=sub, users=users)
@app.get("/h/<sub>/blockers")
@auth_required
def blockers(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
users = g.db.query(User).join(SubBlock, SubBlock.user_id==User.id).filter_by(sub=sub.name).all()
return render_template("sub/blockers.html", v=v, sub=sub, users=users)
@app.post("/h/<sub>/add_mod")
@limiter.limit("1/second;5/day")
@is_not_permabanned
def add_mod(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
if not v.mods(sub): abort(403)
user = request.values.get('user')
if not user: abort(400)
user = get_user(user)
existing = g.db.query(Mod).filter_by(user_id=user.id, sub=sub).one_or_none()
if not existing:
mod = Mod(user_id=user.id, sub=sub)
g.db.add(mod)
if v.id != user.id:
send_repeatable_notification(user.id, f"@{v.username} has added you as a mod to /h/{sub}")
g.db.commit()
return redirect(f'/h/{sub}/mods')
@app.post("/h/<sub>/remove_mod")
@is_not_permabanned
def remove_mod(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
if not v.mods(sub): abort(403)
uid = request.values.get('uid')
if not uid: abort(400)
try: uid = int(uid)
except: abort(400)
user = g.db.query(User).filter_by(id=uid).one_or_none()
if not user: abort(404)
mod = g.db.query(Mod).filter_by(user_id=user.id, sub=sub).one_or_none()
if not mod: abort(400)
if not (v.id == user.id or v.mod_date(sub) and v.mod_date(sub) < mod.created_utc): abort(403)
g.db.delete(mod)
if v.id != user.id:
send_repeatable_notification(user.id, f"@{v.username} has removed you as a mod from /h/{sub}")
g.db.commit()
return redirect(f'/h/{sub}/mods')
@app.get("/create_sub")
@is_not_permabanned
def create_sub(v):
num = v.subs_created + 1
for a in v.alts:
num += a.subs_created
cost = num * 100
return render_template("sub/create_sub.html", v=v, cost=cost)
@app.post("/create_sub")
@is_not_permabanned
def create_sub2(v):
name = request.values.get('name')
if not name: abort(400)
name = name.strip().lower()
num = v.subs_created + 1
for a in v.alts:
num += a.subs_created
cost = num * 100
if not valid_sub_regex.fullmatch(name):
return render_template("sub/create_sub.html", v=v, cost=cost, error="Sub name not allowed."), 400
sub = g.db.query(Sub).filter_by(name=name).one_or_none()
if not sub:
if v.coins < cost:
return render_template("sub/create_sub.html", v=v, cost=cost, error="You don't have enough coins!"), 403
v.coins -= cost
v.subs_created += 1
g.db.add(v)
sub = Sub(name=name)
g.db.add(sub)
g.db.flush()
mod = Mod(user_id=v.id, sub=sub.name)
g.db.add(mod)
g.db.commit()
return redirect(f'/h/{sub.name}')
@app.post("/kick/<pid>")
@is_not_permabanned
def kick(v, pid):
post = get_post(pid)
if not post.sub: abort(403)
if not v.mods(post.sub): abort(403)
post.sub = None
g.db.add(post)
g.db.commit()
cache.delete_memoized(frontlist)
return {"message": "Post kicked successfully!"}
@app.get('/h/<sub>/settings')
@is_not_permabanned
def sub_settings(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub)
@app.post('/h/<sub>/sidebar')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@is_not_permabanned
def post_sub_sidebar(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
sub.sidebar = request.values.get('sidebar', '').strip()[:500]
sub.sidebar_html = sanitize(sub.sidebar)
if len(sub.sidebar_html) > 1000: return "Sidebar is too big!"
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.post('/h/<sub>/css')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@is_not_permabanned
def post_sub_css(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
sub.css = request.values.get('css', '').strip()
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.get("/h/<sub>/css")
def get_sub_css(sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
resp=make_response(sub.css or "")
resp.headers.add("Content-Type", "text/css")
return resp
@app.post("/h/<sub>/banner")
@limiter.limit("1/second;10/day")
@is_not_permabanned
def sub_banner(v, sub):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.")
sub = g.db.query(Sub).filter_by(name=sub.lower().strip()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
file = request.files["banner"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
bannerurl = process_image(name)
if bannerurl:
if sub.bannerurl and '/images/' in sub.bannerurl:
fpath = '/images/' + sub.bannerurl.split('/images/')[1]
if path.isfile(fpath): os.remove(fpath)
sub.bannerurl = bannerurl
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.post("/h/<sub>/sidebar_image")
@limiter.limit("1/second;10/day")
@is_not_permabanned
def sub_sidebar(v, sub):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.")
sub = g.db.query(Sub).filter_by(name=sub.lower().strip()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
file = request.files["sidebar"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
sidebarurl = process_image(name)
if sidebarurl:
if sub.sidebarurl and '/images/' in sub.sidebarurl:
fpath = '/images/' + sub.sidebarurl.split('/images/')[1]
if path.isfile(fpath): os.remove(fpath)
sub.sidebarurl = sidebarurl
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.get("/holes")
@auth_desired
def subs(v):
subs = g.db.query(Sub, func.count(Submission.sub)).outerjoin(Submission, Sub.name == Submission.sub).group_by(Sub.name).order_by(func.count(Submission.sub).desc()).all()
return render_template('sub/subs.html', v=v, subs=subs)

View file

@ -1,24 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>Users blocking /h/{{sub.name}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -1,62 +0,0 @@
{% extends "submit.html" %}
{% block title %}
<title>Create a hole</title>
{% endblock %}
{% block form %}
<pre>
</pre>
<form id="submitform" action="/create_sub" method="post">
<div class="container">
<div class="row justify-content-center mb-4 pb-7">
<div class="col col-md-6 p-3 py-md-0">
<h1 class="d-none d-md-block">Create a hole</h1>
<h2 class="h3 d-md-none">Create a hole</h2>
<div class="body">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="title">Sub Name</label>
<input minlength="3" maxlength="20" pattern='[a-zA-Z0-9_\-]*' class="form-control" id="title-register" aria-describedby="titleHelpRegister" type="text" name="name" required>
<small class="form-text text-muted">3-20 characters, including letters, numbers, _ , and -</small>
</div>
<div class="footer">
<div class="d-flex">
{% if error %}
<p class="mb-0">
<span class="text-danger text-small" style="vertical-align: sub;">{{ error }}</span>
</p>
{% endif %}
<button class="btn btn-primary ml-auto" id="create_button" {% if cost > v.coins %}disabled{% endif %}>Create Hole</button>
</div>
<p class="mt-2 mr-1" style="float: right"><b>Cost</b>: {{cost}} coins</p>
</div>
</div>
</div>
</div>
</form>
{% endblock %}

View file

@ -1,36 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>Users exiled from /h/{{sub.name}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Exiled by</th>
<th></th>
</tr>
</thead>
{% for user, exile in users %}
<tr>
{% set exiler=exile.exiler %}
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td><a style="color:#{{exiler.namecolor}}" href="/@{{exiler.username}}"><img loading="lazy" src="{{exiler.profile_url}}" class="pp20"><span {% if exiler.patron %}class="patron" style="background-color:#{{exiler.namecolor}}"{% endif %}>{{exiler.username}}</span></a></td>
<td>
{% if v.mods(sub.name) %}
<form action="/h/{{sub.name}}/unexile/{{user.id}}" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input class="btn btn-primary" style="margin-top:-5px" autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Unexile">
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -1,45 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>/h/{{sub.name}} Mods</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Mod since</th>
<th></th>
</tr>
</thead>
{% for user, mod in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td>{{mod.created_datetime}}</td>
<td>
{% if v.id == user.id or v.mod_date(sub.name) and v.mod_date(sub.name) < mod.created_utc %}
<form action="/h/{{sub.name}}/remove_mod" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input type="hidden" name="uid" value="{{user.id}}">
<input class="btn btn-primary" style="margin-top:-5px" autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="{% if v.id == user.id %}Resign{% else %}Remove Mod{% endif %}">
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% if v.mods(sub.name) %}
<form action="/h/{{sub.name}}/add_mod" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input class="form-control" style="display:inline;width:250px" autocomplete="off" type="text" name="user" class="form-control" placeholder="Enter username..">
<input class="btn btn-primary" style="margin-top:-5px" autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Add Mod">
</form>
{% endif %}
{% endblock %}

View file

@ -1,139 +0,0 @@
{% extends "default.html" %}
{% block pagetitle %}Edit {{SITE_TITLE}} sidebar{% endblock %}
{% block content %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
<h2 class="h5 mt-5">Sidebar Picture</h2>
<div class="settings-section rounded">
<div class="d-flex">
<div class="title w-lg-25 text-md-center">
<img loading="lazy" alt="sub sidebar picture" src="{{sub.sidebar_url}}" class="profile-pic-75">
</div>
<div class="body w-lg-100 my-auto">
<div class="d-flex">
<div>
<form action="/h/{{sub.name}}/sidebar_image" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Update<input autocomplete="off" type="file" accept="image/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} hidden name="sidebar" onchange="form.submit()">
</label>
</form>
</div>
</div>
<div class="text-small-extra text-muted mt-3">JPG, PNG, GIF files are supported. Max file size is {% if v and v.patron %}8{% else %}4{% endif %} MB.</div>
</div>
</div>
</div>
<h2 class="h5 mt-5">Banner</h2>
<div class="settings-section rounded">
<div class="d-flex">
<div class="title w-lg-75 text-md-center">
<img loading="lazy" alt="/h/{[sub.name]} banner" src="{{sub.banner_url}}" class="banner-pic-135">
</div>
<div class="body w-lg-100 my-auto">
<div class="d-flex">
<div>
<form action="/h/{{sub.name}}/banner" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Update<input autocomplete="off" type="file" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} accept="image/*" hidden name="banner" onchange="form.submit()">
</label>
</form>
</div>
</div>
<div class="text-small-extra text-muted mt-3">JPG, PNG, GIF files are supported. Max file size is {% if v and v.patron %}8{% else %}4{% endif %} MB.</div>
</div>
</div>
</div>
<div class="row my-5 pt-5">
<div class="col col-md-8">
<div class="settings">
<div id="description">
<h2>Edit Sidebar</h2>
<br>
</div>
<div class="body d-lg-flex">
<div class="w-lg-100">
<form id="sidebar" action="/h/{{sub.name}}/sidebar" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea autocomplete="off" maxlength="500" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Enter sidebar here..." rows="10" name="sidebar" form="sidebar">{% if sub.sidebar %}{{sub.sidebar}}{% endif %}</textarea>
<div class="d-flex mt-2">
<input autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Save">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col col-md-8">
<div class="settings">
<div id="description">
<h2>Edit CSS</h2>
<br>
</div>
<div class="body d-lg-flex">
<div class="w-lg-100">
<form id="css" action="/h/{{sub.name}}/css" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea autocomplete="off" maxlength="4000" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Enter css here..." rows="10" name="css" form="css">{% if sub.css %}{{sub.css}}{% endif %}</textarea>
<div class="d-flex mt-2">
<input autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Save">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,32 +0,0 @@
{% extends "default.html" %}
{% block content %}
<script src="{{ 'js/sort_table.js' | asset }}"></script>
<pre>
</pre>
<h5>List of holes</h5>
<pre></pre>
<div class="overflow-x-auto">
<table id="sortable_table" class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th role="button" onclick="sort_table(2)">Posts</th>
<th role="button" onclick="sort_table(3)">Blockers</th>
</tr>
</thead>
{% for sub, count in subs %}
<tr>
<td>{{loop.index}}</td>
<td><a href="/h/{{sub.name}}">{{sub.name}}</a></td>
<td><a href="/h/{{sub.name}}">{{count}}</a></td>
<td><a href="/h/{{sub.name}}/blockers">{{sub.block_num}}</a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -1,24 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>Users subscribed to /h/{{sub.name}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -201,7 +201,6 @@ def test_bulk_update_descendant_count_quick(accounts, submissions, comments):
'embed_url': None, 'embed_url': None,
'title': f'Clever unique post title number {i}', 'title': f'Clever unique post title number {i}',
'title_html': f'Clever unique post title number {i}', 'title_html': f'Clever unique post title number {i}',
'sub': None,
'ghost': False, 'ghost': False,
'filter_state': 'normal' 'filter_state': 'normal'
}) })