2022-11-15 09:19:08 +00:00
import os
2022-05-04 23:09:46 +00:00
import time
2022-11-26 06:11:00 +00:00
import html
2022-11-15 09:19:08 +00:00
from io import BytesIO
from os import path
from shutil import copyfile
from sys import stdout
2023-06-26 15:28:07 +00:00
from urllib . parse import ParseResult , urlparse , urlunparse , unquote
2022-11-15 09:19:08 +00:00
2022-05-04 23:09:46 +00:00
import gevent
import requests
2022-11-15 09:19:08 +00:00
from PIL import Image
from files . __main__ import app , cache , limiter
from files . classes import *
from files . helpers . actions import *
2022-05-04 23:09:46 +00:00
from files . helpers . alerts import *
2022-12-11 23:44:34 +00:00
from files . helpers . config . const import *
2022-11-15 09:19:08 +00:00
from files . helpers . get import *
2022-06-24 14:30:59 +00:00
from files . helpers . regex import *
2022-11-15 09:19:08 +00:00
from files . helpers . sanitize import *
from files . helpers . settings import get_setting
2022-05-04 23:09:46 +00:00
from files . helpers . slots import *
2022-07-09 10:32:49 +00:00
from files . helpers . sorting_and_time import *
2022-11-15 09:19:08 +00:00
from files . routes . routehelpers import execute_shadowban_viewers_and_voters
from files . routes . wrappers import *
2022-07-13 21:03:11 +00:00
from . front import frontlist
2022-11-15 09:19:08 +00:00
from . users import userpagelisting
2022-05-04 23:09:46 +00:00
2023-06-27 20:03:14 +00:00
from files . __main__ import app , limiter , redis_instance
2022-05-04 23:09:46 +00:00
2022-12-29 10:39:10 +00:00
@app.post ( " /publish/<int:pid> " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def publish ( pid , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid )
if not p . private : return { " message " : " Post published! " }
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
if p . author_id != v . id : abort ( 403 )
p . private = False
p . created_utc = int ( time . time ( ) )
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2023-01-01 11:36:20 +00:00
2023-06-24 20:31:12 +00:00
notify_users = NOTIFY_USERS ( f ' { p . title } { p . body } ' , v , ghost = p . ghost , log_cost = p )
2022-05-04 23:09:46 +00:00
2023-03-01 19:28:19 +00:00
if notify_users :
cid , text = notif_comment2 ( p )
2023-03-02 00:32:51 +00:00
if notify_users == ' everyone ' :
alert_everyone ( cid )
else :
for x in notify_users :
add_notif ( cid , x , text , pushnotif_url = p . permalink )
2022-05-04 23:09:46 +00:00
cache . delete_memoized ( frontlist )
2022-11-15 09:19:08 +00:00
cache . delete_memoized ( userpagelisting )
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
execute_snappy ( p , v )
2022-07-08 11:44:17 +00:00
2022-07-10 14:21:16 +00:00
if is_site_url ( request . referrer ) :
2022-06-27 02:34:15 +00:00
return redirect ( request . referrer )
2023-02-28 22:37:12 +00:00
return redirect ( p . permalink )
2022-05-04 23:09:46 +00:00
@app.get ( " /submit " )
@app.get ( " /h/<sub>/submit " )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
2022-11-26 21:00:03 +00:00
def submit_get ( v : User , sub = None ) :
2022-10-05 10:30:44 +00:00
sub = get_sub_by_name ( sub , graceful = True )
2022-05-04 23:09:46 +00:00
if request . path . startswith ( ' /h/ ' ) and not sub : abort ( 404 )
2023-03-16 06:27:58 +00:00
SUBS = [ x [ 0 ] for x in g . db . query ( Sub . name ) . order_by ( Sub . name ) . all ( ) ]
2022-05-04 23:09:46 +00:00
return render_template ( " submit.html " , SUBS = SUBS , v = v , sub = sub )
2022-12-29 10:39:10 +00:00
@app.get ( " /post/<int:pid> " )
@app.get ( " /post/<int:pid>/<anything> " )
@app.get ( " /h/<sub>/post/<int:pid> " )
@app.get ( " /h/<sub>/post/<int:pid>/<anything> " )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
2022-08-05 20:40:48 +00:00
@auth_desired_with_logingate
2023-07-08 13:32:14 +00:00
def post_id ( pid , v , anything = None , sub = None ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid , v = v )
if not User . can_see ( v , p ) : abort ( 403 )
2022-08-19 22:36:28 +00:00
2023-05-16 03:45:54 +00:00
if p . over_18 and not ( v and v . over_18 ) and session . get ( ' over_18_cookies ' , 0 ) < int ( time . time ( ) ) :
2022-12-26 02:37:30 +00:00
if g . is_api_or_xhr : abort ( 451 , " Must be 18+ to view " )
2022-05-04 23:09:46 +00:00
return render_template ( " errors/nsfw.html " , v = v )
2023-05-12 19:40:34 +00:00
p . views + = 1
g . db . add ( p )
2023-02-28 22:37:12 +00:00
if p . new : defaultsortingcomments = ' new '
2022-05-04 23:09:46 +00:00
elif v : defaultsortingcomments = v . defaultsortingcomments
2022-10-11 16:41:09 +00:00
else : defaultsortingcomments = " hot "
2022-05-04 23:09:46 +00:00
sort = request . values . get ( " sort " , defaultsortingcomments )
2023-07-17 17:32:41 +00:00
if sort == ' saves ' :
sort = defaultsortingcomments
2023-06-27 20:08:52 +00:00
if not v :
result = cache . get ( f ' post_ { p . id } _ { sort } ' )
2023-07-07 20:40:16 +00:00
if result :
calc_users ( )
return result
2023-06-27 20:08:52 +00:00
2022-05-04 23:09:46 +00:00
if v :
2023-02-28 22:37:12 +00:00
execute_shadowban_viewers_and_voters ( v , p )
2023-02-10 13:48:31 +00:00
# shadowban check is done in sort_objects
# output is needed: see comments.py
2023-06-23 13:46:42 +00:00
comments , output = get_comments_v_properties ( v , None , Comment . parent_post == p . id , Comment . level < 10 )
2023-06-08 01:22:17 +00:00
if sort == " hot " :
pinned = [ c [ 0 ] for c in comments . filter ( Comment . stickied != None ) . order_by ( Comment . created_utc . desc ( ) ) . all ( ) ]
comments = comments . filter ( Comment . stickied == None )
comments = comments . filter ( Comment . level == 1 )
2023-02-27 15:38:12 +00:00
comments = sort_objects ( sort , comments , Comment )
2023-02-10 13:48:31 +00:00
comments = [ c [ 0 ] for c in comments . all ( ) ]
2022-05-04 23:09:46 +00:00
else :
2023-06-23 13:46:42 +00:00
comments = g . db . query ( Comment ) . filter ( Comment . parent_post == p . id )
2022-05-04 23:09:46 +00:00
2023-06-08 01:22:17 +00:00
if sort == " hot " :
pinned = comments . filter ( Comment . stickied != None ) . order_by ( Comment . created_utc . desc ( ) ) . all ( )
comments = comments . filter ( Comment . stickied == None )
2022-05-04 23:09:46 +00:00
2023-06-08 01:22:17 +00:00
comments = comments . filter ( Comment . level == 1 )
2023-02-27 15:38:12 +00:00
comments = sort_objects ( sort , comments , Comment )
2022-09-04 23:21:48 +00:00
comments = comments . all ( )
2022-05-04 23:09:46 +00:00
2022-07-04 03:37:48 +00:00
offset = 0
ids = set ( )
2022-10-25 18:20:43 +00:00
threshold = 100
2022-06-27 19:08:05 +00:00
2023-02-28 22:37:12 +00:00
if p . comment_count > threshold + 25 and not ( v and v . client ) :
2022-05-04 23:09:46 +00:00
comments2 = [ ]
count = 0
2023-02-28 22:37:12 +00:00
if p . created_utc > 1638672040 : # TODO: migrate old comments to use top_comment_id
2022-07-04 03:37:48 +00:00
for comment in comments :
comments2 . append ( comment )
ids . add ( comment . id )
2023-06-23 13:46:42 +00:00
count + = g . db . query ( Comment ) . filter_by ( parent_post = p . id , top_comment_id = comment . id ) . count ( ) + 1
2022-07-04 03:37:48 +00:00
if count > threshold : break
else :
for comment in comments :
comments2 . append ( comment )
ids . add ( comment . id )
2023-06-23 13:46:42 +00:00
count + = g . db . query ( Comment ) . filter_by ( parent_post = p . id , parent_comment_id = comment . id ) . count ( ) + 1
2022-07-04 03:37:48 +00:00
if count > 20 : break
if len ( comments ) == len ( comments2 ) : offset = 0
else : offset = 1
2022-05-04 23:09:46 +00:00
comments = comments2
2023-06-08 01:22:17 +00:00
p . replies = comments
if sort == " hot " :
pinned2 = { }
for pin in pinned :
if pin . stickied_utc and int ( time . time ( ) ) > pin . stickied_utc :
pin . stickied = None
pin . stickied_utc = None
g . db . add ( pin )
elif pin . level > 1 :
pinned2 [ pin . top_comment ] = ' '
if pin . top_comment in comments :
comments . remove ( pin . top_comment )
else :
pinned2 [ pin ] = ' '
p . replies = list ( pinned2 . keys ( ) ) + p . replies
2022-05-04 23:09:46 +00:00
2022-10-15 09:11:36 +00:00
if v and v . client :
2023-06-08 00:49:37 +00:00
return p . json
2022-08-08 22:21:59 +00:00
2023-06-07 23:26:32 +00:00
template = " post.html "
2023-02-28 22:37:12 +00:00
if ( p . is_banned or p . author . shadowbanned ) \
and not ( v and ( v . admin_level > = PERMS [ ' POST_COMMENT_MODERATION ' ] or p . author_id == v . id ) ) :
2023-06-07 23:26:32 +00:00
template = " post_banned.html "
2022-08-08 22:21:59 +00:00
2023-05-04 19:55:08 +00:00
result = render_template ( template , v = v , p = p , ids = list ( ids ) ,
2023-02-28 22:37:12 +00:00
sort = sort , render_replies = True , offset = offset , sub = p . subr ,
2022-11-30 18:26:07 +00:00
fart = get_setting ( ' fart_mode ' ) )
2022-07-04 03:37:48 +00:00
2023-06-27 20:08:52 +00:00
if not v :
2023-07-07 19:08:23 +00:00
cache . set ( f ' post_ { p . id } _ { sort } ' , result , timeout = 3600 )
2023-05-04 19:55:08 +00:00
return result
2022-12-29 14:20:27 +00:00
@app.get ( " /view_more/<int:pid>/<sort>/<offset> " )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
2022-08-05 20:40:48 +00:00
@auth_desired_with_logingate
2022-12-29 14:20:27 +00:00
def view_more ( v , pid , sort , offset ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid , v = v )
2022-10-16 09:51:42 +00:00
try :
offset = int ( offset )
except : abort ( 400 )
2022-07-04 03:37:48 +00:00
try : ids = set ( int ( x ) for x in request . values . get ( " ids " ) . split ( ' , ' ) )
except : abort ( 400 )
2023-01-01 11:36:20 +00:00
2022-07-04 03:37:48 +00:00
if v :
2022-10-29 00:13:37 +00:00
# shadowban check is done in sort_objects
# output is needed: see comments.py
2023-06-23 13:46:42 +00:00
comments , output = get_comments_v_properties ( v , None , Comment . parent_post == pid , Comment . stickied == None , Comment . id . notin_ ( ids ) , Comment . level < 10 )
2022-07-04 03:37:48 +00:00
comments = comments . filter ( Comment . level == 1 )
2023-02-27 15:38:12 +00:00
comments = sort_objects ( sort , comments , Comment )
2022-07-04 03:37:48 +00:00
2022-09-04 23:21:48 +00:00
comments = [ c [ 0 ] for c in comments . all ( ) ]
2022-07-04 03:37:48 +00:00
else :
2023-03-16 06:27:58 +00:00
comments = g . db . query ( Comment ) . filter (
2023-06-23 13:46:42 +00:00
Comment . parent_post == pid ,
2022-10-13 10:47:55 +00:00
Comment . level == 1 ,
Comment . stickied == None ,
Comment . id . notin_ ( ids )
)
2022-07-04 03:37:48 +00:00
2023-02-27 15:38:12 +00:00
comments = sort_objects ( sort , comments , Comment )
2023-01-01 11:36:20 +00:00
2022-09-04 23:21:48 +00:00
comments = comments . offset ( offset ) . all ( )
2022-07-04 03:37:48 +00:00
comments2 = [ ]
count = 0
2023-02-28 22:37:12 +00:00
if p . created_utc > 1638672040 : # TODO: migrate old comments to use top_comment_id
2022-07-04 03:37:48 +00:00
for comment in comments :
comments2 . append ( comment )
ids . add ( comment . id )
2023-06-23 13:46:42 +00:00
count + = g . db . query ( Comment ) . filter_by ( parent_post = p . id , top_comment_id = comment . id ) . count ( ) + 1
2022-07-04 03:37:48 +00:00
if count > 100 : break
else :
for comment in comments :
comments2 . append ( comment )
ids . add ( comment . id )
2023-06-23 13:46:42 +00:00
count + = g . db . query ( Comment ) . filter_by ( parent_post = p . id , parent_comment_id = comment . id ) . count ( ) + 1
2022-07-04 03:37:48 +00:00
if count > 20 : break
2023-01-01 11:36:20 +00:00
2022-07-04 03:37:48 +00:00
if len ( comments ) == len ( comments2 ) : offset = 0
else : offset + = 1
comments = comments2
2023-02-28 22:37:12 +00:00
return render_template ( " comments.html " , v = v , comments = comments , p = p , ids = list ( ids ) , render_replies = True , pid = pid , sort = sort , offset = offset )
2022-05-04 23:09:46 +00:00
2022-12-29 14:20:27 +00:00
@app.get ( " /more_comments/<int:cid> " )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
2022-08-05 20:40:48 +00:00
@auth_desired_with_logingate
2022-12-29 14:20:27 +00:00
def more_comments ( v , cid ) :
2022-05-04 23:09:46 +00:00
try : cid = int ( cid )
2022-10-09 08:25:21 +00:00
except : abort ( 404 )
2022-05-04 23:09:46 +00:00
2023-03-16 06:27:58 +00:00
tcid = g . db . query ( Comment . top_comment_id ) . filter_by ( id = cid ) . one_or_none ( ) [ 0 ]
2022-05-04 23:09:46 +00:00
if v :
2022-10-29 00:13:37 +00:00
# shadowban check is done in sort_objects i think
# output is needed: see comments.py
2023-02-27 16:16:12 +00:00
comments , output = get_comments_v_properties ( v , None , Comment . top_comment_id == tcid , Comment . level > 9 )
2022-12-14 18:24:14 +00:00
comments = comments . filter ( Comment . parent_comment_id == cid )
comments = [ c [ 0 ] for c in comments . all ( ) ]
2022-05-04 23:09:46 +00:00
else :
2022-06-24 13:19:53 +00:00
c = get_comment ( cid )
2023-07-08 13:32:14 +00:00
comments = c . replies ( sort = request . values . get ( ' sort ' ) )
2022-05-04 23:09:46 +00:00
if comments : p = comments [ 0 ] . post
else : p = None
2023-01-01 11:36:20 +00:00
2022-08-30 05:26:13 +00:00
return render_template ( " comments.html " , v = v , comments = comments , p = p , render_replies = True )
2022-05-04 23:09:46 +00:00
2022-11-15 10:57:49 +00:00
def thumbnail_thread ( pid : int , vid : int ) :
2023-03-16 06:27:58 +00:00
db = db_session ( )
2022-05-04 23:09:46 +00:00
def expand_url ( post_url , fragment_url ) :
if fragment_url . startswith ( " https:// " ) :
return fragment_url
elif fragment_url . startswith ( " https:// " ) :
return f " https:// { fragment_url . split ( ' https:// ' ) [ 1 ] } "
elif fragment_url . startswith ( ' // ' ) :
return f " https: { fragment_url } "
2022-05-25 19:45:34 +00:00
elif fragment_url . startswith ( ' / ' ) and ' \\ ' not in fragment_url :
2022-05-04 23:09:46 +00:00
parsed_url = urlparse ( post_url )
return f " https:// { parsed_url . netloc } { fragment_url } "
else :
2022-07-08 18:06:54 +00:00
return f " { post_url } / { fragment_url } "
2022-05-04 23:09:46 +00:00
2023-06-07 23:26:32 +00:00
p = db . get ( Post , pid )
2023-01-01 11:36:20 +00:00
2023-02-28 22:37:12 +00:00
if not p or not p . url :
2022-08-25 15:26:27 +00:00
time . sleep ( 5 )
2023-06-07 23:26:32 +00:00
p = db . get ( Post , pid )
2022-08-25 15:26:27 +00:00
2023-02-28 22:37:12 +00:00
if not p or not p . url : return
2023-01-01 11:36:20 +00:00
2023-02-28 22:37:12 +00:00
fetch_url = p . url
2022-05-04 23:09:46 +00:00
2022-05-25 19:45:34 +00:00
if fetch_url . startswith ( ' / ' ) and ' \\ ' not in fetch_url :
2022-10-27 17:53:08 +00:00
fetch_url = f " { SITE_FULL } { fetch_url } "
2022-05-04 23:09:46 +00:00
2022-08-25 15:26:27 +00:00
try :
2023-05-15 00:04:14 +00:00
x = requests . get ( fetch_url , headers = HEADERS , timeout = 5 , proxies = proxies )
2022-08-25 15:26:27 +00:00
except :
db . close ( )
return
if x . status_code != 200 :
db . close ( )
return
2022-05-04 23:09:46 +00:00
if x . headers . get ( " Content-Type " , " " ) . startswith ( " text/html " ) :
soup = BeautifulSoup ( x . content , ' lxml ' )
thumb_candidate_urls = [ ]
meta_tags = [
" drama:thumbnail " ,
" twitter:image " ,
" og:image " ,
" thumbnail "
]
for tag_name in meta_tags :
2023-01-01 11:36:20 +00:00
2022-05-04 23:09:46 +00:00
tag = soup . find (
2023-01-01 11:36:20 +00:00
' meta ' ,
2022-05-04 23:09:46 +00:00
attrs = {
2023-01-01 11:36:20 +00:00
" name " : tag_name ,
2022-05-04 23:09:46 +00:00
" content " : True
}
)
if not tag :
tag = soup . find (
' meta ' ,
attrs = {
' property ' : tag_name ,
' content ' : True
}
)
if tag :
2023-02-28 22:37:12 +00:00
thumb_candidate_urls . append ( expand_url ( p . url , tag [ ' content ' ] ) )
2022-05-04 23:09:46 +00:00
for tag in soup . find_all ( " img " , attrs = { ' src ' : True } ) :
2023-02-28 22:37:12 +00:00
thumb_candidate_urls . append ( expand_url ( p . url , tag [ ' src ' ] ) )
2022-05-04 23:09:46 +00:00
for url in thumb_candidate_urls :
try :
2023-05-15 00:04:14 +00:00
image_req = requests . get ( url , headers = HEADERS , timeout = 5 , proxies = proxies )
2022-05-04 23:09:46 +00:00
except :
continue
if image_req . status_code > = 400 :
continue
if not image_req . headers . get ( " Content-Type " , " " ) . startswith ( " image/ " ) :
continue
if image_req . headers . get ( " Content-Type " , " " ) . startswith ( " image/svg " ) :
continue
2022-10-25 15:39:57 +00:00
with Image . open ( BytesIO ( image_req . content ) ) as i :
if i . width < 30 or i . height < 30 :
continue
2022-05-04 23:09:46 +00:00
break
2022-08-25 15:26:27 +00:00
else :
db . close ( )
return
2022-05-04 23:09:46 +00:00
elif x . headers . get ( " Content-Type " , " " ) . startswith ( " image/ " ) :
image_req = x
2022-10-25 15:39:57 +00:00
with Image . open ( BytesIO ( x . content ) ) as i :
size = len ( i . fp . read ( ) )
2022-10-28 17:13:21 +00:00
if size > 8 * 1024 * 1024 :
db . close ( )
return
2022-05-04 23:09:46 +00:00
2022-08-25 15:26:27 +00:00
else :
db . close ( )
return
2022-05-04 23:09:46 +00:00
name = f ' /images/ { time . time ( ) } ' . replace ( ' . ' , ' ' ) + ' .webp '
with open ( name , " wb " ) as file :
for chunk in image_req . iter_content ( 1024 ) :
file . write ( chunk )
2022-11-15 09:19:08 +00:00
v = db . get ( User , vid )
2023-03-16 06:27:58 +00:00
url = process_image ( name , v , resize = 99 , uploader_id = p . author_id , db = db )
2022-11-15 09:19:08 +00:00
if url :
2023-02-28 22:37:12 +00:00
p . thumburl = url
db . add ( p )
2022-11-15 09:19:08 +00:00
db . commit ( )
2022-08-25 15:26:27 +00:00
db . close ( )
2022-05-04 23:09:46 +00:00
stdout . flush ( )
@app.post ( " /is_repost " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2023-04-02 08:04:32 +00:00
@auth_required
def is_repost ( v ) :
2022-10-14 10:26:48 +00:00
not_a_repost = { ' permalink ' : ' ' }
2022-08-04 04:07:17 +00:00
if not FEATURES [ ' REPOST_DETECTION ' ] :
2022-10-14 10:26:48 +00:00
return not_a_repost
2022-05-04 23:09:46 +00:00
url = request . values . get ( ' url ' )
2023-06-30 21:30:24 +00:00
if not url or len ( url ) < MIN_REPOST_CHECK_URL_LENGTH :
abort ( 400 )
2022-05-04 23:09:46 +00:00
2022-05-25 08:43:16 +00:00
url = normalize_url ( url )
2023-06-26 15:28:07 +00:00
url = unquote ( url )
2022-05-04 23:09:46 +00:00
parsed_url = urlparse ( url )
domain = parsed_url . netloc
2022-12-23 22:22:41 +00:00
if domain in { ' old.reddit.com ' , ' twitter.com ' , ' instagram.com ' , ' tiktok.com ' } and ' /search ' not in url :
2022-05-04 23:09:46 +00:00
new_url = ParseResult ( scheme = " https " ,
netloc = parsed_url . netloc ,
path = parsed_url . path ,
params = parsed_url . params ,
query = None ,
fragment = parsed_url . fragment )
else :
2022-09-01 20:46:57 +00:00
qd = parse_qs ( parsed_url . query , keep_blank_values = True )
2022-05-04 23:09:46 +00:00
filtered = { k : val for k , val in qd . items ( ) if not k . startswith ( ' utm_ ' ) and not k . startswith ( ' ref_ ' ) }
new_url = ParseResult ( scheme = " https " ,
netloc = parsed_url . netloc ,
path = parsed_url . path ,
params = parsed_url . params ,
query = urlencode ( filtered , doseq = True ) ,
fragment = parsed_url . fragment )
2023-01-01 11:36:20 +00:00
2022-05-04 23:09:46 +00:00
url = urlunparse ( new_url )
2022-09-05 01:44:38 +00:00
url = url . rstrip ( ' / ' )
2022-05-04 23:09:46 +00:00
search_url = url . replace ( ' % ' , ' ' ) . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . strip ( )
2023-06-07 23:26:32 +00:00
repost = g . db . query ( Post ) . filter (
Post . url . ilike ( search_url ) ,
Post . deleted_utc == 0 ,
Post . is_banned == False
2022-05-04 23:09:46 +00:00
) . first ( )
if repost : return { ' permalink ' : repost . permalink }
2022-10-14 10:26:48 +00:00
else : return not_a_repost
2022-05-04 23:09:46 +00:00
@app.post ( " /submit " )
@app.post ( " /h/<sub>/submit " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( POST_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( POST_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
2022-11-26 21:00:03 +00:00
def submit_post ( v : User , sub = None ) :
2022-05-04 23:09:46 +00:00
url = request . values . get ( " url " , " " ) . strip ( )
2022-05-25 18:29:22 +00:00
if ' \\ ' in url : abort ( 400 )
2022-10-05 08:04:32 +00:00
title = sanitize_raw_title ( request . values . get ( " title " , " " ) )
2022-10-09 12:54:46 +00:00
body = sanitize_raw_body ( request . values . get ( " body " , " " ) , True )
2022-06-29 01:13:11 +00:00
2022-10-05 08:04:32 +00:00
if not title :
2023-02-17 23:25:01 +00:00
abort ( 400 , " Please enter a better title! " )
2022-12-05 01:21:47 +00:00
sub = request . values . get ( " sub " , " " ) . lower ( ) . replace ( ' /h/ ' , ' ' ) . strip ( )
2023-06-27 12:06:26 +00:00
if SITE == ' rdrama.net ' and v . chud == 1 :
2023-06-27 12:02:34 +00:00
sub = ' chudrama '
2023-03-23 15:36:28 +00:00
title_html = filter_emojis_only ( title , graceful = True , count_emojis = True )
2022-10-05 08:04:32 +00:00
if v . marseyawarded and not marseyaward_title_regex . fullmatch ( title_html ) :
2023-02-17 23:25:01 +00:00
abort ( 400 , " You can only type marseys! " )
2023-01-01 11:36:20 +00:00
if len ( title_html ) > POST_TITLE_HTML_LENGTH_LIMIT :
2023-02-17 23:25:01 +00:00
abort ( 400 , " Rendered title is too big! " )
2022-05-04 23:09:46 +00:00
2023-04-29 16:31:51 +00:00
if sub == ' changelog ' :
abort ( 400 , " /h/changelog is archived " )
2022-05-04 23:09:46 +00:00
2023-06-26 15:05:27 +00:00
if sub in { ' furry ' , ' vampire ' , ' racist ' , ' femboy ' , ' edgy ' } and not v . client and not v . house . lower ( ) . startswith ( sub ) :
2023-02-17 23:25:01 +00:00
abort ( 400 , f " You need to be a member of House { sub . capitalize ( ) } to post in /h/ { sub } " )
2022-08-21 15:24:16 +00:00
2022-05-04 23:09:46 +00:00
if sub and sub != ' none ' :
sname = sub . strip ( ) . lower ( )
2023-03-16 06:27:58 +00:00
sub = g . db . query ( Sub . name ) . filter_by ( name = sname ) . one_or_none ( )
2023-02-17 23:25:01 +00:00
if not sub : abort ( 400 , f " /h/ { sname } not found! " )
2022-05-04 23:09:46 +00:00
sub = sub [ 0 ]
2023-02-17 23:25:01 +00:00
if v . exiled_from ( sub ) : abort ( 400 , f " You ' re exiled from /h/ { sub } " )
2022-05-04 23:09:46 +00:00
else : sub = None
2022-06-26 01:01:21 +00:00
if not sub and HOLE_REQUIRED :
2023-02-17 23:25:01 +00:00
abort ( 400 , f " You must choose a { HOLE_NAME } for your post! " )
2022-06-26 01:01:21 +00:00
2023-02-17 23:25:01 +00:00
if v . is_suspended : abort ( 400 , " You can ' t perform this action while banned! " )
2022-05-04 23:09:46 +00:00
if v . longpost and ( len ( body ) < 280 or ' []( ' in body or body . startswith ( ' []( ' ) ) :
2023-02-17 23:25:01 +00:00
abort ( 400 , " You have to type more than 280 characters! " )
2022-05-04 23:09:46 +00:00
elif v . bird and len ( body ) > 140 :
2023-02-17 23:25:01 +00:00
abort ( 400 , " You have to type less than 140 characters! " )
2022-05-04 23:09:46 +00:00
embed = None
if url :
2022-05-25 08:43:16 +00:00
url = normalize_url ( url )
2023-06-26 15:28:07 +00:00
url = unquote ( url )
2022-05-04 23:09:46 +00:00
parsed_url = urlparse ( url )
domain = parsed_url . netloc
2022-12-23 22:22:41 +00:00
if domain in { ' old.reddit.com ' , ' twitter.com ' , ' instagram.com ' , ' tiktok.com ' } and ' /search ' not in url :
2022-07-01 18:29:12 +00:00
new_url = ParseResult ( scheme = " https " ,
netloc = parsed_url . netloc ,
path = parsed_url . path ,
params = parsed_url . params ,
query = None ,
fragment = parsed_url . fragment )
else :
2022-09-01 20:46:57 +00:00
qd = parse_qs ( parsed_url . query , keep_blank_values = True )
2022-07-01 18:29:12 +00:00
filtered = { k : val for k , val in qd . items ( ) if not k . startswith ( ' utm_ ' ) and not k . startswith ( ' ref_ ' ) }
new_url = ParseResult ( scheme = " https " ,
netloc = parsed_url . netloc ,
path = parsed_url . path ,
params = parsed_url . params ,
query = urlencode ( filtered , doseq = True ) ,
fragment = parsed_url . fragment )
2023-01-01 11:36:20 +00:00
2022-07-01 18:29:12 +00:00
url = urlunparse ( new_url )
2022-09-05 01:44:38 +00:00
url = url . rstrip ( ' / ' )
2022-07-01 18:29:12 +00:00
2023-06-02 13:48:58 +00:00
if v . admin_level < PERMS [ " IGNORE_DOMAIN_BAN " ] :
y = tldextract . extract ( url ) . registered_domain + parsed_url . path
y = y . lower ( )
banned_domains = g . db . query ( BannedDomain ) . all ( )
for x in banned_domains :
if y . startswith ( x . domain ) :
abort ( 400 , f ' Remove the banned link " { x . domain } " and try again! \n Reason for link ban: " { x . reason } " ' )
2022-10-27 22:37:24 +00:00
2022-10-27 22:42:32 +00:00
if " twitter.com " == domain :
2022-06-11 09:53:53 +00:00
try :
2022-09-26 04:01:25 +00:00
embed = requests . get ( " https://publish.twitter.com/oembed " , params = { " url " : url , " omit_script " : " t " } , timeout = 5 ) . json ( ) [ " html " ]
2022-10-29 21:46:30 +00:00
embed = embed . replace ( ' <a href ' , ' <a rel= " nofollow noopener " href ' )
2022-05-04 23:09:46 +00:00
except : pass
2023-01-23 02:06:56 +00:00
elif url . startswith ( ' https://youtube.com/watch? ' ) :
2023-01-25 11:17:12 +00:00
embed = handle_youtube_links ( url )
2022-07-11 11:55:15 +00:00
elif SITE in domain and " /post/ " in url and " context " not in url and url . count ( ' / ' ) < 6 :
2022-05-04 23:09:46 +00:00
id = url . split ( " /post/ " ) [ 1 ]
if " / " in id : id = id . split ( " / " ) [ 0 ]
embed = str ( int ( id ) )
2022-10-05 08:16:56 +00:00
if not url and not body and not request . files . get ( " file " ) and not request . files . get ( " file-url " ) :
2023-02-17 23:25:01 +00:00
abort ( 400 , " Please enter a url or some text! " )
2022-05-04 23:09:46 +00:00
2023-01-01 11:36:20 +00:00
if not IS_LOCALHOST :
2023-06-07 23:26:32 +00:00
dup = g . db . query ( Post ) . filter (
Post . author_id == v . id ,
Post . deleted_utc == 0 ,
Post . title == title ,
Post . url == url ,
Post . body == body
2022-11-06 23:48:37 +00:00
) . one_or_none ( )
2023-03-06 00:00:01 +00:00
if dup :
2023-03-09 05:52:20 +00:00
return { " post_id " : dup . id , " success " : False }
2022-05-04 23:09:46 +00:00
2023-06-23 13:46:42 +00:00
if not execute_antispam_post_check ( title , v , url ) :
2022-05-04 23:09:46 +00:00
return redirect ( " /notifications " )
if len ( url ) > 2048 :
2023-02-17 23:25:01 +00:00
abort ( 400 , " There ' s a 2048 character limit for URLs! " )
2022-05-04 23:09:46 +00:00
2023-02-26 12:08:37 +00:00
body = process_files ( request . files , v , body )
2023-02-28 19:36:14 +00:00
body = body . strip ( ) [ : POST_BODY_LENGTH_LIMIT ( v ) ] # process_files() adds content to the body, so we need to re-strip
2022-05-22 10:26:59 +00:00
2023-06-08 01:36:41 +00:00
body_html = sanitize ( body , count_emojis = True , limit_pings = 100 )
2022-05-04 23:09:46 +00:00
if v . marseyawarded and marseyaward_body_regex . search ( body_html ) :
2023-02-17 23:25:01 +00:00
abort ( 400 , " You can only type marseys! " )
2022-05-04 23:09:46 +00:00
2022-12-28 09:50:48 +00:00
if len ( body_html ) > POST_BODY_HTML_LENGTH_LIMIT :
2023-06-07 23:26:32 +00:00
abort ( 400 , " Post body_html too long! " )
2022-05-04 23:09:46 +00:00
2022-11-11 05:21:18 +00:00
flag_notify = ( request . values . get ( " notify " , " on " ) == " on " )
2022-12-03 22:01:08 +00:00
flag_new = request . values . get ( " new " , False , bool ) or ' megathread ' in title . lower ( )
2023-02-08 06:22:11 +00:00
flag_over_18 = FEATURES [ ' NSFW_MARKING ' ] and request . values . get ( " over_18 " , False , bool )
2022-11-11 05:21:18 +00:00
flag_private = request . values . get ( " private " , False , bool )
2022-11-14 17:17:29 +00:00
flag_ghost = request . values . get ( " ghost " , False , bool ) and v . can_post_in_ghost_threads
2022-05-04 23:09:46 +00:00
2023-07-01 00:39:24 +00:00
if flag_ghost : sub = None
2022-11-11 05:21:18 +00:00
if embed and len ( embed ) > 1500 : embed = None
2022-07-09 07:41:05 +00:00
if embed : embed = embed . strip ( )
2023-06-08 00:32:33 +00:00
if url and url . startswith ( f ' { SITE_FULL } / ' ) :
2022-10-18 11:09:53 +00:00
url = url . split ( SITE_FULL ) [ 1 ]
2023-05-15 09:27:24 +00:00
if url == ' ' : url = None
2023-06-23 13:14:23 +00:00
flag_chudded = v . chud and sub != ' chudrama '
2023-06-07 23:26:32 +00:00
p = Post (
2022-11-11 05:21:18 +00:00
private = flag_private ,
notify = flag_notify ,
2022-05-04 23:09:46 +00:00
author_id = v . id ,
2022-11-11 05:21:18 +00:00
over_18 = flag_over_18 ,
new = flag_new ,
2022-05-04 23:09:46 +00:00
app_id = v . client . application . id if v . client else None ,
2022-10-15 14:11:14 +00:00
is_bot = ( v . client is not None ) ,
2022-05-04 23:09:46 +00:00
url = url ,
2022-10-05 08:16:56 +00:00
body = body ,
2022-05-04 23:09:46 +00:00
body_html = body_html ,
2023-02-18 16:33:19 +00:00
embed = embed ,
2022-10-05 08:04:32 +00:00
title = title ,
2022-05-04 23:09:46 +00:00
title_html = title_html ,
sub = sub ,
2023-06-23 13:14:23 +00:00
ghost = flag_ghost ,
chudded = flag_chudded ,
2022-05-04 23:09:46 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( p )
g . db . flush ( )
2022-05-04 23:09:46 +00:00
2023-05-14 20:38:09 +00:00
execute_under_siege ( v , p , p . body , ' post ' )
2023-03-13 19:18:08 +00:00
2023-02-28 22:37:12 +00:00
process_poll_options ( v , p )
2023-02-28 22:09:16 +00:00
2023-02-28 22:37:12 +00:00
for text in { p . body , p . title , p . url } :
2023-05-14 20:38:09 +00:00
if execute_blackjack ( v , p , text , ' post ' ) : break
2022-05-04 23:09:46 +00:00
vote = Vote ( user_id = v . id ,
vote_type = 1 ,
2023-06-07 23:26:32 +00:00
post_id = p . id ,
2023-03-12 09:40:30 +00:00
coins = 0
2022-05-04 23:09:46 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( vote )
2023-01-01 11:36:20 +00:00
2022-11-15 09:19:08 +00:00
if request . files . get ( ' file-url ' ) and not g . is_tor :
2022-06-18 15:53:34 +00:00
file = request . files [ ' file-url ' ]
2022-05-04 23:09:46 +00:00
if file . content_type . startswith ( ' image/ ' ) :
name = f ' /images/ { time . time ( ) } ' . replace ( ' . ' , ' ' ) + ' .webp '
file . save ( name )
2023-02-28 22:37:12 +00:00
p . url = process_image ( name , v )
2022-05-04 23:09:46 +00:00
name2 = name . replace ( ' .webp ' , ' r.webp ' )
copyfile ( name , name2 )
2023-02-28 22:37:12 +00:00
p . thumburl = process_image ( name2 , v , resize = 99 )
2022-05-04 23:09:46 +00:00
elif file . content_type . startswith ( ' video/ ' ) :
2023-02-28 22:37:12 +00:00
p . url = process_video ( file , v )
2022-12-09 06:35:56 +00:00
name = f ' /images/ { time . time ( ) } ' . replace ( ' . ' , ' ' ) + ' .webp '
2022-12-10 04:02:35 +00:00
subprocess . run ( [ ' ffmpeg ' , ' -y ' , ' -loglevel ' , ' warning ' ,
2023-03-11 06:29:52 +00:00
' -i ' , p . url , ' -vf ' , " scale= ' iw ' :-2 " ,
2023-06-29 19:38:55 +00:00
' -q:v ' , ' 3 ' , ' -frames:v ' , ' 1 ' , name ] , check = True , timeout = SUBPROCESS_TIMEOUT_DURATION )
2023-03-11 06:29:52 +00:00
p . posterurl = name
name2 = name . replace ( ' .webp ' , ' r.webp ' )
copyfile ( name , name2 )
p . thumburl = process_image ( name2 , v , resize = 99 )
2022-05-22 22:15:29 +00:00
elif file . content_type . startswith ( ' audio/ ' ) :
2023-02-28 22:37:12 +00:00
p . url = process_audio ( file , v )
2022-05-04 23:09:46 +00:00
else :
2022-06-19 16:56:45 +00:00
abort ( 415 )
2023-01-01 11:36:20 +00:00
2023-03-21 15:39:26 +00:00
if not p . thumburl and p . url and p . domain != SITE :
2023-02-28 22:37:12 +00:00
gevent . spawn ( thumbnail_thread , p . id , v . id )
2022-05-04 23:09:46 +00:00
2023-03-01 19:28:19 +00:00
if not p . private :
2023-06-24 20:31:12 +00:00
notify_users = NOTIFY_USERS ( f ' { title } { body } ' , v , ghost = p . ghost , log_cost = p )
2022-05-04 23:09:46 +00:00
if notify_users :
2023-02-28 22:37:12 +00:00
cid , text = notif_comment2 ( p )
2023-03-02 00:32:51 +00:00
if notify_users == ' everyone ' :
alert_everyone ( cid )
else :
for x in notify_users :
add_notif ( cid , x , text , pushnotif_url = p . permalink )
2022-05-04 23:09:46 +00:00
2023-03-23 12:50:01 +00:00
if not complies_with_chud ( p ) :
2023-02-28 22:37:12 +00:00
p . is_banned = True
p . ban_reason = " AutoJanny "
2022-05-04 23:09:46 +00:00
2023-06-23 11:07:47 +00:00
body = CHUD_MSG . format ( username = v . username , type = ' post ' , CHUD_PHRASE = v . chud_phrase )
2023-03-25 16:46:50 +00:00
body_jannied_html = sanitize ( body )
2022-05-04 23:09:46 +00:00
2022-07-08 19:03:04 +00:00
c_jannied = Comment ( author_id = AUTOJANNY_ID ,
2023-06-23 13:46:42 +00:00
parent_post = p . id ,
2022-05-04 23:09:46 +00:00
level = 1 ,
over_18 = False ,
is_bot = True ,
app_id = None ,
distinguish_level = 6 ,
2022-06-18 15:37:01 +00:00
body = body ,
2022-05-04 23:09:46 +00:00
body_html = body_jannied_html ,
2023-02-28 22:37:12 +00:00
ghost = p . ghost
2022-05-04 23:09:46 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( c_jannied )
g . db . flush ( )
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
p . comment_count + = 1
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2023-02-18 21:59:17 +00:00
2022-05-04 23:09:46 +00:00
c_jannied . top_comment_id = c_jannied . id
n = Notification ( comment_id = c_jannied . id , user_id = v . id )
2023-03-16 06:27:58 +00:00
g . db . add ( n )
2022-05-04 23:09:46 +00:00
2023-06-23 17:47:08 +00:00
autojanny = g . db . get ( User , AUTOJANNY_ID )
autojanny . comment_count + = 1
g . db . add ( autojanny )
2023-03-16 06:27:58 +00:00
if not p . private and not ( p . sub and g . db . query ( Exile . user_id ) . filter_by ( user_id = SNAPPY_ID , sub = p . sub ) . one_or_none ( ) ) :
2023-02-28 22:37:12 +00:00
execute_snappy ( p , v )
2022-05-10 07:20:49 +00:00
2023-06-07 23:26:32 +00:00
v . post_count = g . db . query ( Post ) . filter_by ( author_id = v . id , deleted_utc = 0 ) . count ( )
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
execute_lawlz_actions ( v , p )
2022-08-21 17:14:03 +00:00
2023-04-24 15:17:12 +00:00
if ( SITE == ' rdrama.net '
and v . id in { TGTW_ID , SNALLY_ID }
2023-07-06 01:44:05 +00:00
and not ( p . sub and p . subr . stealth ) ) and p . sub != ' slavshit ' and not p . ghost :
2023-06-20 10:29:36 +00:00
p . stickied_utc = int ( time . time ( ) ) + 28800
2023-04-24 15:17:12 +00:00
p . stickied = " AutoJanny "
2022-05-04 23:09:46 +00:00
cache . delete_memoized ( frontlist )
2022-11-15 09:19:08 +00:00
cache . delete_memoized ( userpagelisting )
2022-05-04 23:09:46 +00:00
2023-03-16 06:27:58 +00:00
g . db . flush ( )
2023-06-27 20:03:14 +00:00
key_pattern = app . config [ " CACHE_KEY_PREFIX " ] + ' frontpage_* '
for key in redis_instance . scan_iter ( key_pattern ) :
redis_instance . delete ( key )
2023-06-08 00:49:37 +00:00
if v . client : return p . json
2022-05-04 23:09:46 +00:00
else :
2023-02-28 22:37:12 +00:00
p . voted = 1
2023-03-09 05:52:20 +00:00
return { " post_id " : p . id , " success " : True }
2022-05-04 23:09:46 +00:00
2022-12-29 10:39:10 +00:00
@app.post ( " /delete_post/<int:pid> " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def delete_post_pid ( pid , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid )
if p . author_id != v . id : abort ( 403 )
2022-05-04 23:09:46 +00:00
2022-10-11 03:47:39 +00:00
# Temporary special logic by Carp request for events of 2022-10-10
2023-02-28 22:37:12 +00:00
if SITE_NAME == ' rDrama ' and p . author_id == 3161 : abort ( 403 )
2022-10-11 03:47:39 +00:00
2023-02-28 22:37:12 +00:00
if not p . deleted_utc :
p . deleted_utc = int ( time . time ( ) )
p . is_pinned = False
p . stickied = None
2022-05-04 23:09:46 +00:00
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2022-05-04 23:09:46 +00:00
2022-07-08 11:07:27 +00:00
cache . delete_memoized ( frontlist )
2022-11-15 09:19:08 +00:00
cache . delete_memoized ( userpagelisting )
2022-05-04 23:09:46 +00:00
2023-06-07 23:26:32 +00:00
v . post_count = g . db . query ( Post ) . filter_by ( author_id = v . id , deleted_utc = 0 ) . count ( )
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
return { " message " : " Post deleted! " }
2022-12-29 10:39:10 +00:00
@app.post ( " /undelete_post/<int:pid> " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def undelete_post_pid ( pid , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid )
if p . author_id != v . id : abort ( 403 )
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
if p . deleted_utc :
p . deleted_utc = 0
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2022-07-08 11:07:27 +00:00
cache . delete_memoized ( frontlist )
2022-11-15 09:19:08 +00:00
cache . delete_memoized ( userpagelisting )
2022-05-04 23:09:46 +00:00
2023-06-07 23:26:32 +00:00
v . post_count = g . db . query ( Post ) . filter_by ( author_id = v . id , deleted_utc = 0 ) . count ( )
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
return { " message " : " Post undeleted! " }
2022-12-29 10:39:10 +00:00
@app.post ( " /mark_post_nsfw/<int:pid> " )
2023-02-01 18:52:34 +00:00
@feature_required ( ' NSFW_MARKING ' )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
2022-12-10 14:47:20 +00:00
def mark_post_nsfw ( pid , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid )
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
if p . author_id != v . id and not v . admin_level > = PERMS [ ' POST_COMMENT_MODERATION ' ] and not ( p . sub and v . mods ( p . sub ) ) :
2022-05-04 23:09:46 +00:00
abort ( 403 )
2023-01-01 11:36:20 +00:00
2023-06-29 19:51:32 +00:00
if p . over_18 and v . is_permabanned :
2022-10-04 21:09:25 +00:00
abort ( 403 )
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
p . over_18 = True
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2022-05-04 23:09:46 +00:00
2023-02-28 22:37:12 +00:00
if p . author_id != v . id :
2022-10-06 00:57:08 +00:00
if v . admin_level > = PERMS [ ' POST_COMMENT_MODERATION ' ] :
2022-10-05 23:24:54 +00:00
ma = ModAction (
2022-12-10 14:47:20 +00:00
kind = " set_nsfw " ,
2022-10-05 23:24:54 +00:00
user_id = v . id ,
2023-06-07 23:26:32 +00:00
target_post_id = p . id ,
2022-05-30 12:00:16 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( ma )
2022-10-05 23:24:54 +00:00
else :
ma = SubAction (
2023-02-28 22:37:12 +00:00
sub = p . sub ,
2022-12-10 14:47:20 +00:00
kind = " set_nsfw " ,
2022-10-05 23:24:54 +00:00
user_id = v . id ,
2023-06-07 23:26:32 +00:00
target_post_id = p . id ,
2022-10-05 23:24:54 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( ma )
2023-02-28 22:37:12 +00:00
send_repeatable_notification ( p . author_id , f " @ { v . username } (a site admin) has marked [ { p . title } ](/post/ { p . id } ) as +18 " )
2022-05-04 23:09:46 +00:00
2022-12-10 14:47:20 +00:00
return { " message " : " Post has been marked as +18! " }
2022-12-29 10:39:10 +00:00
@app.post ( " /unmark_post_nsfw/<int:pid> " )
2023-02-01 18:52:34 +00:00
@feature_required ( ' NSFW_MARKING ' )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-12-10 14:47:20 +00:00
@auth_required
def unmark_post_nsfw ( pid , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid )
2022-12-10 14:47:20 +00:00
2023-02-28 22:37:12 +00:00
if p . author_id != v . id and not v . admin_level > = PERMS [ ' POST_COMMENT_MODERATION ' ] and not ( p . sub and v . mods ( p . sub ) ) :
2022-12-10 14:47:20 +00:00
abort ( 403 )
2023-01-01 11:36:20 +00:00
2023-06-29 19:51:32 +00:00
if p . over_18 and v . is_permabanned :
2022-12-10 14:47:20 +00:00
abort ( 403 )
2023-02-28 22:37:12 +00:00
p . over_18 = False
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2022-12-10 14:47:20 +00:00
2023-02-28 22:37:12 +00:00
if p . author_id != v . id :
2022-12-10 14:47:20 +00:00
if v . admin_level > = PERMS [ ' POST_COMMENT_MODERATION ' ] :
ma = ModAction (
kind = " unset_nsfw " ,
user_id = v . id ,
2023-06-07 23:26:32 +00:00
target_post_id = p . id ,
2022-12-10 14:47:20 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( ma )
2022-12-10 14:47:20 +00:00
else :
ma = SubAction (
2023-02-28 22:37:12 +00:00
sub = p . sub ,
2022-12-10 14:47:20 +00:00
kind = " unset_nsfw " ,
user_id = v . id ,
2023-06-07 23:26:32 +00:00
target_post_id = p . id ,
2022-12-10 14:47:20 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( ma )
2023-02-28 22:37:12 +00:00
send_repeatable_notification ( p . author_id , f " @ { v . username } (a site admin) has unmarked [ { p . title } ](/post/ { p . id } ) as +18 " )
2022-12-10 14:47:20 +00:00
return { " message " : " Post has been unmarked as +18! " }
2022-05-04 23:09:46 +00:00
2022-12-29 10:39:10 +00:00
@app.post ( " /save_post/<int:pid> " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def save_post ( pid , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid )
2022-05-04 23:09:46 +00:00
2023-06-07 23:26:32 +00:00
save = g . db . query ( SaveRelationship ) . filter_by ( user_id = v . id , post_id = p . id ) . one_or_none ( )
2022-05-04 23:09:46 +00:00
if not save :
2023-06-07 23:26:32 +00:00
new_save = SaveRelationship ( user_id = v . id , post_id = p . id )
2023-03-16 06:27:58 +00:00
g . db . add ( new_save )
2023-06-08 01:56:12 +00:00
cache . delete_memoized ( userpagelisting )
2022-05-04 23:09:46 +00:00
return { " message " : " Post saved! " }
2022-12-29 10:39:10 +00:00
@app.post ( " /unsave_post/<int:pid> " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def unsave_post ( pid , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( pid )
2022-05-04 23:09:46 +00:00
2023-06-07 23:26:32 +00:00
save = g . db . query ( SaveRelationship ) . filter_by ( user_id = v . id , post_id = p . id ) . one_or_none ( )
2022-05-04 23:09:46 +00:00
if save :
2023-03-16 06:27:58 +00:00
g . db . delete ( save )
2023-06-08 01:56:12 +00:00
cache . delete_memoized ( userpagelisting )
2022-05-04 23:09:46 +00:00
return { " message " : " Post unsaved! " }
2022-12-29 10:39:10 +00:00
@app.post ( " /pin/<int:post_id> " )
2023-02-27 05:33:45 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
2022-08-11 04:05:23 +00:00
def pin_post ( post_id , v ) :
2023-02-28 22:37:12 +00:00
p = get_post ( post_id )
if p :
if v . id != p . author_id : abort ( 403 , " Only the post author can do that! " )
p . is_pinned = not p . is_pinned
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2022-11-15 09:19:08 +00:00
cache . delete_memoized ( userpagelisting )
2023-02-28 22:37:12 +00:00
if p . is_pinned : return { " message " : " Post pinned! " }
2022-05-04 23:09:46 +00:00
else : return { " message " : " Post unpinned! " }
2022-10-11 13:01:39 +00:00
return abort ( 404 , " Post not found! " )
2022-05-04 23:09:46 +00:00
2022-12-29 10:39:10 +00:00
@app.put ( " /post/<int:post_id>/new " )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-12-01 22:04:10 +00:00
@auth_required
2022-12-10 14:57:19 +00:00
def set_new_sort ( post_id : int , v : User ) :
2023-02-28 22:37:12 +00:00
p = get_post ( post_id )
if not v . can_edit ( p ) : abort ( 403 , " Only the post author can do that! " )
p . new = True
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2022-12-10 14:57:19 +00:00
2023-02-28 22:37:12 +00:00
if v . id != p . author_id :
2022-12-10 14:57:19 +00:00
ma = ModAction (
kind = " set_new " ,
user_id = v . id ,
2023-06-07 23:26:32 +00:00
target_post_id = p . id ,
2022-12-10 14:57:19 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( ma )
2023-02-28 22:37:12 +00:00
send_repeatable_notification ( p . author_id , f " @ { v . username } (a site admin) has changed the the default sorting of comments on [ { p . title } ](/post/ { p . id } ) to `new` " )
2022-12-10 14:57:19 +00:00
2023-02-24 02:54:31 +00:00
return { " message " : " Changed the the default sorting of comments on this post to ' new ' " }
2022-12-10 14:57:19 +00:00
2022-12-29 10:39:10 +00:00
@app.delete ( " /post/<int:post_id>/new " )
2023-07-13 13:50:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( DEFAULT_RATELIMIT , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-12-10 14:57:19 +00:00
@auth_required
def unset_new_sort ( post_id : int , v : User ) :
2023-02-28 22:37:12 +00:00
p = get_post ( post_id )
if not v . can_edit ( p ) : abort ( 403 , " Only the post author can do that! " )
p . new = None
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2022-12-10 14:57:19 +00:00
2023-02-28 22:37:12 +00:00
if v . id != p . author_id :
2022-12-10 14:57:19 +00:00
ma = ModAction (
kind = " set_hot " ,
user_id = v . id ,
2023-06-07 23:26:32 +00:00
target_post_id = p . id ,
2022-12-10 14:57:19 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( ma )
2023-02-28 22:37:12 +00:00
send_repeatable_notification ( p . author_id , f " @ { v . username } (a site admin) has changed the the default sorting of comments on [ { p . title } ](/post/ { p . id } ) to `hot` " )
2022-12-10 14:57:19 +00:00
2023-02-24 02:54:31 +00:00
return { " message " : " Changed the the default sorting of comments on this post to ' hot ' " }
2022-12-01 22:04:10 +00:00
2022-05-04 23:09:46 +00:00
2022-10-30 14:55:43 +00:00
extensions = IMAGE_FORMATS + VIDEO_FORMATS + AUDIO_FORMATS
2022-08-30 01:51:09 +00:00
2022-05-04 23:09:46 +00:00
@app.get ( " /submit/title " )
2023-07-13 13:50:46 +00:00
@limiter.limit ( " 3/minute " , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( " 3/minute " , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def get_post_title ( v ) :
2022-11-25 13:10:05 +00:00
POST_TITLE_TIMEOUT = 5
2022-05-04 23:09:46 +00:00
url = request . values . get ( " url " )
2022-08-30 01:51:09 +00:00
if not url or ' \\ ' in url : abort ( 400 )
2022-11-12 05:40:17 +00:00
url = url . strip ( )
if not url . startswith ( ' http ' ) : abort ( 400 )
2022-08-30 01:51:09 +00:00
2022-10-30 14:55:43 +00:00
checking_url = url . lower ( ) . split ( ' ? ' ) [ 0 ] . split ( ' %3F ' ) [ 0 ]
if any ( ( checking_url . endswith ( f ' . { x } ' ) for x in extensions ) ) :
2022-08-26 22:03:15 +00:00
abort ( 400 )
2022-05-04 23:09:46 +00:00
2022-11-25 13:10:05 +00:00
try :
2023-05-15 00:04:14 +00:00
x = gevent . with_timeout ( POST_TITLE_TIMEOUT , requests . get , url , headers = HEADERS , timeout = POST_TITLE_TIMEOUT , proxies = proxies )
2022-05-04 23:09:46 +00:00
except : abort ( 400 )
2023-01-01 11:36:20 +00:00
2022-09-30 12:13:06 +00:00
content_type = x . headers . get ( " Content-Type " )
if not content_type or " text/html " not in content_type : abort ( 400 )
2022-05-04 23:09:46 +00:00
2022-11-11 09:24:54 +00:00
# no you can't just parse html with reeeeeeeegex
2022-11-11 10:17:59 +00:00
match = html_title_regex . search ( x . text )
2022-11-11 09:24:54 +00:00
if match and match . lastindex > = 1 :
title = match . group ( 1 )
2022-11-11 09:49:43 +00:00
else : abort ( 400 )
2022-05-04 23:09:46 +00:00
2022-11-26 06:11:00 +00:00
title = html . unescape ( title )
2022-11-11 10:17:59 +00:00
return { " url " : url , " title " : title }
2023-02-28 22:22:59 +00:00
@app.post ( " /edit_post/<int:pid> " )
@limiter.limit ( ' 1/second ' , scope = rpath )
2023-04-02 06:52:26 +00:00
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
2023-07-13 13:50:46 +00:00
@limiter.limit ( " 10/minute;100/hour;200/day " , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( " 10/minute;100/hour;200/day " , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2023-02-28 22:22:59 +00:00
@is_not_permabanned
def edit_post ( pid , v ) :
p = get_post ( pid )
if not v . can_edit ( p ) : abort ( 403 )
# Disable edits on things older than 1wk unless it's a draft or editor is a jannie
2023-03-06 20:22:15 +00:00
if time . time ( ) - p . created_utc > 7 * 24 * 60 * 60 and not p . private \
2023-07-07 21:28:08 +00:00
and v . admin_level < PERMS [ " IGNORE_1WEEk_EDITING_LIMIT " ] and v . id not in EXEMPT_FROM_1WEEK_EDITING_LIMIT :
2023-02-28 22:22:59 +00:00
abort ( 403 , " You can ' t edit posts older than 1 week! " )
title = sanitize_raw_title ( request . values . get ( " title " , " " ) )
body = sanitize_raw_body ( request . values . get ( " body " , " " ) , True )
if v . id == p . author_id :
if v . longpost and ( len ( body ) < 280 or ' []( ' in body or body . startswith ( ' []( ' ) ) :
abort ( 403 , " You have to type more than 280 characters! " )
elif v . bird and len ( body ) > 140 :
abort ( 403 , " You have to type less than 140 characters! " )
if not title :
abort ( 400 , " Please enter a better title! " )
2023-03-02 19:56:43 +00:00
if not p . private :
2023-07-16 11:56:24 +00:00
notify_users = NOTIFY_USERS ( f ' { title } { body } ' , v , oldtext = f ' { p . title } { p . body } ' , ghost = p . ghost , log_cost = p )
2023-03-02 19:56:43 +00:00
if notify_users :
cid , text = notif_comment2 ( p )
if notify_users == ' everyone ' :
alert_everyone ( cid )
else :
for x in notify_users :
add_notif ( cid , x , text , pushnotif_url = p . permalink )
2023-02-28 22:22:59 +00:00
if title != p . title :
2023-03-23 15:36:28 +00:00
title_html = filter_emojis_only ( title , golden = False )
2023-02-28 22:22:59 +00:00
if v . id == p . author_id and v . marseyawarded and not marseyaward_title_regex . fullmatch ( title_html ) :
abort ( 403 , " You can only type marseys! " )
if ' megathread ' in title . lower ( ) and ' megathread ' not in p . title . lower ( ) :
p . new = True
p . title = title
p . title_html = title_html
body = process_files ( request . files , v , body )
body = body . strip ( ) [ : POST_BODY_LENGTH_LIMIT ( v ) ] # process_files() may be adding stuff to the body
if body != p . body :
2023-06-08 01:36:41 +00:00
body_html = sanitize ( body , golden = False , limit_pings = 100 )
2023-02-28 22:22:59 +00:00
if v . id == p . author_id and v . marseyawarded and marseyaward_body_regex . search ( body_html ) :
abort ( 403 , " You can only type marseys! " )
p . body = body
process_poll_options ( v , p )
for text in [ p . body , p . title , p . url ] :
2023-05-14 20:38:09 +00:00
if execute_blackjack ( v , p , text , ' post ' ) : break
2023-02-28 22:22:59 +00:00
if len ( body_html ) > POST_BODY_HTML_LENGTH_LIMIT :
2023-06-07 23:26:32 +00:00
abort ( 400 , " Post body_html too long! " )
2023-02-28 22:22:59 +00:00
p . body_html = body_html
2023-03-25 18:18:12 +00:00
if not complies_with_chud ( p ) :
2023-06-23 11:07:47 +00:00
abort ( 403 , f ' You have to include " { v . chud_phrase } " in your post! ' )
2023-02-28 22:22:59 +00:00
if v . id == p . author_id :
if int ( time . time ( ) ) - p . created_utc > 60 * 3 : p . edited_utc = int ( time . time ( ) )
2023-03-16 06:27:58 +00:00
g . db . add ( p )
2023-02-28 22:22:59 +00:00
else :
ma = ModAction (
kind = " edit_post " ,
user_id = v . id ,
2023-06-07 23:26:32 +00:00
target_post_id = p . id
2023-02-28 22:22:59 +00:00
)
2023-03-16 06:27:58 +00:00
g . db . add ( ma )
2023-02-28 22:22:59 +00:00
2023-07-14 17:07:58 +00:00
return { " message " : " Post edited successfully! " }