2022-05-18 19:29:07 +00:00
import os
2022-11-15 09:19:08 +00:00
from shutil import copyfile
from sqlalchemy import func , nullslast
from files . helpers . media import process_files
2022-06-07 12:31:24 +00:00
import files . helpers . stats as statshelper
2022-11-15 09:19:08 +00:00
from files . classes . award import AWARDS
from files . classes . badges import Badge , BadgeDef
from files . classes . mod_logs import ModAction , ACTIONTYPES , ACTIONTYPES2
from files . classes . userblock import UserBlock
from files . helpers . actions import *
from files . helpers . alerts import *
from files . helpers . const import *
from files . routes . wrappers import *
from files . __main__ import app , cache , limiter
2022-05-04 23:09:46 +00:00
2022-09-29 04:26:50 +00:00
2022-05-04 23:09:46 +00:00
@app.get ( " /r/drama/comments/<id>/<title> " )
@app.get ( " /r/Drama/comments/<id>/<title> " )
def rdrama ( id , title ) :
id = ' ' . join ( f ' { x } / ' for x in id )
return redirect ( f ' /archives/drama/comments/ { id } { title } .html ' )
2022-11-28 22:12:39 +00:00
@app.get ( " /r/<subreddit> " )
@auth_desired
def subreddit ( subreddit , v ) :
reddit = v . reddit if v else " old.reddit.com "
return redirect ( f ' https:// { reddit } /r/ { subreddit } ' )
2022-05-04 23:09:46 +00:00
2022-11-28 23:45:52 +00:00
@app.get ( " /archives/<subreddit>/comments/<path:path> " )
@auth_desired
def reddit_post ( subreddit , v , path ) :
post_id = path . rsplit ( " / " , 1 ) [ 0 ] . replace ( ' / ' , ' ' )
reddit = v . reddit if v else " old.reddit.com "
return redirect ( f ' https:// { reddit } / { post_id } ' )
2022-05-04 23:09:46 +00:00
@app.get ( " /marseys " )
@auth_required
2022-11-26 21:00:03 +00:00
def marseys ( v : User ) :
2022-11-28 04:59:35 +00:00
if SITE == ' rdrama.net ' :
marseys = g . db . query ( Marsey , User ) . join ( User , Marsey . author_id == User . id ) . filter ( Marsey . submitter_id == None )
sort = request . values . get ( " sort " , " usage " )
if sort == " usage " :
marseys = marseys . order_by ( Marsey . count . desc ( ) , User . username ) . all ( )
elif sort == " added " :
marseys = marseys . order_by ( nullslast ( Marsey . created_utc . desc ( ) ) , User . username ) . all ( )
else : # implied sort == "author"
marseys = marseys . order_by ( User . username , Marsey . count . desc ( ) ) . all ( )
original = os . listdir ( " /asset_submissions/marseys/original " )
for marsey , user in marseys :
for x in IMAGE_FORMATS :
2022-11-05 21:01:23 +00:00
if f ' { marsey . name } . { x } ' in original :
marsey . og = f ' { marsey . name } . { x } '
break
2022-11-28 04:59:35 +00:00
else :
marseys = g . db . query ( Marsey ) . filter ( Marsey . submitter_id == None ) . order_by ( Marsey . count . desc ( ) )
2022-05-04 23:09:46 +00:00
return render_template ( " marseys.html " , v = v , marseys = marseys )
2022-11-27 16:59:36 +00:00
@app.get ( " /emojis " )
def emoji_list ( ) :
return jsonify ( get_emojis ( g . db ) )
@cache.cached ( timeout = 86400 , key_prefix = MARSEYS_CACHE_KEY )
def get_marseys ( db : scoped_session ) :
if not FEATURES [ ' MARSEYS ' ] : return [ ]
marseys = [ ]
for marsey , author in db . query ( Marsey , User ) . join ( User , Marsey . author_id == User . id ) . filter ( Marsey . submitter_id == None ) . order_by ( Marsey . count . desc ( ) ) :
marsey . author = author . username if FEATURES [ ' ASSET_SUBMISSIONS ' ] or author == " anton-d " else None
setattr ( marsey , " class " , " Marsey " )
marseys . append ( marsey )
return marseys
@cache.cached ( timeout = 600 , key_prefix = EMOJIS_CACHE_KEY )
def get_emojis ( db : scoped_session ) :
emojis = [ m . json ( ) for m in get_marseys ( db ) ]
2022-07-09 04:32:48 +00:00
for src in EMOJI_SRCS :
with open ( src , " r " , encoding = " utf-8 " ) as f :
emojis = emojis + json . load ( f )
2022-11-27 16:59:36 +00:00
return emojis
2022-05-04 23:09:46 +00:00
@app.get ( ' /sidebar ' )
2022-10-06 06:26:10 +00:00
@auth_desired
2022-11-26 21:00:03 +00:00
def sidebar ( v : Optional [ User ] ) :
2022-05-04 23:09:46 +00:00
return render_template ( ' sidebar.html ' , v = v )
@app.get ( " /stats " )
@auth_required
2022-11-26 21:00:03 +00:00
def participation_stats ( v : User ) :
2022-10-15 09:11:36 +00:00
if v . client : return stats_cached ( )
2022-09-05 20:23:35 +00:00
return render_template ( " stats.html " , v = v , title = " Content Statistics " , data = stats_cached ( ) )
2022-05-04 23:09:46 +00:00
@cache.memoize ( timeout = 86400 )
2022-06-10 09:47:41 +00:00
def stats_cached ( ) :
return statshelper . stats ( SITE_NAME )
2022-05-04 23:09:46 +00:00
@app.get ( " /chart " )
def chart ( ) :
return redirect ( ' /weekly_chart ' )
@app.get ( " /weekly_chart " )
@auth_required
2022-11-26 21:00:03 +00:00
def weekly_chart ( v : User ) :
2022-06-07 12:31:24 +00:00
return send_file ( statshelper . chart_path ( kind = " weekly " , site = SITE ) )
2022-05-04 23:09:46 +00:00
@app.get ( " /daily_chart " )
@auth_required
2022-11-26 21:00:03 +00:00
def daily_chart ( v : User ) :
2022-06-07 12:31:24 +00:00
return send_file ( statshelper . chart_path ( kind = " daily " , site = SITE ) )
2022-05-04 23:09:46 +00:00
@app.get ( " /patrons " )
@app.get ( " /paypigs " )
2022-10-06 05:48:55 +00:00
@admin_level_required ( PERMS [ ' VIEW_PATRONS ' ] )
2022-05-04 23:09:46 +00:00
def patrons ( v ) :
2022-09-30 21:07:53 +00:00
if AEVANN_ID and v . id not in ( AEVANN_ID , CARP_ID , SNAKES_ID ) : abort ( 404 )
2022-09-17 22:06:29 +00:00
2022-05-04 23:09:46 +00:00
users = g . db . query ( User ) . filter ( User . patron > 0 ) . order_by ( User . patron . desc ( ) , User . id ) . all ( )
2022-10-02 19:37:07 +00:00
return render_template ( " patrons.html " , v = v , users = users , benefactor_def = AWARDS [ ' benefactor ' ] )
2022-05-04 23:09:46 +00:00
@app.get ( " /admins " )
@app.get ( " /badmins " )
@auth_required
2022-11-26 21:00:03 +00:00
def admins ( v : User ) :
2022-11-26 01:15:39 +00:00
admins = g . db . query ( User ) . filter ( User . admin_level > = PERMS [ ' ADMIN_MOP_VISIBLE ' ] ) . order_by ( User . truescore . desc ( ) ) . all ( )
2022-05-04 23:09:46 +00:00
return render_template ( " admins.html " , v = v , admins = admins )
@app.get ( " /log " )
@app.get ( " /modlog " )
@auth_required
2022-11-26 21:00:03 +00:00
def log ( v : User ) :
2022-05-04 23:09:46 +00:00
try : page = max ( int ( request . values . get ( " page " , 1 ) ) , 1 )
except : page = 1
admin = request . values . get ( " admin " )
2022-10-01 05:15:56 +00:00
if admin : admin_id = get_id ( admin )
2022-05-04 23:09:46 +00:00
else : admin_id = 0
kind = request . values . get ( " kind " )
2022-10-06 05:56:19 +00:00
if v and v . admin_level > = PERMS [ ' USER_SHADOWBAN ' ] : types = ACTIONTYPES
2022-05-04 23:09:46 +00:00
else : types = ACTIONTYPES2
2022-06-13 16:41:46 +00:00
if kind and kind not in types :
kind = None
actions = [ ]
else :
actions = g . db . query ( ModAction )
2022-10-06 02:24:37 +00:00
if not ( v and v . admin_level > = PERMS [ ' USER_SHADOWBAN ' ] ) :
2022-11-13 02:37:33 +00:00
actions = actions . filter ( ModAction . kind . notin_ ( [
" shadowban " , " unshadowban " ,
" mod_mute_user " , " mod_unmute_user " ,
2022-11-14 17:32:13 +00:00
" link_accounts " , " delink_accounts " ,
2022-11-13 02:37:33 +00:00
] ) )
2022-06-13 16:41:46 +00:00
if admin_id :
actions = actions . filter_by ( user_id = admin_id )
kinds = set ( [ x . kind for x in actions ] )
2022-10-02 07:50:59 +00:00
kinds . add ( kind )
2022-06-13 16:41:46 +00:00
types2 = { }
for k , val in types . items ( ) :
if k in kinds : types2 [ k ] = val
types = types2
if kind : actions = actions . filter_by ( kind = kind )
2022-10-29 03:20:48 +00:00
actions = actions . order_by ( ModAction . id . desc ( ) ) . offset ( PAGE_SIZE * ( page - 1 ) ) . limit ( PAGE_SIZE + 1 ) . all ( )
2022-06-13 16:41:46 +00:00
2022-10-29 03:20:48 +00:00
next_exists = len ( actions ) > PAGE_SIZE
actions = actions [ : PAGE_SIZE ]
2022-10-06 05:25:45 +00:00
admins = [ x [ 0 ] for x in g . db . query ( User . username ) . filter ( User . admin_level > = PERMS [ ' ADMIN_MOP_VISIBLE ' ] ) . order_by ( User . username ) . all ( ) ]
2022-05-04 23:09:46 +00:00
2022-11-04 23:51:42 +00:00
return render_template ( " log.html " , v = v , admins = admins , types = types , admin = admin , type = kind , actions = actions , next_exists = next_exists , page = page , single_user_url = ' admin ' )
2022-05-04 23:09:46 +00:00
@app.get ( " /log/<id> " )
@auth_required
def log_item ( id , v ) :
try : id = int ( id )
except : abort ( 404 )
2022-06-18 00:57:23 +00:00
action = g . db . get ( ModAction , id )
2022-05-04 23:09:46 +00:00
if not action : abort ( 404 )
2022-10-06 05:25:45 +00:00
admins = [ x [ 0 ] for x in g . db . query ( User . username ) . filter ( User . admin_level > = PERMS [ ' ADMIN_MOP_VISIBLE ' ] ) . all ( ) ]
2022-05-04 23:09:46 +00:00
2022-10-06 05:25:45 +00:00
if v and v . admin_level > = PERMS [ ' USER_SHADOWBAN ' ] : types = ACTIONTYPES
2022-05-04 23:09:46 +00:00
else : types = ACTIONTYPES2
2022-11-04 23:51:42 +00:00
return render_template ( " log.html " , v = v , actions = [ action ] , next_exists = False , page = 1 , action = action , admins = admins , types = types , single_user_url = ' admin ' )
2022-05-04 23:09:46 +00:00
2022-06-15 07:27:04 +00:00
@app.get ( " /directory " )
2022-09-20 00:54:10 +00:00
@auth_required
2022-11-26 21:00:03 +00:00
def static_megathread_index ( v : User ) :
2022-06-15 07:27:04 +00:00
return render_template ( " megathread_index.html " , v = v )
2022-05-04 23:09:46 +00:00
2022-11-26 21:04:39 +00:00
@app.get ( " /api " )
@auth_required
def api ( v ) :
return render_template ( " api.html " , v = v )
2022-05-04 23:09:46 +00:00
@app.get ( " /contact " )
2022-09-09 09:13:50 +00:00
@app.get ( " /contactus " )
@app.get ( " /contact_us " )
2022-05-04 23:09:46 +00:00
@app.get ( " /press " )
@app.get ( " /media " )
2022-10-01 10:14:46 +00:00
@auth_desired
2022-11-26 21:00:03 +00:00
def contact ( v : Optional [ User ] ) :
2022-05-04 23:09:46 +00:00
return render_template ( " contact.html " , v = v )
@app.post ( " /send_admin " )
2022-11-13 02:37:33 +00:00
@limiter.limit ( " 1/second;1/2 minutes;10/day " )
2022-05-04 23:09:46 +00:00
@auth_required
2022-11-25 23:37:04 +00:00
@ratelimit_user ( " 1/second;1/2 minutes;10/day " )
2022-05-04 23:09:46 +00:00
def submit_contact ( v ) :
body = request . values . get ( " message " )
if not body : abort ( 400 )
2022-09-01 21:29:27 +00:00
if v . is_muted :
abort ( 403 )
2022-11-17 16:00:49 +00:00
body = f ' This message has been sent automatically to all admins via [/contact](/contact) \n \n Message: \n \n { body } '
2022-11-15 09:19:08 +00:00
body + = process_files ( request . files , v )
2022-06-18 17:41:24 +00:00
body = body . strip ( )
body_html = sanitize ( body )
2022-05-04 23:09:46 +00:00
2022-11-14 03:48:52 +00:00
execute_antispam_duplicate_comment_check ( v , body_html )
2022-11-13 02:48:33 +00:00
2022-05-04 23:09:46 +00:00
new_comment = Comment ( author_id = v . id ,
2022-09-04 23:15:37 +00:00
parent_submission = None ,
level = 1 ,
body_html = body_html ,
2022-11-17 22:50:06 +00:00
sentto = MODMAIL_ID
2022-09-04 23:15:37 +00:00
)
2022-05-04 23:09:46 +00:00
g . db . add ( new_comment )
g . db . flush ( )
2022-11-14 02:43:26 +00:00
execute_blackjack ( v , new_comment , new_comment . body_html , ' modmail ' )
2022-05-04 23:09:46 +00:00
new_comment . top_comment_id = new_comment . id
2022-11-25 20:56:11 +00:00
admins = g . db . query ( User ) . filter ( User . admin_level > = PERMS [ ' NOTIFICATIONS_MODMAIL ' ] , User . id != AEVANN_ID )
2022-09-21 19:38:29 +00:00
for admin in admins . all ( ) :
2022-05-04 23:09:46 +00:00
notif = Notification ( comment_id = new_comment . id , user_id = admin . id )
g . db . add ( notif )
2022-09-12 05:23:08 +00:00
return render_template ( " contact.html " , v = v , msg = " Your message has been sent to the admins! " )
2022-05-04 23:09:46 +00:00
@app.get ( ' /archives ' )
def archivesindex ( ) :
return redirect ( " /archives/index.html " )
@app.get ( ' /archives/<path:path> ' )
def archives ( path ) :
resp = make_response ( send_from_directory ( ' /archives ' , path ) )
if request . path . endswith ( ' .css ' ) : resp . headers . add ( " Content-Type " , " text/css " )
return resp
2022-10-17 10:08:09 +00:00
def static_file ( dir : str , path : str , should_cache : bool , is_webp : bool ) - > Response :
resp = make_response ( send_from_directory ( dir , path ) )
if should_cache :
resp . headers . remove ( " Cache-Control " )
resp . headers . add ( " Cache-Control " , " public, max-age=3153600 " )
if is_webp :
resp . headers . remove ( " Content-Type " )
resp . headers . add ( " Content-Type " , " image/webp " )
return resp
2022-05-04 23:09:46 +00:00
@app.get ( ' /e/<emoji> ' )
2022-05-22 17:23:52 +00:00
@limiter.exempt
2022-05-04 23:09:46 +00:00
def emoji ( emoji ) :
if not emoji . endswith ( ' .webp ' ) : abort ( 404 )
2022-10-17 10:08:09 +00:00
return static_file ( ' assets/images/emojis ' , emoji , True , True )
2022-05-04 23:09:46 +00:00
2022-06-22 15:54:25 +00:00
@app.get ( ' /i/<path:path> ' )
2022-06-22 15:51:19 +00:00
@limiter.exempt
2022-06-22 15:54:25 +00:00
def image ( path ) :
2022-10-17 10:08:09 +00:00
is_webp = path . endswith ( ' .webp ' )
return static_file ( ' assets/images ' , path , is_webp or path . endswith ( ' .gif ' ) or path . endswith ( ' .ttf ' ) or path . endswith ( ' .woff2 ' ) , is_webp )
2022-06-22 15:51:19 +00:00
2022-05-04 23:09:46 +00:00
@app.get ( ' /assets/<path:path> ' )
@app.get ( ' /static/assets/<path:path> ' )
2022-05-22 17:23:52 +00:00
@limiter.exempt
2022-05-04 23:09:46 +00:00
def static_service ( path ) :
2022-11-19 22:04:08 +00:00
if path . startswith ( f ' app_ { SITE_NAME } _v ' ) :
return redirect ( ' /app ' )
2022-10-17 10:08:09 +00:00
is_webp = path . endswith ( ' .webp ' )
return static_file ( ' assets ' , path , is_webp or path . endswith ( ' .gif ' ) or path . endswith ( ' .ttf ' ) or path . endswith ( ' .woff2 ' ) , is_webp )
2022-05-04 23:09:46 +00:00
2022-10-04 19:35:02 +00:00
### BEGIN FALLBACK ASSET SERVING
# In production, we have nginx serve these locations now.
# These routes stay for local testing. Requests don't reach them on prod.
@app.get ( ' /images/<path> ' )
@app.get ( ' /hostedimages/<path> ' )
@app.get ( " /static/images/<path> " )
@limiter.exempt
def images ( path ) :
2022-10-17 10:08:09 +00:00
return static_file ( ' /images ' , path , True , True )
2022-10-04 19:35:02 +00:00
@app.get ( ' /videos/<path> ' )
@limiter.exempt
def videos ( path ) :
2022-10-17 10:08:09 +00:00
return static_file ( ' /videos ' , path , True , False )
2022-10-04 19:35:02 +00:00
@app.get ( ' /audio/<path> ' )
@limiter.exempt
def audio ( path ) :
2022-10-17 10:08:09 +00:00
return static_file ( ' /audio ' , path , True , False )
2022-10-04 19:35:02 +00:00
### END FALLBACK ASSET SERVING
2022-05-04 23:09:46 +00:00
@app.get ( " /robots.txt " )
def robots_txt ( ) :
2022-09-11 01:56:47 +00:00
return send_file ( " assets/robots.txt " )
2022-05-04 23:09:46 +00:00
no = ( 21 , 22 , 23 , 24 , 25 , 26 , 27 )
@cache.memoize ( timeout = 3600 )
def badge_list ( site ) :
badges = g . db . query ( BadgeDef ) . filter ( BadgeDef . id . notin_ ( no ) ) . order_by ( BadgeDef . id ) . all ( )
counts_raw = g . db . query ( Badge . badge_id , func . count ( ) ) . group_by ( Badge . badge_id ) . all ( )
2022-05-09 11:21:49 +00:00
users = g . db . query ( User ) . count ( )
2022-05-04 23:09:46 +00:00
counts = { }
for c in counts_raw :
counts [ c [ 0 ] ] = ( c [ 1 ] , float ( c [ 1 ] ) * 100 / max ( users , 1 ) )
return badges , counts
@app.get ( " /badges " )
2022-10-11 07:29:24 +00:00
@feature_required ( ' BADGES ' )
2022-11-14 15:11:05 +00:00
@auth_required
2022-11-26 21:00:03 +00:00
def badges ( v : User ) :
2022-05-04 23:09:46 +00:00
badges , counts = badge_list ( SITE )
return render_template ( " badges.html " , v = v , badges = badges , counts = counts )
@app.get ( " /blocks " )
2022-07-18 07:17:45 +00:00
@admin_level_required ( PERMS [ ' USER_BLOCKS_VISIBLE ' ] )
2022-05-04 23:09:46 +00:00
def blocks ( v ) :
blocks = g . db . query ( UserBlock ) . all ( )
users = [ ]
targets = [ ]
for x in blocks :
2022-06-13 03:03:36 +00:00
acc_user = get_account ( x . user_id )
2022-09-04 23:15:37 +00:00
acc_tgt = get_account ( x . target_id )
2022-06-13 03:03:36 +00:00
if acc_user . shadowbanned or acc_tgt . shadowbanned : continue
users . append ( acc_user )
targets . append ( acc_tgt )
2022-05-04 23:09:46 +00:00
return render_template ( " blocks.html " , v = v , users = users , targets = targets )
@app.get ( " /banned " )
@auth_required
2022-11-26 21:00:03 +00:00
def banned ( v : User ) :
2022-11-28 01:32:01 +00:00
after_30_days = int ( time . time ( ) ) + 86400 * 30
users = g . db . query ( User ) . filter ( User . is_banned > 0 , or_ ( User . unban_utc == 0 , User . unban_utc > after_30_days ) )
2022-11-01 20:59:18 +00:00
if not v . can_see_shadowbanned :
users = users . filter ( User . shadowbanned == None )
users = users . all ( )
2022-05-04 23:09:46 +00:00
return render_template ( " banned.html " , v = v , users = users )
@app.get ( " /formatting " )
@auth_required
2022-11-26 21:00:03 +00:00
def formatting ( v : User ) :
2022-05-04 23:09:46 +00:00
return render_template ( " formatting.html " , v = v )
2022-11-19 22:04:08 +00:00
@app.get ( " /app " )
@auth_desired
2022-11-26 21:00:03 +00:00
def mobile_app ( v : Optional [ User ] ) :
2022-11-19 22:04:08 +00:00
return render_template ( " app.html " , v = v )
2022-06-10 22:14:03 +00:00
@app.get ( " /service-worker.js " )
2022-05-04 23:09:46 +00:00
def serviceworker ( ) :
2022-06-10 22:14:03 +00:00
with open ( " files/assets/js/service-worker.js " , " r " , encoding = " utf-8 " ) as f :
return Response ( f . read ( ) , mimetype = ' application/javascript ' )
2022-05-27 19:23:01 +00:00
2022-05-04 23:09:46 +00:00
@app.post ( " /dismiss_mobile_tip " )
def dismiss_mobile_tip ( ) :
session [ " tooltip_last_dismissed " ] = int ( time . time ( ) )
return " " , 204
2022-07-13 15:20:10 +00:00
@app.get ( " /transfers/<id> " )
@auth_required
def transfers_id ( id , v ) :
try : id = int ( id )
except : abort ( 404 )
transfer = g . db . get ( Comment , id )
if not transfer : abort ( 404 )
return render_template ( " transfers.html " , v = v , page = 1 , comments = [ transfer ] , standalone = True , next_exists = False )
@app.get ( " /transfers " )
@auth_required
2022-11-26 21:00:03 +00:00
def transfers ( v : User ) :
2022-07-13 15:20:10 +00:00
comments = g . db . query ( Comment ) . filter ( Comment . author_id == AUTOJANNY_ID , Comment . parent_submission == None , Comment . body_html . like ( " % </a> has transferred % " ) ) . order_by ( Comment . id . desc ( ) )
try : page = max ( int ( request . values . get ( " page " , 1 ) ) , 1 )
except : page = 1
2022-10-29 03:20:48 +00:00
comments = comments . offset ( PAGE_SIZE * ( page - 1 ) ) . limit ( PAGE_SIZE + 1 ) . all ( )
next_exists = len ( comments ) > PAGE_SIZE
comments = comments [ : PAGE_SIZE ]
2022-09-10 00:30:25 +00:00
2022-10-15 09:11:36 +00:00
if v . client :
2022-11-15 09:28:39 +00:00
return { " data " : [ x . json ( g . db ) for x in comments ] }
2022-09-10 00:30:25 +00:00
else :
return render_template ( " transfers.html " , v = v , page = page , comments = comments , standalone = True , next_exists = next_exists )
2022-07-18 08:39:21 +00:00
2022-09-19 23:59:24 +00:00
if not os . path . exists ( f ' files/templates/donate_ { SITE_NAME } .html ' ) :
2022-11-17 16:00:49 +00:00
copyfile ( ' files/templates/donate_rDrama.html ' , f ' files/templates/donate_ { SITE_NAME } .html ' )
2022-09-19 23:59:24 +00:00
@app.get ( ' /donate ' )
2022-10-30 18:43:06 +00:00
@auth_desired_with_logingate
2022-09-19 23:59:24 +00:00
def donate ( v ) :
2022-09-22 02:17:02 +00:00
return render_template ( f ' donate_ { SITE_NAME } .html ' , v = v )