2021-10-15 14:08:27 +00:00
from files . mail import *
from files . __main__ import app , limiter , mail
from files . helpers . alerts import *
2021-11-21 17:40:47 +00:00
from files . helpers . const import BADGES
2021-10-15 14:08:27 +00:00
from files . classes . award import AWARDS
from sqlalchemy import func
from os import path
import calendar
import matplotlib . pyplot as plt
2021-11-23 22:54:48 +00:00
from files . classes . mod_logs import ACTIONTYPES , ACTIONTYPES2
2021-10-15 14:08:27 +00:00
site = environ . get ( " DOMAIN " ) . strip ( )
site_name = environ . get ( " SITE_NAME " ) . strip ( )
@app.get ( ' /rules ' )
@auth_desired
def static_rules ( v ) :
2021-11-29 23:35:43 +00:00
if not path . exists ( f ' ./rules_ { site_name } .html ' ) :
2021-11-15 22:13:29 +00:00
if v and v . admin_level > 1 :
2021-10-15 14:08:27 +00:00
return render_template ( ' norules.html ' , v = v )
else :
abort ( 404 )
2021-11-29 23:35:43 +00:00
with open ( f ' ./rules_ { site_name } .html ' , ' r ' ) as f :
2021-10-15 14:08:27 +00:00
rules = f . read ( )
return render_template ( ' rules.html ' , rules = rules , v = v )
@app.get ( " /stats " )
@auth_required
def participation_stats ( v ) :
now = int ( time . time ( ) )
day = now - 86400
2021-11-06 15:52:48 +00:00
data = { " valid_users " : g . db . query ( User . id ) . count ( ) ,
" private_users " : g . db . query ( User . id ) . filter_by ( is_private = True ) . count ( ) ,
" banned_users " : g . db . query ( User . id ) . filter ( User . is_banned > 0 ) . count ( ) ,
" verified_email_users " : g . db . query ( User . id ) . filter_by ( is_activated = True ) . count ( ) ,
" total_coins " : g . db . query ( func . sum ( User . coins ) ) . scalar ( ) ,
" signups_last_24h " : g . db . query ( User . id ) . filter ( User . created_utc > day ) . count ( ) ,
" total_posts " : g . db . query ( Submission . id ) . count ( ) ,
" posting_users " : g . db . query ( Submission . author_id ) . distinct ( ) . count ( ) ,
" listed_posts " : g . db . query ( Submission . id ) . filter_by ( is_banned = False ) . filter ( Submission . deleted_utc == 0 ) . count ( ) ,
" removed_posts " : g . db . query ( Submission . id ) . filter_by ( is_banned = True ) . count ( ) ,
" deleted_posts " : g . db . query ( Submission . id ) . filter ( Submission . deleted_utc > 0 ) . count ( ) ,
" posts_last_24h " : g . db . query ( Submission . id ) . filter ( Submission . created_utc > day ) . count ( ) ,
" total_comments " : g . db . query ( Comment . id ) . count ( ) ,
" commenting_users " : g . db . query ( Comment . author_id ) . distinct ( ) . count ( ) ,
" removed_comments " : g . db . query ( Comment . id ) . filter_by ( is_banned = True ) . count ( ) ,
" deleted_comments " : g . db . query ( Comment . id ) . filter ( Comment . deleted_utc > 0 ) . count ( ) ,
" comments_last_24h " : g . db . query ( Comment . id ) . filter ( Comment . created_utc > day ) . count ( ) ,
" post_votes " : g . db . query ( Vote . id ) . count ( ) ,
" post_voting_users " : g . db . query ( Vote . user_id ) . distinct ( ) . count ( ) ,
" comment_votes " : g . db . query ( CommentVote . id ) . count ( ) ,
" comment_voting_users " : g . db . query ( CommentVote . user_id ) . distinct ( ) . count ( ) ,
" total_awards " : g . db . query ( AwardRelationship . id ) . count ( ) ,
" awards_given " : g . db . query ( AwardRelationship . id ) . filter ( or_ ( AwardRelationship . submission_id != None , AwardRelationship . comment_id != None ) ) . count ( )
2021-10-15 14:08:27 +00:00
}
return render_template ( " admin/content_stats.html " , v = v , title = " Content Statistics " , data = data )
@app.get ( " /chart " )
@auth_required
def chart ( v ) :
file = cached_chart ( )
return send_file ( f " ../ { file } " )
@cache.memoize ( timeout = 86400 )
def cached_chart ( ) :
days = int ( request . values . get ( " days " , 25 ) )
now = time . gmtime ( )
midnight_this_morning = time . struct_time ( ( now . tm_year ,
now . tm_mon ,
now . tm_mday ,
0 ,
0 ,
0 ,
now . tm_wday ,
now . tm_yday ,
0 )
)
today_cutoff = calendar . timegm ( midnight_this_morning )
2021-11-18 16:59:05 +00:00
day = 3600 * 200
2021-10-15 14:08:27 +00:00
day_cutoffs = [ today_cutoff - day * i for i in range ( days ) ]
day_cutoffs . insert ( 0 , calendar . timegm ( now ) )
2021-11-18 16:59:05 +00:00
daily_times = [ time . strftime ( " %d / % m " , time . gmtime ( day_cutoffs [ i + 1 ] ) ) for i in range ( len ( day_cutoffs ) - 1 ) ] [ 2 : ] [ : : - 1 ]
2021-10-15 14:08:27 +00:00
2021-11-06 15:52:48 +00:00
daily_signups = [ g . db . query ( User . id ) . filter ( User . created_utc < day_cutoffs [ i ] , User . created_utc > day_cutoffs [ i + 1 ] ) . count ( ) for i in range ( len ( day_cutoffs ) - 1 ) ] [ 2 : ] [ : : - 1 ]
2021-10-15 14:08:27 +00:00
2021-11-06 15:52:48 +00:00
post_stats = [ g . db . query ( Submission . id ) . filter ( Submission . created_utc < day_cutoffs [ i ] , Submission . created_utc > day_cutoffs [ i + 1 ] , Submission . is_banned == False ) . count ( ) for i in range ( len ( day_cutoffs ) - 1 ) ] [ 2 : ] [ : : - 1 ]
2021-10-15 14:08:27 +00:00
2021-11-06 15:52:48 +00:00
comment_stats = [ g . db . query ( Comment . id ) . filter ( Comment . created_utc < day_cutoffs [ i ] , Comment . created_utc > day_cutoffs [ i + 1 ] , Comment . is_banned == False , Comment . author_id != 1 ) . count ( ) for i in range ( len ( day_cutoffs ) - 1 ) ] [ 2 : ] [ : : - 1 ]
2021-10-15 14:08:27 +00:00
2021-11-18 16:59:05 +00:00
plt . rcParams [ " figure.figsize " ] = ( 20 , 20 )
2021-10-15 14:08:27 +00:00
signup_chart = plt . subplot2grid ( ( 20 , 4 ) , ( 0 , 0 ) , rowspan = 5 , colspan = 4 )
posts_chart = plt . subplot2grid ( ( 20 , 4 ) , ( 7 , 0 ) , rowspan = 5 , colspan = 4 )
comments_chart = plt . subplot2grid ( ( 20 , 4 ) , ( 14 , 0 ) , rowspan = 5 , colspan = 4 )
signup_chart . grid ( ) , posts_chart . grid ( ) , comments_chart . grid ( )
signup_chart . plot (
daily_times ,
daily_signups ,
color = ' red ' )
posts_chart . plot (
daily_times ,
post_stats ,
color = ' green ' )
comments_chart . plot (
daily_times ,
comment_stats ,
color = ' gold ' )
signup_chart . set_ylabel ( " Signups " )
posts_chart . set_ylabel ( " Posts " )
comments_chart . set_ylabel ( " Comments " )
comments_chart . set_xlabel ( " Time (UTC) " )
signup_chart . legend ( loc = ' upper left ' , frameon = True )
posts_chart . legend ( loc = ' upper left ' , frameon = True )
comments_chart . legend ( loc = ' upper left ' , frameon = True )
file = " chart.png "
plt . savefig ( file )
plt . clf ( )
return file
@app.get ( " /patrons " )
@app.get ( " /paypigs " )
2021-11-21 18:54:36 +00:00
@admin_level_required ( 3 )
2021-10-15 14:08:27 +00:00
def patrons ( v ) :
2021-11-24 12:29:31 +00:00
users = g . db . query ( User ) . filter ( User . patron > 0 ) . order_by ( User . patron . desc ( ) , User . id ) . all ( )
2021-11-24 12:20:52 +00:00
return render_template ( " patrons.html " , v = v , users = users )
2021-10-15 14:08:27 +00:00
@app.get ( " /admins " )
2021-10-19 15:42:58 +00:00
@app.get ( " /badmins " )
2021-10-15 14:08:27 +00:00
@auth_desired
def admins ( v ) :
2021-11-30 14:38:09 +00:00
admins = g . db . query ( User ) . filter ( User . admin_level > 0 ) . order_by ( User . truecoins . desc ( ) ) . all ( )
2021-10-15 14:08:27 +00:00
return render_template ( " admins.html " , v = v , admins = admins )
@app.get ( " /log " )
2021-10-19 15:42:58 +00:00
@app.get ( " /modlog " )
2021-10-15 14:08:27 +00:00
@auth_desired
def log ( v ) :
2021-11-22 23:25:33 +00:00
page = int ( request . args . get ( " page " , 1 ) )
admin = request . args . get ( " admin " )
2021-11-23 00:04:13 +00:00
if admin : admin_id = get_id ( admin )
else : admin_id = 0
2021-10-15 14:08:27 +00:00
2021-11-22 23:25:33 +00:00
kind = request . args . get ( " kind " )
2021-10-15 14:08:27 +00:00
2021-11-23 21:58:46 +00:00
if v and v . admin_level > 1 : types = ACTIONTYPES
else : types = ACTIONTYPES2
if kind not in types : kind = None
2021-11-22 23:25:33 +00:00
2021-11-23 21:58:46 +00:00
actions = g . db . query ( ModAction )
2021-11-23 21:54:07 +00:00
if not ( v and v . admin_level > 1 ) :
2021-11-26 19:28:55 +00:00
actions = actions . filter ( ModAction . kind . notin_ ( [ " shadowban " , " unshadowban " , " check " ] ) )
2021-11-23 21:58:46 +00:00
if admin_id : actions = actions . filter_by ( user_id = admin_id )
2021-11-23 23:01:50 +00:00
if kind : actions = actions . filter_by ( kind = kind )
2021-11-23 21:54:07 +00:00
2021-11-22 23:25:33 +00:00
actions = actions . order_by ( ModAction . id . desc ( ) ) . offset ( 25 * ( page - 1 ) ) . limit ( 26 ) . all ( )
2021-10-15 14:08:27 +00:00
next_exists = len ( actions ) > 25
actions = actions [ : 25 ]
2021-11-23 00:04:13 +00:00
admins = [ x [ 0 ] for x in g . db . query ( User . username ) . filter ( User . admin_level > 1 ) . all ( ) ]
2021-11-23 00:09:11 +00:00
2021-11-23 21:58:46 +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 )
2021-10-15 14:08:27 +00:00
@app.get ( " /log/<id> " )
@auth_desired
def log_item ( id , v ) :
try : id = int ( id )
except :
try : id = int ( id , 36 )
except : abort ( 404 )
2021-11-06 15:52:48 +00:00
action = g . db . query ( ModAction ) . filter_by ( id = id ) . first ( )
2021-10-15 14:08:27 +00:00
if not action :
abort ( 404 )
if request . path != action . permalink :
return redirect ( action . permalink )
2021-11-23 21:54:07 +00:00
admins = [ x [ 0 ] for x in g . db . query ( User . username ) . filter ( User . admin_level > 1 ) . all ( ) ]
2021-11-23 21:58:46 +00:00
if v and v . admin_level > 1 : types = ACTIONTYPES
else : types = ACTIONTYPES2
return render_template ( " log.html " , v = v , actions = [ action ] , next_exists = False , page = 1 , action = action , admins = admins , types = types )
2021-10-15 14:08:27 +00:00
@app.get ( " /assets/favicon.ico " )
def favicon ( ) :
2021-12-12 20:14:30 +00:00
return send_file ( f " ./assets/images/ { site_name } /icon.webp?v=200 " )
2021-10-15 14:08:27 +00:00
@app.get ( " /api " )
@auth_desired
def api ( v ) :
return render_template ( " api.html " , v = v )
@app.get ( " /contact " )
2021-10-19 15:42:58 +00:00
@app.get ( " /press " )
@app.get ( " /media " )
2021-10-15 14:08:27 +00:00
@auth_required
def contact ( v ) :
return render_template ( " contact.html " , v = v )
@app.post ( " /contact " )
@limiter.limit ( " 1/second " )
@auth_required
2021-12-09 21:21:52 +00:00
@validate_formkey
2021-10-15 14:08:27 +00:00
def submit_contact ( v ) :
2021-12-05 19:45:08 +00:00
message = f ' This message has been sent automatically to all admins via https:// { site } /contact, user email is " { v . email } " \n \n Message: \n \n ' + request . values . get ( " message " , " " )
2021-10-15 14:08:27 +00:00
send_admin ( v . id , message )
g . db . commit ( )
return render_template ( " contact.html " , v = v , msg = " Your message has been sent. " )
@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
@app.get ( ' /assets/<path:path> ' )
@limiter.exempt
def static_service ( path ) :
resp = make_response ( send_from_directory ( ' ./assets ' , path ) )
if request . path . endswith ( ' .webp ' ) or request . path . endswith ( ' .gif ' ) or request . path . endswith ( ' .ttf ' ) or request . path . endswith ( ' .woff ' ) or request . path . endswith ( ' .woff2 ' ) :
resp . headers . remove ( " Cache-Control " )
resp . headers . add ( " Cache-Control " , " public, max-age=2628000 " )
2021-11-11 18:58:09 +00:00
if request . path . endswith ( ' .webp ' ) :
resp . headers . remove ( " Content-Type " )
resp . headers . add ( " Content-Type " , " image/webp " )
2021-10-15 14:08:27 +00:00
return resp
@app.get ( ' /images/<path:path> ' )
@app.get ( ' /hostedimages/<path:path> ' )
@limiter.exempt
def images ( path ) :
2021-12-11 17:48:06 +00:00
resp = make_response ( send_from_directory ( ' ../images ' , path . replace ( ' .WEBP ' , ' .webp ' ) ) )
2021-10-15 14:08:27 +00:00
resp . headers . remove ( " Cache-Control " )
resp . headers . add ( " Cache-Control " , " public, max-age=2628000 " )
2021-11-11 18:58:09 +00:00
if request . path . endswith ( ' .webp ' ) :
resp . headers . remove ( " Content-Type " )
resp . headers . add ( " Content-Type " , " image/webp " )
2021-10-15 14:08:27 +00:00
return resp
@app.get ( " /robots.txt " )
def robots_txt ( ) :
return send_file ( " ./assets/robots.txt " )
@app.get ( " /settings " )
@auth_required
def settings ( v ) :
return redirect ( " /settings/profile " )
@app.get ( " /settings/profile " )
@auth_required
def settings_profile ( v ) :
return render_template ( " settings_profile.html " ,
v = v )
@app.get ( " /badges " )
@auth_desired
def badges ( v ) :
2021-11-21 17:40:47 +00:00
return render_template ( " badges.html " , v = v , badges = BADGES )
2021-10-15 14:08:27 +00:00
@app.get ( " /blocks " )
@auth_desired
def blocks ( v ) :
2021-11-06 15:52:48 +00:00
blocks = g . db . query ( UserBlock ) . all ( )
2021-10-15 14:08:27 +00:00
users = [ ]
targets = [ ]
for x in blocks :
users . append ( get_account ( x . user_id ) )
targets . append ( get_account ( x . target_id ) )
return render_template ( " blocks.html " , v = v , users = users , targets = targets )
@app.get ( " /banned " )
@auth_desired
def banned ( v ) :
2021-11-06 15:52:48 +00:00
users = [ x for x in g . db . query ( User ) . filter ( User . is_banned > 0 , User . unban_utc == 0 ) . all ( ) ]
2021-10-15 14:08:27 +00:00
return render_template ( " banned.html " , v = v , users = users )
@app.get ( " /formatting " )
@auth_desired
def formatting ( v ) :
return render_template ( " formatting.html " , v = v )
@app.get ( " /service-worker.js " )
def serviceworker ( ) :
with open ( " files/assets/js/service-worker.js " , " r " ) as f : return Response ( f . read ( ) , mimetype = ' application/javascript ' )
@app.get ( " /settings/security " )
@auth_required
def settings_security ( v ) :
return render_template ( " settings_security.html " ,
v = v ,
mfa_secret = pyotp . random_base32 ( ) if not v . mfa_secret else None ,
error = request . values . get ( " error " ) or None ,
msg = request . values . get ( " msg " ) or None
)
@app.post ( " /dismiss_mobile_tip " )
@limiter.limit ( " 1/second " )
def dismiss_mobile_tip ( ) :
session [ " tooltip_last_dismissed " ] = int ( time . time ( ) )
session . modified = True
return " " , 204