add hole mod logs (#380)

remotes/1693176582716663532/tmp_refs/heads/watchparty
Aevann1 2022-09-29 11:39:37 +02:00 committed by GitHub
parent 57ce828af4
commit 09cc43060d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 520 additions and 86 deletions

View File

@ -6031,6 +6031,9 @@ g {
.fa-hat-cowboy:before{content:"\f8c0"}
.fa-cloud-rainbow:before{content:"\f73e"}
.fa-telegram:before{content:"\f2c6"}
.fa-css3-alt:before{content:"\f38b"}
.fa-landscape:before{content:"\e1b5"}
.fa-user-ninja:before{content:"\f504"}
.pronouns {
font-size: 9px;
@ -6283,4 +6286,4 @@ div.markdown {
.bug {
pointer-events: none !important;
}
}

File diff suppressed because one or more lines are too long

View File

@ -26,3 +26,4 @@ from .hats import *
from .marsey import *
from .transactions import *
from .streamers import *
from .sub_logs import *

View File

@ -410,12 +410,12 @@ ACTIONTYPES = {
"color": 'bg-success'
},
'unpin_comment': {
"str": 'un-pinned a {self.target_link}',
"str": 'unpinned a {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},
'unpin_post': {
"str": 'un-pinned post {self.target_link}',
"str": 'unpinned post {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},

View File

@ -0,0 +1,200 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time
from files.helpers.lazy import lazy
from files.helpers.const import *
from files.helpers.regex import censor_slurs
class SubAction(Base):
__tablename__ = "subactions"
id = Column(Integer, primary_key=True)
sub = Column(String, ForeignKey("subs.name"))
user_id = Column(Integer, ForeignKey("users.id"))
kind = Column(String)
target_user_id = Column(Integer, ForeignKey("users.id"))
target_submission_id = Column(Integer, ForeignKey("submissions.id"))
target_comment_id = Column(Integer, ForeignKey("comments.id"))
_note=Column(String)
created_utc = Column(Integer)
user = relationship("User", primaryjoin="User.id==SubAction.user_id")
target_user = relationship("User", primaryjoin="User.id==SubAction.target_user_id")
target_post = relationship("Submission")
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"<SubAction(id={self.id})>"
@property
@lazy
def age_string(self):
age = int(time.time()) - self.created_utc
if age < 60:
return "just now"
elif age < 3600:
minutes = int(age / 60)
return f"{minutes}m ago"
elif age < 86400:
hours = int(age / 3600)
return f"{hours}hr ago"
elif age < 2678400:
days = int(age / 86400)
return f"{days}d ago"
now = time.gmtime()
ctd = time.gmtime(self.created_utc)
months = now.tm_mon - ctd.tm_mon + 12 * (now.tm_year - ctd.tm_year)
if now.tm_mday < ctd.tm_mday:
months -= 1
if months < 12:
return f"{months}mo ago"
else:
years = int(months / 12)
return f"{years}yr ago"
@property
def note(self):
if self.kind=="ban_user":
if self.target_post: return f'for <a href="{self.target_post.permalink}">post</a>'
else: return self._note
else:
return self._note or ""
@note.setter
def note(self, x):
self._note=x
@property
@lazy
def string(self):
output = ACTIONTYPES[self.kind]["str"].format(self=self, cc=CC_TITLE)
if self.note: output += f" <i>({self.note})</i>"
return output
@property
@lazy
def target_link(self):
if self.target_user: return f'<a href="{self.target_user.url}">{self.target_user.username}</a>'
elif self.target_post:
if self.target_post.club: return f'<a href="{self.target_post.permalink}">{CC} ONLY</a>'
return censor_slurs(f'<a href="{self.target_post.permalink}">{self.target_post.title_html}</a>', None)
@property
@lazy
def icon(self):
return ACTIONTYPES[self.kind]['icon']
@property
@lazy
def color(self):
return ACTIONTYPES[self.kind]['color']
@property
@lazy
def permalink(self):
return f"{SITE_FULL}/log/{self.id}"
ACTIONTYPES = {
'exile_user': {
"str": 'exiled user {self.target_link}',
"icon": 'fa-user-slash',
"color": 'bg-danger'
},
'unexile_user': {
"str": 'unexile user {self.target_link}',
"icon": 'fa-user',
"color": 'bg-success'
},
'make_mod': {
"str": 'made {self.target_link} mod',
"icon": 'fa-user-crown',
"color": 'bg-success'
},
'remove_mod': {
"str": 'removed {self.target_link} as mod',
"icon": 'fa-user-crown',
"color": 'bg-danger'
},
'kick_post': {
"str": 'kicked post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-danger'
},
'move_chudrama': {
"str": 'moved post {self.target_link} to /h/chudrama',
"icon": 'fa-feather-alt',
"color": 'bg-danger'
},
'flair_post': {
"str": 'set a flair on {self.target_link}',
"icon": 'fa-tag',
"color": 'bg-primary'
},
'change_sidebar': {
"str": 'changed the sidebar',
"icon": 'fa-columns',
"color": 'bg-primary'
},
'change_css': {
"str": 'changed the css',
"icon": 'fa-css3-alt',
"color": 'bg-primary'
},
'change_banner': {
"str": 'changed the banner',
"icon": 'fa-landscape',
"color": 'bg-primary'
},
'change_sidebar_image': {
"str": 'changed the sidebar image',
"icon": 'fa-image',
"color": 'bg-primary'
},
'change_marsey': {
"str": 'changed the hole marsey',
"icon": 'fa-cat',
"color": 'bg-primary'
},
'pin_post': {
"str": 'pinned post {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-success'
},
'unpin_post': {
"str": 'unpinned post {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},
'pin_comment': {
"str": 'pinned comment {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-success'
},
'unpin_comment': {
"str": 'unpinned comment {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},
'enable_stealth': {
"str": 'enabled stealth mode',
"icon": 'fa-user-ninja',
"color": 'bg-primary'
},
'disable_stealth': {
"str": 'disabled stealth mode',
"icon": 'fa-user-ninja',
"color": 'bg-muted'
}
}

View File

@ -281,6 +281,7 @@ class User(Base):
@lazy
def mods(self, sub):
if self.is_suspended or self.shadowbanned: return False
return self.admin_level > 2 or bool(g.db.query(Mod.user_id).filter_by(user_id=self.id, sub=sub).one_or_none())
@lazy

View File

@ -817,45 +817,6 @@ def unpin_comment(cid, v):
return {"message": "Comment unpinned!"}
@app.post("/mod_pin/<cid>")
@auth_required
def mod_pin(cid, v):
if not FEATURES['PINS']:
abort(403)
comment = get_comment(cid, v=v)
if not comment.stickied:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.stickied = 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)
return {"message": "Comment pinned!"}
@app.post("/mod_unpin/<cid>")
@auth_required
def mod_unpin(cid, v):
comment = get_comment(cid, v=v)
if comment.stickied:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.stickied = 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)
return {"message": "Comment unpinned!"}
@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", key_func=lambda:f'{SITE}-{session.get("lo_user")}')

View File

@ -22,7 +22,7 @@ def front_all(v, sub=None, subdomain=None):
if sub:
sub = sub.strip().lower()
if sub == 'chudrama' and not (v and v.can_see_chudrama): abort(403)
sub = g.db.query(Sub).filter_by(name=sub).one_or_none()
sub = g.db.get(Sub, sub)
if (request.path.startswith('/h/') or request.path.startswith('/s/')) and not sub: abort(404)

View File

@ -87,7 +87,7 @@ def publish(pid, v):
@app.get("/h/<sub>/submit")
@auth_required
def submit_get(v, sub=None):
if sub: sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if sub: sub = g.db.get(Sub, sub.strip().lower())
if request.path.startswith('/h/') and not sub: abort(404)

View File

@ -40,11 +40,19 @@ def flag_post(pid, v):
_note=f'"{post.flair}"'
)
g.db.add(ma)
else:
ma = SubAction(
kind="flair_post",
user_id=v.id,
target_submission_id=post.id
)
g.db.add(ma)
elif reason.startswith('/h/') and (v.admin_level >= 2 or v.id == post.author_id or (reason == '/h/chudrama' and v.mods(post.sub))):
sub_from = post.sub
sub_to = reason[3:].strip().lower()
sub_to = g.db.query(Sub).filter_by(name=sub_to).one_or_none()
sub_to = g.db.get(Sub, sub_to)
sub_to = sub_to.name if sub_to else None
if sub_from == sub_to: {"error": f"Post is already in /h/{sub_to}"}, 400
@ -61,18 +69,26 @@ def flag_post(pid, v):
post.sub = sub_to
g.db.add(post)
if v.admin_level and v.id != post.author_id:
sub_from_str = 'main feed' if sub_from is None else \
f'<a href="/h/{sub_from}">/h/{sub_from}</a>'
sub_to_str = 'main feed' if sub_to is None else \
f'<a href="/h/{sub_to}">/h/{sub_to}</a>'
ma = ModAction(
kind='move_hole',
user_id=v.id,
target_submission_id=post.id,
_note=f'{sub_from_str}{sub_to_str}',
)
g.db.add(ma)
if v.id != post.author_id:
if v.admin_level:
sub_from_str = 'main feed' if sub_from is None else \
f'<a href="/h/{sub_from}">/h/{sub_from}</a>'
sub_to_str = 'main feed' if sub_to is None else \
f'<a href="/h/{sub_to}">/h/{sub_to}</a>'
ma = ModAction(
kind='move_hole',
user_id=v.id,
target_submission_id=post.id,
_note=f'{sub_from_str}{sub_to_str}',
)
g.db.add(ma)
else:
ma = SubAction(
kind='move_chudrama',
user_id=v.id,
target_submission_id=post.id
)
g.db.add(ma)
if v.id != post.author_id:
if v.admin_level >= 3: position = 'Admin'

View File

@ -30,6 +30,13 @@ def exile_post(v, pid):
send_notification(u.id, f"@{v.username} has exiled you from /h/{sub} for [{p.title}]({p.shortlink})")
ma = SubAction(
kind='exile_user',
user_id=v.id,
target_user_id=u.id,
_note=f'for <a href="{p.permalink}">{p.title_html}</a>'
)
g.db.add(ma)
return {"message": f"@{u.username} has been exiled from /h/{sub} successfully!"}
@ -57,7 +64,14 @@ def exile_comment(v, cid):
send_notification(u.id, f"@{v.username} has exiled you from /h/{sub} for [{c.permalink}]({c.shortlink})")
ma = SubAction(
kind='exile_user',
user_id=v.id,
target_user_id=u.id,
_note=f'for <a href="/comment/{c.id}?context=8#context">comment</a>'
)
g.db.add(ma)
return {"message": f"@{u.username} has been exiled from /h/{sub} successfully!"}
@ -74,7 +88,12 @@ def unexile(v, sub, uid):
send_notification(u.id, f"@{v.username} has revoked your exile from /h/{sub}")
ma = SubAction(
kind='unexile_user',
user_id=v.id,
target_user_id=u.id
)
g.db.add(ma)
if request.headers.get("Authorization") or request.headers.get("xhr"):
return {"message": f"@{u.username} has been unexiled from /h/{sub} successfully!"}
@ -84,14 +103,10 @@ def unexile(v, sub, uid):
@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()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
sub = sub.name
@ -108,7 +123,7 @@ def block_sub(v, sub):
@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()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
sub = sub.name
@ -124,7 +139,7 @@ def unblock_sub(v, sub):
@app.post("/h/<sub>/subscribe")
@auth_required
def subscribe_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
sub = sub.name
@ -140,7 +155,7 @@ def subscribe_sub(v, sub):
@app.post("/h/<sub>/unsubscribe")
@auth_required
def unsubscribe_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
sub = sub.name
@ -155,7 +170,7 @@ def unsubscribe_sub(v, sub):
@app.post("/h/<sub>/follow")
@auth_required
def follow_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
existing = g.db.query(SubSubscription).filter_by(user_id=v.id, sub=sub.name).one_or_none()
@ -170,7 +185,7 @@ def follow_sub(v, sub):
@app.post("/h/<sub>/unfollow")
@auth_required
def unfollow_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
subscription = g.db.query(SubSubscription).filter_by(user_id=v.id, sub=sub.name).one_or_none()
@ -184,7 +199,7 @@ def unfollow_sub(v, sub):
@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()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
users = g.db.query(User, Mod).join(Mod).filter_by(sub=sub.name).order_by(Mod.created_utc).all()
@ -195,7 +210,7 @@ def mods(v, sub):
@app.get("/h/<sub>/exilees")
@auth_required
def sub_exilees(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
users = g.db.query(User, Exile).join(Exile, Exile.user_id==User.id) \
@ -208,7 +223,7 @@ def sub_exilees(v, sub):
@app.get("/h/<sub>/blockers")
@auth_required
def sub_blockers(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
users = g.db.query(User).join(SubBlock) \
@ -222,7 +237,7 @@ def sub_blockers(v, sub):
@app.get("/h/<sub>/followers")
@auth_required
def sub_followers(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
users = g.db.query(User).join(SubSubscription) \
@ -240,7 +255,7 @@ def sub_followers(v, sub):
def add_mod(v, sub):
if SITE_NAME == 'WPD': abort(403)
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
sub = sub.name
@ -264,14 +279,20 @@ def add_mod(v, sub):
if v.id != user.id:
send_repeatable_notification(user.id, f"@{v.username} has added you as a mod to /h/{sub}")
ma = SubAction(
kind='make_mod',
user_id=v.id,
target_user_id=user.id
)
g.db.add(ma)
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()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
sub = sub.name
@ -298,7 +319,13 @@ def remove_mod(v, sub):
if v.id != user.id:
send_repeatable_notification(user.id, f"@{v.username} has removed you as a mod from /h/{sub}")
ma = SubAction(
kind='remove_mod',
user_id=v.id,
target_user_id=user.id
)
g.db.add(ma)
return redirect(f'/h/{sub}/mods')
@app.get("/create_hole")
@ -322,7 +349,7 @@ def create_sub2(v):
if not valid_sub_regex.fullmatch(name):
return render_template("sub/create_hole.html", v=v, cost=HOLE_COST, error=f"{HOLE_NAME.capitalize()} name not allowed."), 400
sub = g.db.query(Sub).filter_by(name=name).one_or_none()
sub = g.db.get(Sub, name)
if not sub:
if v.coins < HOLE_COST:
return render_template("sub/create_hole.html", v=v, cost=HOLE_COST, error="You don't have enough coins!"), 403
@ -366,6 +393,13 @@ def kick(v, pid):
_note=f'{old_str} → main feed',
)
g.db.add(ma)
else:
ma = SubAction(
kind='kick_post',
user_id=v.id,
target_submission_id=post.id
)
g.db.add(ma)
if v.id != post.author_id:
if v.admin_level >= 3: position = 'Admin'
@ -382,7 +416,7 @@ def kick(v, pid):
@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()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
@ -395,7 +429,7 @@ def sub_settings(v, sub):
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}')
@is_not_permabanned
def post_sub_sidebar(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
@ -406,6 +440,11 @@ def post_sub_sidebar(v, sub):
g.db.add(sub)
ma = SubAction(
kind='change_sidebar',
user_id=v.id
)
g.db.add(ma)
return redirect(f'/h/{sub}/settings')
@ -415,7 +454,7 @@ def post_sub_sidebar(v, sub):
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}')
@is_not_permabanned
def post_sub_css(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
css = request.values.get('css', '').strip()
if not sub: abort(404)
@ -432,6 +471,12 @@ def post_sub_css(v, sub):
sub.css = css
g.db.add(sub)
ma = SubAction(
kind='change_css',
user_id=v.id
)
g.db.add(ma)
return redirect(f'/h/{sub}/settings')
@ -451,7 +496,7 @@ def get_sub_css(sub):
def sub_banner(v, sub):
if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403
sub = g.db.query(Sub).filter_by(name=sub.lower().strip()).one_or_none()
sub = g.db.get(Sub, sub.lower().strip())
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
@ -469,6 +514,12 @@ def sub_banner(v, sub):
sub.bannerurl = bannerurl
g.db.add(sub)
ma = SubAction(
kind='change_banner',
user_id=v.id
)
g.db.add(ma)
return redirect(f'/h/{sub}/settings')
@app.post("/h/<sub>/sidebar_image")
@ -478,7 +529,7 @@ def sub_banner(v, sub):
def sub_sidebar(v, sub):
if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403
sub = g.db.query(Sub).filter_by(name=sub.lower().strip()).one_or_none()
sub = g.db.get(Sub, sub.lower().strip())
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
@ -495,6 +546,12 @@ def sub_sidebar(v, sub):
sub.sidebarurl = sidebarurl
g.db.add(sub)
ma = SubAction(
kind='change_sidebar_image',
user_id=v.id
)
g.db.add(ma)
return redirect(f'/h/{sub}/settings')
@app.post("/h/<sub>/marsey_image")
@ -504,7 +561,7 @@ def sub_sidebar(v, sub):
def sub_marsey(v, sub):
if request.headers.get("cf-ipcountry") == "T1": return {"error":"Image uploads are not allowed through TOR."}, 403
sub = g.db.query(Sub).filter_by(name=sub.lower().strip()).one_or_none()
sub = g.db.get(Sub, sub.lower().strip())
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
@ -521,6 +578,12 @@ def sub_marsey(v, sub):
sub.marseyurl = marseyurl
g.db.add(sub)
ma = SubAction(
kind='change_marsey',
user_id=v.id
)
g.db.add(ma)
return redirect(f'/h/{sub}/settings')
@app.get("/holes")
@ -545,6 +608,13 @@ def hole_pin(v, pid):
message = f"@{v.username} (Mod) has pinned [{p.title}]({p.shortlink}) in /h/{p.sub}"
send_repeatable_notification(p.author_id, message)
ma = SubAction(
kind='pin_post',
user_id=v.id,
target_submission_id=p.id
)
g.db.add(ma)
return {"message": f"Post pinned to /h/{p.sub} successfully!"}
@app.post("/hole_unpin/<pid>")
@ -563,13 +633,20 @@ def hole_unpin(v, pid):
message = f"@{v.username} (Mod) has unpinned [{p.title}]({p.shortlink}) in /h/{p.sub}"
send_repeatable_notification(p.author_id, message)
ma = SubAction(
kind='unpin_post',
user_id=v.id,
target_submission_id=p.id
)
g.db.add(ma)
return {"message": f"Post unpinned from /h/{p.sub} successfully!"}
@app.post('/h/<sub>/stealth')
@is_not_permabanned
def sub_stealth(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
if sub.name == 'braincels': abort(403)
@ -581,6 +658,130 @@ def sub_stealth(v, sub):
cache.delete_memoized(frontlist)
if sub.stealth:
ma = SubAction(
kind='enable_stealth',
user_id=v.id
)
g.db.add(ma)
return {"message": f"Stealth mode has been enabled for /h/{sub} successfully!"}
else:
ma = SubAction(
kind='disable_stealth',
user_id=v.id
)
g.db.add(ma)
return {"message": f"Stealth mode has been disabled for /h/{sub} successfully!"}
@app.post("/mod_pin/<cid>")
@is_not_permabanned
def mod_pin(cid, v):
if not FEATURES['PINS']:
abort(403)
comment = get_comment(cid, v=v)
if not comment.stickied:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.stickied = v.username + " (Mod)"
g.db.add(comment)
ma = SubAction(
kind="pin_comment",
user_id=v.id,
target_comment_id=comment.id
)
g.db.add(ma)
if v.id != comment.author_id:
message = f"@{v.username} (Mod) has pinned your [comment]({comment.shortlink})!"
send_repeatable_notification(comment.author_id, message)
return {"message": "Comment pinned!"}
@app.post("/mod_unpin/<cid>")
@is_not_permabanned
def mod_unpin(cid, v):
comment = get_comment(cid, v=v)
if comment.stickied:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.stickied = None
g.db.add(comment)
ma = SubAction(
kind="unpin_comment",
user_id=v.id,
target_comment_id=comment.id
)
g.db.add(ma)
if v.id != comment.author_id:
message = f"@{v.username} (Mod) has unpinned your [comment]({comment.shortlink})!"
send_repeatable_notification(comment.author_id, message)
return {"message": "Comment unpinned!"}
@app.get("/h/<sub>/log")
@app.get("/h/<sub>/modlog")
@auth_required
def hole_log(v, sub):
sub = g.db.get(Sub, sub.strip().lower())
if not sub: abort(404)
sub = sub.name
try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1
mod = request.values.get("mod")
if mod: mod_id = get_id(mod)
else: mod_id = 0
kind = request.values.get("kind")
types = ACTIONTYPES
if kind and kind not in types:
kind = None
actions = []
else:
actions = g.db.query(SubAction).filter_by(sub=sub)
if mod_id:
actions = actions.filter_by(user_id=mod_id)
kinds = set([x.kind for x in actions])
types2 = {}
for k,val in types.items():
if k in kinds: types2[k] = val
types = types2
if kind: actions = actions.filter_by(kind=kind)
actions = actions.order_by(SubAction.id.desc()).offset(25*(page-1)).limit(26).all()
next_exists=len(actions)>25
actions=actions[:25]
mods = [x[0] for x in g.db.query(Mod.user_id).filter_by(sub=sub).all()]
mods = [x[0] for x in g.db.query(User.username).filter(User.id.in_(mods)).order_by(User.username).all()]
return render_template("log.html", v=v, mods=mods, types=types, mod=mod, type=kind, actions=actions, next_exists=next_exists, page=page)
@app.get("/h/<sub>/log/<id>")
@auth_required
def hole_log_item(id, v, sub):
try: id = int(id)
except: abort(404)
action=g.db.get(SubAction, id)
if not action: abort(404)
mods = [x[0] for x in g.db.query(Mod.user_id).filter_by(sub=sub).all()]
mods = [x[0] for x in g.db.query(User.username).filter(User.id.in_(mods)).order_by(User.username).all()]
types = ACTIONTYPES
return render_template("log.html", v=v, actions=[action], next_exists=False, page=1, action=action, mods=mods, types=types)

View File

@ -127,7 +127,7 @@
{% else %}
<div class="px-3">There's nothing here right now.</div>
<div class="p-3">There's nothing here right now.</div>
{% endfor %}
</div>

View File

@ -11,6 +11,7 @@
{% if sub.sidebar_html %}
<div class="mb-4">{{sub.sidebar_html|safe}}</div>
{% endif %}
<a class="btn btn-primary btn-block" href="/h/{{sub}}/log">{{HOLE_NAME|upper}} LOG</a>
{% if v and v.mods(sub.name) %}
<a class="btn btn-primary btn-block" href="/h/{{sub}}/settings">{{HOLE_NAME|upper}} SETTINGS</a>
{% endif %}

View File

@ -37,6 +37,7 @@
{% if v.can_create_hole -%}
<a class="btn btn-primary btn-block mb-3" href="/create_hole">CREATE {{HOLE_NAME|upper}}</a>
{%- endif %}
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub}}/log">{{HOLE_NAME|upper}} LOG</a>
{% if v.mods(sub.name) %}
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub}}/settings">{{HOLE_NAME|upper}} SETTINGS</a>
{% endif %}

View File

@ -0,0 +1,43 @@
CREATE TABLE public.subactions (
id integer PRIMARY KEY,
sub character varying(25) NOT NULL,
user_id integer,
target_user_id integer,
target_submission_id integer,
target_comment_id integer,
created_utc integer NOT NULL,
kind character varying(32) DEFAULT NULL::character varying,
_note character varying(500) DEFAULT NULL::character varying
);
CREATE SEQUENCE public.subactions_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.subactions_id_seq OWNED BY public.subactions.id;
ALTER TABLE ONLY public.subactions ALTER COLUMN id SET DEFAULT nextval('public.subactions_id_seq'::regclass);
CREATE INDEX fki_subactions_user_fkey ON public.subactions USING btree (target_user_id);
CREATE INDEX modaction_action_idx ON public.subactions USING btree (kind);
CREATE INDEX modaction_pid_idx ON public.subactions USING btree (target_submission_id);
ALTER TABLE ONLY public.subactions
ADD CONSTRAINT subactions_submission_fkey FOREIGN KEY (target_submission_id) REFERENCES public.submissions(id);
CREATE INDEX modaction_cid_idx ON public.subactions USING btree (target_comment_id);
ALTER TABLE ONLY public.subactions
ADD CONSTRAINT subactions_comment_fkey FOREIGN KEY (target_comment_id) REFERENCES public.comments(id);
ALTER TABLE ONLY public.subactions
ADD CONSTRAINT subactions_user_fkey FOREIGN KEY (target_user_id) REFERENCES public.users(id);
ALTER TABLE ONLY public.subactions
ADD CONSTRAINT subactions_sub_fkey FOREIGN KEY (sub) REFERENCES public.subs(name);