2022-05-04 23:09:46 +00:00
from __future__ import unicode_literals
2022-11-15 09:19:08 +00:00
import os
from shutil import copyfile
import pyotp
import requests
2023-08-05 01:48:47 +00:00
import yt_dlp
2022-11-15 09:19:08 +00:00
2023-07-26 17:27:30 +00:00
from sqlalchemy . orm import load_only
2022-11-15 09:19:08 +00:00
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-06-24 13:19:53 +00:00
from files . helpers . get import *
2022-11-15 09:19:08 +00:00
from files . helpers . mail import *
2023-03-06 19:32:08 +00:00
from files . helpers . media import *
2022-11-15 09:19:08 +00:00
from files . helpers . regex import *
from files . helpers . sanitize import *
from files . helpers . sanitize import filter_emojis_only
2022-10-06 22:59:50 +00:00
from files . helpers . security import *
2022-11-15 09:19:08 +00:00
from files . helpers . useractions import *
from files . routes . wrappers import *
2022-05-04 23:09:46 +00:00
from . front import frontlist
2022-11-15 09:19:08 +00:00
from files . __main__ import app , cache , limiter
2022-11-06 07:02:15 +00:00
@app.get ( " /settings " )
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-11-06 07:02:15 +00:00
@auth_required
2023-07-30 00:42:06 +00:00
def settings ( v ) :
2022-11-06 07:02:15 +00:00
return redirect ( " /settings/personal " )
@app.get ( " /settings/personal " )
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-11-06 07:02:15 +00:00
@auth_required
2023-07-30 00:42:06 +00:00
def settings_personal ( v ) :
2023-08-11 21:50:23 +00:00
return render_template ( " settings/personal.html " , v = v , error = get_error ( ) )
2022-05-04 23:09:46 +00:00
2023-08-12 14:56:39 +00:00
@app.post ( ' /settings/remove_background ' )
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-11-06 12:08:39 +00:00
@auth_required
2022-11-06 12:04:00 +00:00
def remove_background ( v ) :
2022-12-03 22:17:24 +00:00
if v . background :
2022-12-05 00:02:29 +00:00
if v . background . startswith ( ' /images/ ' ) :
2023-03-17 10:25:49 +00:00
remove_media_using_link ( v . background )
2022-11-06 12:04:00 +00:00
v . background = None
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
return { " message " : " Background removed! " }
2022-12-05 00:02:29 +00:00
@app.post ( ' /settings/custom_background ' )
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-05 00:02:29 +00:00
@auth_required
def upload_custom_background ( v ) :
2023-01-27 11:57:29 +00:00
if g . is_tor : abort ( 403 , " Image uploads are not allowed through TOR! " )
2022-12-05 00:02:29 +00:00
if not v . patron :
2023-07-02 19:57:08 +00:00
abort ( 403 , f " Custom site backgrounds are only available to { patron } s! " )
2022-12-05 00:02:29 +00:00
file = request . files [ " file " ]
name = f ' /images/ { time . time ( ) } ' . replace ( ' . ' , ' ' ) + ' .webp '
file . save ( name )
background = process_image ( name , v )
if background :
if v . background and v . background . startswith ( ' /images/ ' ) :
2023-03-17 10:25:49 +00:00
remove_media_using_link ( v . background )
2022-12-05 00:02:29 +00:00
v . background = background
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-12-05 00:02:29 +00:00
return redirect ( ' /settings/personal ' )
2022-11-06 12:30:07 +00:00
@app.post ( " /settings/personal " )
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-11-06 22:55:31 +00:00
def settings_personal_post ( v ) :
2022-12-27 05:31:28 +00:00
if v . id == 253 and request . values . get ( " private " ) :
abort ( 403 )
2022-05-04 23:09:46 +00:00
updated = False
2022-11-07 03:28:27 +00:00
# begin common selectors #
2023-01-01 11:36:20 +00:00
2023-07-30 00:42:06 +00:00
def update_flag ( column_name , request_name ) :
2022-11-07 02:03:09 +00:00
if not request . values . get ( request_name , ' ' ) : return False
2022-11-06 12:04:00 +00:00
request_flag = request . values . get ( request_name , ' ' ) == ' true '
2022-11-07 00:40:51 +00:00
if request_flag != getattr ( v , column_name ) :
2022-11-06 12:04:00 +00:00
setattr ( v , column_name , request_flag )
return True
return False
2023-01-01 11:36:20 +00:00
2023-07-30 00:42:06 +00:00
def update_potentially_permanent_flag ( column_name , request_name , friendly_name , badge_id ) :
2022-11-06 12:33:25 +00:00
if not request . values . get ( request_name ) : return False
2022-11-06 12:04:00 +00:00
current_value = getattr ( v , column_name )
if FEATURES [ ' USERS_PERMANENT_WORD_FILTERS ' ] and current_value > 1 :
2022-11-07 00:03:43 +00:00
abort ( 403 , f " Cannot change the { friendly_name } setting after you ' ve already set it permanently! " )
2022-11-06 22:55:31 +00:00
request_flag = int ( request . values . get ( request_name , ' ' ) == ' true ' )
2022-11-06 12:04:00 +00:00
if current_value and request_flag and request . values . get ( " permanent " , ' ' ) == ' true ' and request . values . get ( " username " ) == v . username :
2022-11-07 02:11:59 +00:00
if v . client : abort ( 403 , f " Cannot set { friendly_name } permanently from the API " )
2022-11-06 12:04:00 +00:00
request_flag = int ( time . time ( ) )
2022-11-06 22:55:31 +00:00
setattr ( v , column_name , request_flag )
2022-11-06 12:04:00 +00:00
if badge_id : badge_grant ( v , badge_id )
2023-08-11 21:50:23 +00:00
return { " message " : f " You have set the { friendly_name } permanently! Enjoy your new badge! " }
2022-11-06 12:04:00 +00:00
elif current_value != request_flag :
setattr ( v , column_name , request_flag )
return True
return False
2023-07-30 00:42:06 +00:00
def set_selector_option ( column_name , api_name , valid_values , error_msg = " value " ) :
2022-11-07 03:28:27 +00:00
opt = request . values . get ( api_name )
if opt : opt = opt . strip ( )
if not opt : return False
2023-09-05 14:32:36 +00:00
if opt in valid_values . keys ( ) :
2022-11-07 03:28:27 +00:00
setattr ( v , column_name , opt )
return True
abort ( 400 , f " ' { opt } ' is not a valid { error_msg } " )
# end common selectors #
background = request . values . get ( " background " , v . background )
2022-11-07 03:50:38 +00:00
if background != v . background and background . endswith ( " .webp " ) and len ( background ) < = 20 :
2022-12-05 00:02:29 +00:00
v . background = ' /i/backgrounds/ ' + request . values . get ( " background " )
2022-11-07 03:28:27 +00:00
updated = True
2022-05-04 23:09:46 +00:00
elif request . values . get ( " reddit " , v . reddit ) != v . reddit :
reddit = request . values . get ( " reddit " )
2023-09-14 00:09:19 +00:00
if reddit in { ' old.reddit.com ' , ' reddit.com ' , ' reddit.lol ' , ' libreddit.hu ' , ' undelete.pullpush.io ' } :
2022-05-04 23:09:46 +00:00
updated = True
v . reddit = reddit
2023-06-27 12:01:40 +00:00
elif request . values . get ( " poor " , v . poor ) != v . poor :
updated = True
2023-08-03 20:26:53 +00:00
session [ ' poor ' ] = request . values . get ( " poor " , v . poor ) == ' true '
2023-01-01 11:36:20 +00:00
2022-11-07 02:11:59 +00:00
slur_filter_updated = updated or update_potentially_permanent_flag ( " slurreplacer " , " slurreplacer " , " slur replacer " , 192 )
2022-11-06 12:04:00 +00:00
if isinstance ( slur_filter_updated , bool ) :
updated = slur_filter_updated
else :
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-11-06 12:04:00 +00:00
return slur_filter_updated
2023-01-01 11:36:20 +00:00
2022-11-07 02:11:59 +00:00
profanity_filter_updated = updated or update_potentially_permanent_flag ( " profanityreplacer " , " profanityreplacer " , " profanity replacer " , 190 )
2022-11-06 12:04:00 +00:00
if isinstance ( profanity_filter_updated , bool ) :
updated = profanity_filter_updated
else :
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-11-06 12:04:00 +00:00
return profanity_filter_updated
2023-03-15 06:03:11 +00:00
updated = updated or update_flag ( " hidevotedon " , " hidevotedon " )
2022-11-21 16:55:09 +00:00
updated = updated or update_flag ( " newtab " , " newtab " )
2022-11-06 12:04:00 +00:00
updated = updated or update_flag ( " newtabexternal " , " newtabexternal " )
updated = updated or update_flag ( " nitter " , " nitter " )
updated = updated or update_flag ( " imginn " , " imginn " )
updated = updated or update_flag ( " controversial " , " controversial " )
updated = updated or update_flag ( " sigs_disabled " , " sigs_disabled " )
updated = updated or update_flag ( " is_private " , " private " )
2023-08-03 04:57:09 +00:00
updated = updated or update_flag ( " lifetimedonated_visible " , " lifetimedonated_visible " )
2022-11-06 22:55:31 +00:00
2022-11-06 12:04:00 +00:00
if not updated and request . values . get ( " spider " , v . spider ) != v . spider and v . spider < = 1 :
2022-09-25 02:44:19 +00:00
updated = True
v . spider = int ( request . values . get ( " spider " ) == ' true ' )
if v . spider : badge_grant ( user = v , badge_id = 179 )
2023-01-01 11:36:20 +00:00
else :
2022-09-25 20:17:11 +00:00
badge = v . has_badge ( 179 )
2023-09-07 15:26:31 +00:00
if badge :
2023-07-02 20:07:31 +00:00
g . db . delete ( badge )
2023-08-03 20:40:15 +00:00
elif not updated and request . values . get ( " cursormarsey " , v . cursormarsey ) != v . cursormarsey :
updated = True
session [ " cursormarsey " ] = int ( request . values . get ( " cursormarsey " ) == ' true ' )
2023-08-03 21:17:55 +00:00
elif not updated and request . values . get ( " over_18 " , v . over_18 ) != v . over_18 :
updated = True
session [ " over_18 " ] = int ( request . values . get ( " over_18 " ) == ' true ' )
2023-07-02 20:07:31 +00:00
elif not updated and request . values . get ( " marsify " , v . marsify ) != v . marsify and v . marsify < = 1 :
if not v . patron :
abort ( 403 , f " Perma-marsify is only available to { patron } s! " )
updated = True
v . marsify = int ( request . values . get ( " marsify " ) == ' true ' )
if v . marsify : badge_grant ( user = v , badge_id = 170 )
else :
badge = v . has_badge ( 170 )
2023-03-16 06:27:58 +00:00
if badge : g . db . delete ( badge )
2023-01-01 05:33:09 +00:00
2023-03-08 06:51:40 +00:00
elif IS_FISTMAS ( ) and not updated and request . values . get ( " event_music " , v . event_music ) != v . event_music and v . can_toggle_event_music :
2022-12-20 01:38:19 +00:00
updated = True
v . event_music = not v . event_music
2023-01-01 10:41:27 +00:00
2022-12-03 02:14:01 +00:00
elif not updated and request . values . get ( " bio " ) == " " and not request . files . get ( ' file ' ) :
2022-05-04 23:09:46 +00:00
v . bio = None
v . bio_html = None
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your bio has been updated. " }
2022-05-04 23:09:46 +00:00
2022-11-06 12:04:00 +00:00
elif not updated and request . values . get ( " sig " ) == " " :
2022-05-04 23:09:46 +00:00
v . sig = None
v . sig_html = None
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your sig has been updated. " }
2022-05-04 23:09:46 +00:00
2022-11-06 12:04:00 +00:00
elif not updated and request . values . get ( " friends " ) == " " :
2022-05-04 23:09:46 +00:00
v . friends = None
v . friends_html = None
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your friends list has been updated. " }
2022-05-04 23:09:46 +00:00
2022-11-06 12:04:00 +00:00
elif not updated and request . values . get ( " enemies " ) == " " :
2022-05-04 23:09:46 +00:00
v . enemies = None
v . enemies_html = None
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your enemies list has been updated. " }
2022-05-04 23:09:46 +00:00
2023-07-02 19:57:08 +00:00
elif not updated and request . values . get ( " sig " ) :
if not v . patron :
abort ( 403 , f " Signatures are only available to { patron } s! " )
2022-07-17 17:24:58 +00:00
sig = request . values . get ( " sig " ) [ : 200 ] . replace ( ' \n ' , ' ' ) . replace ( ' \r ' , ' ' )
2023-08-16 23:46:42 +00:00
sig = process_files ( request . files , v , sig )
sig = sig [ : 200 ] . strip ( ) # process_files potentially adds characters to the post
2023-02-07 03:31:49 +00:00
sig_html = sanitize ( sig , blackjack = " signature " )
2022-05-04 23:09:46 +00:00
if len ( sig_html ) > 1000 :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Your sig is too long " )
2022-05-04 23:09:46 +00:00
2023-08-16 23:46:42 +00:00
v . sig = sig
2022-05-04 23:09:46 +00:00
v . sig_html = sig_html
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your sig has been updated. " }
2022-05-04 23:09:46 +00:00
2022-11-06 12:04:00 +00:00
elif not updated and FEATURES [ ' USERS_PROFILE_BODYTEXT ' ] and request . values . get ( " friends " ) :
2023-03-03 04:19:52 +00:00
friends = request . values . get ( " friends " ) [ : BIO_FRIENDS_ENEMIES_LENGTH_LIMIT ]
2022-05-04 23:09:46 +00:00
2023-02-07 03:31:49 +00:00
friends_html = sanitize ( friends , blackjack = " friends " )
2022-05-04 23:09:46 +00:00
2023-03-03 04:19:52 +00:00
if len ( friends_html ) > BIO_FRIENDS_ENEMIES_HTML_LENGTH_LIMIT :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Your friends list is too long " )
2022-05-04 23:09:46 +00:00
2023-03-03 04:19:52 +00:00
friends = friends [ : BIO_FRIENDS_ENEMIES_LENGTH_LIMIT ]
2023-03-02 19:56:43 +00:00
2023-07-16 11:56:24 +00:00
notify_users = NOTIFY_USERS ( friends , v , oldtext = v . friends )
2022-05-04 23:09:46 +00:00
if notify_users :
2023-02-24 02:28:10 +00:00
text = f " @ { v . username } has added you to their friends list! "
cid = notif_comment ( text )
2023-03-02 00:32:51 +00:00
if notify_users == ' everyone ' :
alert_everyone ( cid )
else :
for x in notify_users :
2023-08-16 23:04:56 +00:00
add_notif ( cid , x , text , pushnotif_url = f ' { SITE_FULL } { v . url } ' )
2022-05-04 23:09:46 +00:00
2023-03-02 19:56:43 +00:00
v . friends = friends
2022-05-04 23:09:46 +00:00
v . friends_html = friends_html
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your friends list has been updated. " }
2022-05-04 23:09:46 +00:00
2022-11-06 12:04:00 +00:00
elif not updated and FEATURES [ ' USERS_PROFILE_BODYTEXT ' ] and request . values . get ( " enemies " ) :
2023-03-03 04:19:52 +00:00
enemies = request . values . get ( " enemies " ) [ : BIO_FRIENDS_ENEMIES_LENGTH_LIMIT ]
2022-05-04 23:09:46 +00:00
2023-02-07 03:31:49 +00:00
enemies_html = sanitize ( enemies , blackjack = " enemies " )
2022-05-04 23:09:46 +00:00
2023-03-03 04:19:52 +00:00
if len ( enemies_html ) > BIO_FRIENDS_ENEMIES_HTML_LENGTH_LIMIT :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Your enemies list is too long " )
2022-05-04 23:09:46 +00:00
2023-03-03 04:19:52 +00:00
enemies = enemies [ : BIO_FRIENDS_ENEMIES_LENGTH_LIMIT ]
2023-03-02 19:56:43 +00:00
2023-07-16 11:56:24 +00:00
notify_users = NOTIFY_USERS ( enemies , v , oldtext = v . enemies )
2022-05-04 23:09:46 +00:00
if notify_users :
2023-02-24 02:28:10 +00:00
text = f " @ { v . username } has added you to their enemies list! "
cid = notif_comment ( text )
2023-03-02 00:32:51 +00:00
if notify_users == ' everyone ' :
alert_everyone ( cid )
else :
for x in notify_users :
2023-08-16 23:04:56 +00:00
add_notif ( cid , x , text , pushnotif_url = f ' { SITE_FULL } { v . url } ' )
2022-05-04 23:09:46 +00:00
2023-03-02 19:56:43 +00:00
v . enemies = enemies
2022-05-04 23:09:46 +00:00
v . enemies_html = enemies_html
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your enemies list has been updated. " }
2022-05-04 23:09:46 +00:00
2022-11-06 12:04:00 +00:00
elif not updated and FEATURES [ ' USERS_PROFILE_BODYTEXT ' ] and \
2022-07-20 01:16:59 +00:00
( request . values . get ( " bio " ) or request . files . get ( ' file ' ) ) :
2023-03-03 04:19:52 +00:00
bio = request . values . get ( " bio " ) [ : BIO_FRIENDS_ENEMIES_LENGTH_LIMIT ]
2023-02-26 12:08:37 +00:00
bio = process_files ( request . files , v , bio )
2022-05-22 10:26:59 +00:00
bio = bio . strip ( )
2023-02-07 03:31:49 +00:00
bio_html = sanitize ( bio , blackjack = " bio " )
2022-05-04 23:09:46 +00:00
2023-03-03 04:19:52 +00:00
if len ( bio_html ) > BIO_FRIENDS_ENEMIES_HTML_LENGTH_LIMIT :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Your bio is too long " )
2022-05-04 23:09:46 +00:00
2023-03-03 04:19:52 +00:00
v . bio = bio [ : BIO_FRIENDS_ENEMIES_LENGTH_LIMIT ]
2022-05-04 23:09:46 +00:00
v . bio_html = bio_html
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your bio has been updated. " }
2022-05-04 23:09:46 +00:00
frontsize = request . values . get ( " frontsize " )
if frontsize :
2022-11-06 22:55:31 +00:00
frontsize = int ( frontsize )
2022-11-06 12:04:00 +00:00
if frontsize in PAGE_SIZES :
2022-11-06 22:55:31 +00:00
v . frontsize = frontsize
2022-05-04 23:09:46 +00:00
updated = True
cache . delete_memoized ( frontlist )
else : abort ( 400 )
2023-01-01 11:36:20 +00:00
2022-11-07 03:28:27 +00:00
updated = updated or set_selector_option ( " defaultsortingcomments " , " defaultsortingcomments " , COMMENT_SORTS , " comment sort " )
2023-09-05 14:32:36 +00:00
updated = updated or set_selector_option ( " defaultsorting " , " defaultsorting " , POST_SORTS , " post sort " )
2022-11-07 03:28:27 +00:00
updated = updated or set_selector_option ( " defaulttime " , " defaulttime " , TIME_FILTERS , " time filter " )
2022-05-04 23:09:46 +00:00
theme = request . values . get ( " theme " )
2022-11-07 03:28:27 +00:00
if not updated and theme :
2022-11-06 12:04:00 +00:00
if theme in THEMES :
2022-12-14 16:59:00 +00:00
if v . theme == " win98 " : v . themecolor = DEFAULT_COLOR
2022-05-04 23:09:46 +00:00
v . theme = theme
if theme == " win98 " : v . themecolor = " 30409f "
updated = True
2022-11-06 12:04:00 +00:00
else : abort ( 400 , f " { theme } is not a valid theme " )
2022-05-04 23:09:46 +00:00
house = request . values . get ( " house " )
2022-11-07 03:28:27 +00:00
if not updated and house and house in HOUSES and FEATURES [ ' HOUSES ' ] :
2022-08-27 02:57:19 +00:00
if v . bite : abort ( 403 )
2022-11-05 05:49:12 +00:00
if v . house :
if v . house . replace ( ' Founder ' , ' ' ) == house : abort ( 409 , f " You ' re already in House { house } " )
2022-11-05 06:00:02 +00:00
cost = HOUSE_SWITCH_COST
2023-01-01 11:36:20 +00:00
else :
2022-11-05 06:00:02 +00:00
cost = HOUSE_JOIN_COST
2022-05-04 23:09:46 +00:00
2023-04-24 15:08:40 +00:00
success = v . charge_account ( ' combined ' , cost ) [ 0 ]
2022-11-01 05:25:19 +00:00
if not success : abort ( 403 )
2022-05-04 23:09:46 +00:00
2023-01-01 11:36:20 +00:00
if house == " None " : house = ' '
2023-06-26 15:08:20 +00:00
2022-05-04 23:09:46 +00:00
v . house = house
updated = True
if updated :
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-01-27 11:57:29 +00:00
return { " message " : " Your settings have been updated! " }
2022-05-04 23:09:46 +00:00
else :
2023-01-27 11:57:29 +00:00
abort ( 400 , " You didn ' t change anything! " )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/filters " )
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
2023-07-30 00:42:06 +00:00
def filters ( v ) :
2023-08-23 21:57:39 +00:00
filters = request . values . get ( " filters " ) [ : 1000 ] . strip ( )
2022-05-04 23:09:46 +00:00
if filters == v . custom_filter_list :
2023-08-11 21:50:23 +00:00
abort ( 400 , " You didn ' t change anything! " )
2022-05-04 23:09:46 +00:00
v . custom_filter_list = filters
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your custom filters have been updated! " }
2022-05-04 23:09:46 +00:00
2023-08-11 21:50:23 +00:00
def set_color ( v , attr ) :
color = request . values . get ( attr )
2022-11-07 00:03:43 +00:00
current = getattr ( v , attr )
color = color . strip ( ) . lower ( ) if color else None
if color :
if color . startswith ( ' # ' ) : color = color [ 1 : ]
if not color_regex . fullmatch ( color ) :
2022-11-15 14:33:18 +00:00
return render_template ( " settings/personal.html " , v = v , error = " Invalid color hex code! " )
2022-11-07 00:03:43 +00:00
if color and current != color :
setattr ( v , attr , color )
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-11-15 14:33:18 +00:00
return render_template ( " settings/personal.html " , v = v , msg = " Color successfully updated! " )
2022-11-07 00:03:43 +00:00
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/namecolor " )
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 namecolor ( v ) :
2023-08-11 21:50:23 +00:00
return set_color ( v , " namecolor " )
2023-01-01 11:36:20 +00:00
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/themecolor " )
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 themecolor ( v ) :
2023-08-11 21:50:23 +00:00
return set_color ( v , " themecolor " )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/titlecolor " )
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 titlecolor ( v ) :
2023-08-11 21:50:23 +00:00
return set_color ( v , " titlecolor " )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/verifiedcolor " )
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 verifiedcolor ( v ) :
2023-07-30 05:55:07 +00:00
if not v . verified : abort ( 403 , " You don ' t have a checkmark to edit its color! " )
2023-08-11 21:50:23 +00:00
return set_color ( v , " verifiedcolor " )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/security " )
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 settings_security_post ( v ) :
if request . values . get ( " new_password " ) :
if request . values . get ( " new_password " ) != request . values . get ( " cnf_password " ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Passwords do not match! " )
2022-05-04 23:09:46 +00:00
if not valid_password_regex . fullmatch ( request . values . get ( " new_password " ) ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Password must be between 8 and 100 characters! " )
2022-05-04 23:09:46 +00:00
if not v . verifyPass ( request . values . get ( " old_password " ) ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Incorrect password " )
2022-05-04 23:09:46 +00:00
2022-10-06 22:59:50 +00:00
v . passhash = hash_password ( request . values . get ( " new_password " ) )
2022-05-04 23:09:46 +00:00
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Your password has been changed successfully! " }
2022-05-04 23:09:46 +00:00
if request . values . get ( " new_email " ) :
if not v . verifyPass ( request . values . get ( ' password ' ) ) :
2023-01-27 11:57:29 +00:00
return render_template ( " settings/security.html " , v = v , error = " Invalid password! " )
2022-05-04 23:09:46 +00:00
new_email = request . values . get ( " new_email " , " " ) . strip ( ) . lower ( )
if new_email == v . email :
2022-11-09 06:11:46 +00:00
return render_template ( " settings/security.html " , v = v , error = " This email is already yours! " )
2022-05-04 23:09:46 +00:00
url = f " { SITE_FULL } /activate "
now = int ( time . time ( ) )
token = generate_hash ( f " { new_email } + { v . id } + { now } " )
params = f " ?email= { quote ( new_email ) } &id= { v . id } &time= { now } &token= { token } "
link = url + params
send_mail ( to_address = new_email ,
2022-09-04 23:15:37 +00:00
subject = " Verify your email address. " ,
html = render_template ( " email/email_change.html " ,
action_url = link ,
v = v )
)
2022-05-04 23:09:46 +00:00
2023-01-27 11:57:29 +00:00
return render_template ( " settings/security.html " , v = v , msg = " We have sent you an email, click the verification link inside it to complete the email change. Check your spam folder if you can ' t find it! " )
2022-05-04 23:09:46 +00:00
if request . values . get ( " 2fa_token " ) :
if not v . verifyPass ( request . values . get ( ' password ' ) ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Invalid password! " )
2022-05-04 23:09:46 +00:00
secret = request . values . get ( " 2fa_secret " )
x = pyotp . TOTP ( secret )
if not x . verify ( request . values . get ( " 2fa_token " ) , valid_window = 1 ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Invalid token! " )
2022-05-04 23:09:46 +00:00
v . mfa_secret = secret
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Two-factor authentication enabled! " }
2022-05-04 23:09:46 +00:00
if request . values . get ( " 2fa_remove " ) :
if not v . verifyPass ( request . values . get ( ' password ' ) ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Invalid password! " )
2022-05-04 23:09:46 +00:00
token = request . values . get ( " 2fa_remove " )
2022-12-20 21:15:24 +00:00
if not token or not v . validate_2fa ( token ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Invalid token! " )
2022-05-04 23:09:46 +00:00
v . mfa_secret = None
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Two-factor authentication disabled! " }
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/log_out_all_others " )
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 settings_log_out_others ( v ) :
submitted_password = request . values . get ( " password " , " " ) . strip ( )
if not v . verifyPass ( submitted_password ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " Incorrect password! " )
2022-05-04 23:09:46 +00:00
v . login_nonce + = 1
session [ " login_nonce " ] = v . login_nonce
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-01-27 11:33:03 +00:00
2023-08-11 21:50:23 +00:00
return { " message " : " All other devices have been logged out! " }
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/images/profile " )
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 settings_images_profile ( v ) :
2023-01-27 11:57:29 +00:00
if g . is_tor : abort ( 403 , " Image uploads are not allowed through TOR! " )
2022-05-04 23:09:46 +00:00
file = request . files [ " profile " ]
name = f ' /images/ { time . time ( ) } ' . replace ( ' . ' , ' ' ) + ' .webp '
file . save ( name )
2022-11-15 09:19:08 +00:00
highres = process_image ( name , v )
2022-05-04 23:09:46 +00:00
if not highres : abort ( 400 )
name2 = name . replace ( ' .webp ' , ' r.webp ' )
copyfile ( name , name2 )
2022-11-15 09:19:08 +00:00
imageurl = process_image ( name2 , v , resize = 100 )
2022-05-04 23:09:46 +00:00
if not imageurl : abort ( 400 )
2023-02-17 14:21:12 +00:00
if v . highres and ' /images/ ' in v . highres and path . isfile ( v . highres ) :
2023-03-17 10:25:49 +00:00
remove_media_using_link ( v . highres )
2022-12-05 15:10:15 +00:00
2023-02-17 14:21:12 +00:00
if v . profileurl and ' /images/ ' in v . profileurl and path . isfile ( v . profileurl ) :
2023-03-17 10:25:49 +00:00
remove_media_using_link ( v . profileurl )
2023-02-17 14:21:12 +00:00
2022-05-04 23:09:46 +00:00
v . highres = highres
v . profileurl = imageurl
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
2023-01-25 03:18:17 +00:00
cache . delete_memoized ( get_profile_picture , v . id )
cache . delete_memoized ( get_profile_picture , v . username )
cache . delete_memoized ( get_profile_picture , v . original_username )
2023-05-13 04:53:14 +00:00
cache . delete_memoized ( get_profile_picture , v . prelock_username )
2022-05-04 23:09:46 +00:00
2023-01-27 11:48:48 +00:00
return redirect ( " /settings/personal?msg=Profile picture successfully updated! " )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/images/banner " )
2022-11-14 15:11:05 +00:00
@feature_required ( ' USERS_PROFILE_BANNER ' )
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 settings_images_banner ( v ) :
2023-01-27 11:57:29 +00:00
if g . is_tor : abort ( 403 , " Image uploads are not allowed through TOR! " )
2022-05-04 23:09:46 +00:00
file = request . files [ " banner " ]
name = f ' /images/ { time . time ( ) } ' . replace ( ' . ' , ' ' ) + ' .webp '
file . save ( name )
2022-11-15 09:19:08 +00:00
bannerurl = process_image ( name , v )
2022-05-04 23:09:46 +00:00
if bannerurl :
2022-12-23 21:44:47 +00:00
if v . bannerurl and ' /images/ ' in v . bannerurl and path . isfile ( v . bannerurl ) :
2023-03-17 10:25:49 +00:00
remove_media_using_link ( v . bannerurl )
2022-05-04 23:09:46 +00:00
v . bannerurl = bannerurl
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
2023-01-27 11:48:48 +00:00
return redirect ( " /settings/personal?msg=Banner successfully updated! " )
2022-05-04 23:09:46 +00:00
2023-07-30 05:36:57 +00:00
@app.post ( " /settings/images/profile_background " )
@limiter.limit ( ' 1/second ' , scope = rpath )
@limiter.limit ( ' 1/second ' , scope = rpath , key_func = get_ID )
@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 )
@auth_required
def settings_images_profile_background ( v ) :
if g . is_tor : abort ( 403 , " Image uploads are not allowed through TOR! " )
file = request . files [ " profile_background " ]
name = f ' /images/ { time . time ( ) } ' . replace ( ' . ' , ' ' ) + ' .webp '
file . save ( name )
profile_background = process_image ( name , v )
if profile_background :
if v . profile_background and ' /images/ ' in v . profile_background and path . isfile ( v . profile_background ) :
remove_media_using_link ( v . profile_background )
v . profile_background = profile_background
g . db . add ( v )
2023-08-18 19:01:56 +00:00
badge_grant ( badge_id = 193 , user = v )
2023-07-30 05:36:57 +00:00
return redirect ( " /settings/personal?msg=Profile background successfully updated! " )
2022-05-04 23:09:46 +00:00
@app.get ( " /settings/css " )
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
2023-07-30 00:42:06 +00:00
def settings_css_get ( v ) :
2023-08-11 21:50:23 +00:00
return render_template ( " settings/css.html " , v = v , profilecss = v . profilecss )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/css " )
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 settings_css ( v ) :
2023-08-11 21:50:23 +00:00
if v . chud :
abort ( 400 , " Chudded users can ' t edit CSS! " )
2023-07-30 06:20:02 +00:00
css = request . values . get ( " css " , v . css ) . strip ( ) . replace ( ' \\ ' , ' ' ) [ : CSS_LENGTH_LIMIT ] . strip ( )
2022-05-04 23:09:46 +00:00
v . css = css
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Custom CSS successfully updated! " }
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/profilecss " )
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 settings_profilecss ( v ) :
2023-07-30 06:20:02 +00:00
profilecss = request . values . get ( " profilecss " , v . profilecss ) . replace ( ' \\ ' , ' ' ) [ : CSS_LENGTH_LIMIT ] . strip ( )
2022-08-05 17:09:41 +00:00
valid , error = validate_css ( profilecss )
if not valid :
2023-08-11 21:50:23 +00:00
abort ( 400 , error )
2022-05-04 23:09:46 +00:00
v . profilecss = profilecss
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Profile CSS successfully updated! " }
2022-11-06 07:02:15 +00:00
@app.get ( " /settings/security " )
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-11-06 07:02:15 +00:00
@auth_required
2023-07-30 00:42:06 +00:00
def settings_security ( v ) :
2022-11-09 06:11:46 +00:00
return render_template ( " settings/security.html " ,
2022-11-06 07:02:15 +00:00
v = v ,
mfa_secret = pyotp . random_base32 ( ) if not v . mfa_secret else None ,
2023-01-27 11:33:03 +00:00
now = int ( time . time ( ) ) ,
2022-11-06 07:02:15 +00:00
)
2022-05-04 23:09:46 +00:00
2023-05-05 02:16:19 +00:00
@app.get ( " /settings/blocks " )
@auth_required
2023-07-30 00:42:06 +00:00
def settings_blocks ( v ) :
2023-09-07 08:51:51 +00:00
return redirect ( f ' /@ { v . username } /blocking ' )
2023-05-05 02:16:19 +00:00
2023-09-07 08:51:57 +00:00
@app.post ( " /block_user " )
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 ( " 20/day " , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( " 20/day " , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def settings_block_user ( v ) :
2023-09-06 18:13:32 +00:00
user = get_user ( request . values . get ( " username " ) )
2023-01-01 11:36:20 +00:00
2022-05-04 23:09:46 +00:00
if user . unblockable :
2022-06-13 02:11:55 +00:00
if not v . shadowbanned :
send_notification ( user . id , f " @ { v . username } has tried to block you and failed because of your unblockable status! " )
2023-06-24 17:34:07 +00:00
g . db . commit ( )
2022-11-12 10:11:46 +00:00
abort ( 403 , f " @ { user . username } is unblockable! " )
2022-05-04 23:09:46 +00:00
2022-10-11 14:51:14 +00:00
if user . id == v . id : abort ( 400 , " You can ' t block yourself " )
2022-11-12 10:11:46 +00:00
if user . id == AUTOJANNY_ID : abort ( 403 , f " You can ' t block @ { user . username } " )
2022-10-11 14:51:14 +00:00
if v . has_blocked ( user ) : abort ( 409 , f " You have already blocked @ { user . username } " )
2022-05-04 23:09:46 +00:00
2022-11-06 07:02:15 +00:00
new_block = UserBlock ( user_id = v . id , target_id = user . id )
2023-03-16 06:27:58 +00:00
g . db . add ( new_block )
2022-05-04 23:09:46 +00:00
2023-09-05 12:30:16 +00:00
send_notification ( user . id , f " @ { v . username } has blocked you! " )
2022-05-04 23:09:46 +00:00
cache . delete_memoized ( frontlist )
2023-01-27 11:57:29 +00:00
return { " message " : f " @ { user . username } blocked! " }
2022-05-04 23:09:46 +00:00
2023-09-07 08:51:57 +00:00
@app.post ( " /unblock_user " )
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 settings_unblock_user ( v ) :
user = get_user ( request . values . get ( " username " ) )
2022-07-03 17:55:25 +00:00
x = v . has_blocked ( user )
2022-10-11 14:51:14 +00:00
if not x : abort ( 409 , " You can ' t unblock someone you haven ' t blocked " )
2023-03-16 06:27:58 +00:00
g . db . delete ( x )
2023-09-05 12:30:16 +00:00
send_notification ( user . id , f " @ { v . username } has unblocked you! " )
2022-05-04 23:09:46 +00:00
cache . delete_memoized ( frontlist )
2022-12-04 15:40:32 +00:00
return { " message " : f " @ { user . username } unblocked successfully! " }
2022-05-04 23:09:46 +00:00
@app.get ( " /settings/apps " )
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
2023-07-30 00:42:06 +00:00
def settings_apps ( v ) :
2022-11-09 06:11:46 +00:00
return render_template ( " settings/apps.html " , v = v )
2022-05-04 23:09:46 +00:00
2022-11-06 07:02:15 +00:00
@app.get ( " /settings/advanced " )
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
2023-07-30 00:42:06 +00:00
def settings_advanced_get ( v ) :
2023-08-11 21:50:23 +00:00
return render_template ( " settings/advanced.html " , v = v )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/name_change " )
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-09-14 16:49:46 +00:00
@auth_required
2022-05-04 23:09:46 +00:00
def settings_name_change ( v ) :
2023-07-24 14:41:22 +00:00
if SITE == ' rdrama.net ' and v . id == 10489 :
abort ( 403 )
2023-07-08 16:11:29 +00:00
if v . namechanged : abort ( 403 )
2023-05-13 04:53:14 +00:00
2023-06-06 19:09:44 +00:00
if v . shadowbanned : abort ( 500 )
2023-08-23 21:57:39 +00:00
new_name = request . values . get ( " name " ) . strip ( )
2022-05-04 23:09:46 +00:00
if new_name == v . username :
2023-08-11 21:50:23 +00:00
abort ( 400 , " You didn ' t change anything " )
2022-05-04 23:09:46 +00:00
2023-08-01 06:46:12 +00:00
if v . patron :
used_regex = valid_username_patron_regex
else :
used_regex = valid_username_regex
if not used_regex . fullmatch ( new_name ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " This isn ' t a valid username. " )
2022-05-04 23:09:46 +00:00
search_name = new_name . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' )
2023-03-16 06:27:58 +00:00
x = g . db . query ( User ) . filter (
2022-05-04 23:09:46 +00:00
or_ (
User . username . ilike ( search_name ) ,
2023-05-13 04:53:14 +00:00
User . original_username . ilike ( search_name ) ,
User . prelock_username . ilike ( search_name ) ,
2022-05-04 23:09:46 +00:00
)
) . one_or_none ( )
if x and x . id != v . id :
2023-08-11 21:50:23 +00:00
abort ( 400 , f " Username ` { new_name } ` is already in use. " )
2022-05-04 23:09:46 +00:00
2023-06-06 19:09:44 +00:00
v . username = new_name
v . name_changed_utc = int ( time . time ( ) )
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
2023-08-11 21:50:23 +00:00
return { " message " : " Name successfully changed! " }
2022-05-24 23:26:50 +00:00
@app.post ( " /settings/song_change_mp3 " )
2022-11-14 15:11:05 +00:00
@feature_required ( ' USERS_PROFILE_SONG ' )
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 ( " 10/day " , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( " 10/day " , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-24 23:26:50 +00:00
@auth_required
def settings_song_change_mp3 ( v ) :
file = request . files [ ' file ' ]
if file . content_type != ' audio/mpeg ' :
2023-01-27 11:48:48 +00:00
return redirect ( " /settings/personal?error=Not a valid MP3 file! " )
2022-05-24 23:26:50 +00:00
2022-07-10 15:43:27 +00:00
song = str ( time . time ( ) ) . replace ( ' . ' , ' ' )
name = f ' /songs/ { song } .mp3 '
2022-05-24 23:26:50 +00:00
file . save ( name )
size = os . stat ( name ) . st_size
if size > 8 * 1024 * 1024 :
2023-03-25 15:07:12 +00:00
os . remove ( name )
2023-01-27 11:48:48 +00:00
return redirect ( " /settings/personal?error=MP3 file must be smaller than 8MB " )
2022-05-24 23:26:50 +00:00
2023-03-16 06:27:58 +00:00
if path . isfile ( f " /songs/ { v . song } .mp3 " ) and g . db . query ( User ) . filter_by ( song = v . song ) . count ( ) == 1 :
2023-03-25 15:07:12 +00:00
os . remove ( f " /songs/ { v . song } .mp3 " )
2022-07-10 15:43:27 +00:00
v . song = song
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-24 23:26:50 +00:00
2023-01-27 11:48:48 +00:00
return redirect ( " /settings/personal?msg=Profile Anthem successfully updated! " )
2022-05-24 23:26:50 +00:00
2023-02-10 14:29:09 +00:00
def _change_song_youtube ( vid , id ) :
ydl_opts = {
2023-02-25 17:45:13 +00:00
' cookiefile ' : ' /cookies ' ,
2023-02-17 16:42:55 +00:00
' outtmpl ' : ' /temp_songs/ %(id)s . %(ext)s ' ,
2023-02-10 14:29:09 +00:00
' format ' : ' bestaudio/best ' ,
' postprocessors ' : [ {
' key ' : ' FFmpegExtractAudio ' ,
' preferredcodec ' : ' mp3 ' ,
' preferredquality ' : ' 192 ' ,
} ] ,
}
2023-08-05 01:48:47 +00:00
with yt_dlp . YoutubeDL ( ydl_opts ) as ydl :
2023-02-10 14:29:09 +00:00
try : ydl . download ( [ f " https://youtube.com/watch?v= { id } " ] )
except Exception as e :
print ( e , flush = True )
return
2023-02-17 16:42:55 +00:00
os . rename ( f " /temp_songs/ { id } .mp3 " , f " /songs/ { id } .mp3 " )
2023-02-10 14:29:09 +00:00
2023-07-28 00:27:01 +00:00
db = db_session ( )
v = db . query ( User ) . filter_by ( id = vid ) . options ( load_only ( User . song ) ) . one ( )
if v . song and path . isfile ( f " /songs/ { v . song } .mp3 " ) and db . query ( User ) . filter_by ( song = v . song ) . count ( ) == 1 :
os . remove ( f " /songs/ { v . song } .mp3 " )
2023-02-10 14:29:09 +00:00
v . song = id
db . add ( v )
db . commit ( )
db . close ( )
stdout . flush ( )
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/song_change " )
2022-11-14 15:11:05 +00:00
@feature_required ( ' USERS_PROFILE_SONG ' )
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 ( " 10/day " , deduct_when = lambda response : response . status_code < 400 )
@limiter.limit ( " 10/day " , deduct_when = lambda response : response . status_code < 400 , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
def settings_song_change ( v ) :
2023-08-23 21:57:39 +00:00
song = request . values . get ( " song " ) . strip ( )
2022-05-04 23:09:46 +00:00
if song == " " and v . song :
2023-03-16 06:27:58 +00:00
if path . isfile ( f " /songs/ { v . song } .mp3 " ) and g . db . query ( User ) . filter_by ( song = v . song ) . count ( ) == 1 :
2023-03-25 15:07:12 +00:00
os . remove ( f " /songs/ { v . song } .mp3 " )
2022-05-04 23:09:46 +00:00
v . song = None
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-01-27 11:48:48 +00:00
return redirect ( " /settings/personal?msg=Profile Anthem successfully removed! " )
2022-05-04 23:09:46 +00:00
song = song . replace ( " https://music.youtube.com " , " https://youtube.com " )
if song . startswith ( ( " https://www.youtube.com/watch?v= " , " https://youtube.com/watch?v= " , " https://m.youtube.com/watch?v= " ) ) :
id = song . split ( " v= " ) [ 1 ]
elif song . startswith ( " https://youtu.be/ " ) :
id = song . split ( " https://youtu.be/ " ) [ 1 ]
else :
2023-08-10 12:37:57 +00:00
return redirect ( " /settings/personal?error=Not a YouTube link! " )
2022-05-04 23:09:46 +00:00
if " ? " in id : id = id . split ( " ? " ) [ 0 ]
if " & " in id : id = id . split ( " & " ) [ 0 ]
2022-11-22 12:13:44 +00:00
if not yt_id_regex . fullmatch ( id ) :
2023-08-10 12:37:57 +00:00
return redirect ( " /settings/personal?error=Not a YouTube link! " )
2023-01-01 11:36:20 +00:00
if path . isfile ( f ' /songs/ { id } .mp3 ' ) :
2022-05-04 23:09:46 +00:00
v . song = id
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-01-27 11:48:48 +00:00
return redirect ( " /settings/personal?msg=Profile Anthem successfully updated! " )
2023-01-01 11:36:20 +00:00
2023-02-17 15:20:51 +00:00
if YOUTUBE_KEY != DEFAULT_CONFIG_VALUE :
2023-09-09 17:06:26 +00:00
req = requests . get ( f " https://www.googleapis.com/youtube/v3/videos?id= { id } &key= { YOUTUBE_KEY } &part=contentDetails " , headers = HEADERS , timeout = 5 ) . json ( )
2023-08-25 21:11:32 +00:00
try :
duration = req [ ' items ' ] [ 0 ] [ ' contentDetails ' ] [ ' duration ' ]
except :
return redirect ( " /settings/personal?error=Anthem change failed, please try another video! " )
2023-02-17 15:20:51 +00:00
if duration == ' P0D ' :
2023-08-10 12:37:57 +00:00
return redirect ( " /settings/personal?error=Can ' t use a live youtube video! " )
2022-05-04 23:09:46 +00:00
2023-02-17 15:20:51 +00:00
if " H " in duration :
2023-08-10 12:37:57 +00:00
return redirect ( " /settings/personal?error=Duration of the video must not exceed 15 minutes! " )
2022-05-04 23:09:46 +00:00
2023-02-17 15:20:51 +00:00
if " M " in duration :
duration = int ( duration . split ( " PT " ) [ 1 ] . split ( " M " ) [ 0 ] )
if duration > 15 :
2023-08-10 12:37:57 +00:00
return redirect ( " /settings/personal?error=Duration of the video must not exceed 15 minutes! " )
2023-02-17 15:20:51 +00:00
2023-08-20 16:24:26 +00:00
gevent . spawn ( _change_song_youtube , v . id , id )
2022-05-04 23:09:46 +00:00
2023-02-10 14:29:09 +00:00
return redirect ( " /settings/personal?msg=Profile Anthem successfully updated. Wait 5 minutes for the change to take effect. " )
2022-05-04 23:09:46 +00:00
2023-07-29 23:39:22 +00:00
2023-08-30 17:01:53 +00:00
def process_settings_plaintext ( value , current , length , default_value ) :
2023-07-29 23:39:22 +00:00
value = request . values . get ( value , " " ) . strip ( )
if not value :
2023-08-30 17:01:53 +00:00
return default_value
2023-07-29 23:39:22 +00:00
if len ( value ) > 100 :
2023-08-11 21:50:23 +00:00
abort ( 400 , " The value you entered exceeds the character limit (100 characters) " )
2023-07-29 23:39:22 +00:00
if value == current :
2023-08-11 21:50:23 +00:00
abort ( 400 , " You didn ' t change anything! " )
2023-07-29 23:39:22 +00:00
return value
2022-05-04 23:09:46 +00:00
@app.post ( " /settings/title_change " )
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 settings_title_change ( v ) :
if v . flairchanged : abort ( 403 )
2023-01-01 11:36:20 +00:00
2023-08-30 17:01:53 +00:00
customtitleplain = process_settings_plaintext ( " title " , v . customtitleplain , 100 , None )
2022-12-28 09:28:00 +00:00
2023-08-30 17:01:53 +00:00
if customtitleplain :
customtitle = filter_emojis_only ( customtitleplain )
customtitle = censor_slurs ( customtitle , None )
2022-05-04 23:09:46 +00:00
2023-08-30 17:01:53 +00:00
if len ( customtitle ) > 1000 :
abort ( 400 , " Flair too long! " )
else :
customtitle = None
2022-07-12 20:09:59 +00:00
2023-08-11 21:50:23 +00:00
v . customtitleplain = customtitleplain
2022-08-26 22:01:36 +00:00
v . customtitle = customtitle
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-05-04 23:09:46 +00:00
2023-08-11 21:50:23 +00:00
return { " message " : " Flair successfully updated! " }
2022-05-04 23:09:46 +00:00
2022-07-11 16:46:08 +00:00
@app.post ( " /settings/pronouns_change " )
2022-11-13 12:24:58 +00:00
@feature_required ( ' PRONOUNS ' )
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-07-11 16:46:08 +00:00
@auth_required
def settings_pronouns_change ( v ) :
2023-08-30 17:01:53 +00:00
pronouns = process_settings_plaintext ( " pronouns " , v . pronouns , 15 , " they/them " )
2023-08-01 07:38:58 +00:00
2022-07-11 16:46:08 +00:00
if not pronouns_regex . fullmatch ( pronouns ) :
2023-08-11 21:50:23 +00:00
abort ( 400 , " The pronouns you entered don ' t match the required format! " )
2022-07-11 16:46:08 +00:00
2022-08-16 16:16:04 +00:00
bare_pronouns = pronouns . lower ( ) . replace ( ' / ' , ' ' )
if ' nig ' in bare_pronouns : pronouns = ' BI/POC '
elif ' fag ' in bare_pronouns : pronouns = ' cute/twink '
2022-07-11 16:46:08 +00:00
v . pronouns = pronouns
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2022-07-11 16:46:08 +00:00
2023-08-11 21:50:23 +00:00
return { " message " : " Pronouns successfully updated! " }
2022-07-11 16:46:08 +00:00
2022-05-10 07:20:49 +00:00
@app.post ( " /settings/checkmark_text " )
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-10 07:20:49 +00:00
@auth_required
def settings_checkmark_text ( v ) :
2023-07-30 05:55:07 +00:00
if not v . verified :
abort ( 403 , " You don ' t have a checkmark to edit its hover text! " )
2023-07-29 23:39:22 +00:00
2023-08-30 17:01:53 +00:00
v . verified = process_settings_plaintext ( " checkmark-text " , v . verified , 100 , " Verified " )
2023-03-16 06:27:58 +00:00
g . db . add ( v )
2023-08-11 21:50:23 +00:00
return { " message " : " Checkmark Text successfully updated! " }